From 1e864a508d7f030b79e9bf58667354057694b1fc Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Tue, 5 Sep 2023 11:13:27 +0200 Subject: [PATCH] Rendering, tmp commit before refactoring --- cypress/platform/knsv2.html | 24 +- packages/mermaid/src/dagre-wrapper/nodes.js | 16 +- .../mermaid/src/diagrams/block/blockDB.ts | 14 + .../src/diagrams/block/blockDiagram.ts | 2 + .../src/diagrams/block/blockRenderer.ts | 126 +++--- .../mermaid/src/diagrams/block/blockTypes.ts | 7 + packages/mermaid/src/diagrams/block/layout.ts | 108 +++++ .../src/diagrams/block/renderHelpers.ts | 270 ++++++++++++ packages/mermaid/src/diagrams/block/styles.ts | 144 +++++++ .../flowchart/swimlane/swimlaneRenderer.js | 400 ++++++++++++++++++ 10 files changed, 1033 insertions(+), 78 deletions(-) create mode 100644 packages/mermaid/src/diagrams/block/layout.ts create mode 100644 packages/mermaid/src/diagrams/block/renderHelpers.ts create mode 100644 packages/mermaid/src/diagrams/block/styles.ts create mode 100644 packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index e19d53ae4..4609331dd 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -24,6 +24,9 @@ h1 { color: grey; } + .mermaid { + border: 1px solid #ddd; + } .mermaid2 { display: none; } @@ -59,16 +62,17 @@
 block-beta
-          id
+ id1("Wide 1") + id2("2") + id3("3") + id4("A final one") + +
 flowchart RL
-    subgraph "`one`"
-      a1 -- l1 --> a2
-      a1 -- l2 --> a2
-    end
+    id
     
-
+    
 flowchart RL
     subgraph "`one`"
       a1 -- l1 --> a2
@@ -93,11 +97,11 @@ flowchart LR
         way`"]
   
-
+    
       classDiagram-v2
         note "I love this diagram!\nDo you love it?"
     
-
+    
     stateDiagram-v2
     State1: The state with a note with minus - and plus + in it
     note left of State1
@@ -142,7 +146,7 @@ mindmap
       शान्तिः سلام  和平 `"]
 
     
-
+    
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 flowchart TB
   %% I could not figure out how to use double quotes in labels in Mermaid
diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js
index 6c6733358..e6a9d982a 100644
--- a/packages/mermaid/src/dagre-wrapper/nodes.js
+++ b/packages/mermaid/src/dagre-wrapper/nodes.js
@@ -1037,14 +1037,14 @@ export const positionNode = (node) => {
   const padding = 8;
   const diff = node.diff || 0;
   if (node.clusterNode) {
-    el.attr(
-      'transform',
-      'translate(' +
-        (node.x + diff - node.width / 2) +
-        ', ' +
-        (node.y - node.height / 2 - padding) +
-        ')'
-    );
+      el.attr(
+        'transform',
+        'translate(' +
+          (node.x + diff - node.width / 2) +
+          ', ' +
+          (node.y - node.height / 2 - padding) +
+          ')'
+      );
   } else {
     el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
   }
diff --git a/packages/mermaid/src/diagrams/block/blockDB.ts b/packages/mermaid/src/diagrams/block/blockDB.ts
index 7c90ad2db..039353830 100644
--- a/packages/mermaid/src/diagrams/block/blockDB.ts
+++ b/packages/mermaid/src/diagrams/block/blockDB.ts
@@ -106,6 +106,16 @@ const getBlocks: IGetBlocks = () => {
   log.info('Block in test', blocks, blocks[0].id);
   return blocks || [];
 };
+type IGetBlock = (id: string) => Block | undefined;
+const getBlock: IGetBlock = (id: string) => {
+  log.info('Block in test', blocks, blocks[0].id);
+  return blockDatabase[id];
+};
+type ISetBlock = (block: Block) => void;
+const setBlock: ISetBlock = (block: Block) => {
+  log.info('Block in test', blocks, blocks[0].id);
+  blockDatabase[block.id] = block;
+};
 
 type IGetLinks = () => Link[];
 const getLinks: IGetLinks = () => links;
@@ -119,6 +129,8 @@ export interface BlockDB extends DiagramDB {
   addLink: IAddLink;
   getLogger: IGetLogger;
   getBlocks: IGetBlocks;
+  getBlock: IGetBlock;
+  setBlock: ISetBlock;
   getLinks: IGetLinks;
   getColumns: IGetColumns;
   typeStr2Type: ITypeStr2Type;
@@ -134,6 +146,8 @@ const db: BlockDB = {
   getBlocks,
   getLinks,
   setHierarchy,
+  getBlock,
+  setBlock,
   // getAccTitle,
   // setAccTitle,
   // getAccDescription,
diff --git a/packages/mermaid/src/diagrams/block/blockDiagram.ts b/packages/mermaid/src/diagrams/block/blockDiagram.ts
index e098360f4..b3071cb0b 100644
--- a/packages/mermaid/src/diagrams/block/blockDiagram.ts
+++ b/packages/mermaid/src/diagrams/block/blockDiagram.ts
@@ -2,6 +2,7 @@ import { DiagramDefinition } from '../../diagram-api/types.js';
 // @ts-ignore: jison doesn't export types
 import parser from './parser/block.jison';
 import db from './blockDB.js';
+import flowStyles from './styles.js';
 import renderer from './blockRenderer.js';
 
 // TODO: do we need this?
@@ -14,4 +15,5 @@ export const diagram: DiagramDefinition = {
   parser,
   db,
   renderer,
+  styles: flowStyles,
 };
diff --git a/packages/mermaid/src/diagrams/block/blockRenderer.ts b/packages/mermaid/src/diagrams/block/blockRenderer.ts
index 84acdaf3f..0dac714d4 100644
--- a/packages/mermaid/src/diagrams/block/blockRenderer.ts
+++ b/packages/mermaid/src/diagrams/block/blockRenderer.ts
@@ -1,20 +1,30 @@
 import { Diagram } from '../../Diagram.js';
 import * as configApi from '../../config.js';
-
+import { calculateBlockSizes } from './renderHelpers.js';
+import { layout } from './layout.js';
+import { setupGraphViewbox } from '../../setupGraphViewbox.js';
 import {
   select as d3select,
   scaleOrdinal as d3scaleOrdinal,
   schemeTableau10 as d3schemeTableau10,
+  ContainerElement,
 } from 'd3';
 
-import { BlockDB, Block } from './blockDB.js';
+import { BlockDB } from './blockDB.js';
+import type { Block } from './blockTypes.js';
 
 // import { diagram as BlockDiagram } from './blockDiagram.js';
 import { configureSvgSize } from '../../setupGraphViewbox.js';
 import { Uid } from '../../rendering-util/uid.js';
 
-export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void {
-  const { securityLevel } = configApi.getConfig();
+export const draw = async function (
+  text: string,
+  id: string,
+  _version: string,
+  diagObj: Diagram
+): Promise {
+  const { securityLevel, flowchart: conf } = configApi.getConfig();
+  const db = diagObj.db as BlockDB;
   let sandboxElement: any;
   if (securityLevel === 'sandbox') {
     sandboxElement = d3select('#i' + id);
@@ -27,12 +37,23 @@ export const draw = function (text: string, id: string, _version: string, diagOb
   // @ts-ignore TODO root.select is not callable
   const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
 
+  const bl = db.getBlocks();
+
+  const nodes = svg.insert('g').attr('class', 'block');
+  await calculateBlockSizes(nodes, bl, db);
+  const bounds = layout(db);
+
+  console.log('Here', bl);
+
   // Establish svg dimensions and get width and height
-  //  
-  const height = 400;
-  const width = 600;
+  //
+  // const bounds = nodes.node().getBoundingClientRect();
+  const height = bounds.height;
+  const width = bounds.width;
   const useMaxWidth = false;
   configureSvgSize(svg, height, width, useMaxWidth);
+  console.log('Here Bounds', bounds);
+  svg.attr('viewBox', `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`);
 
   // Prepare data for construction based on diagObj.db
   // This must be a mutable object with `nodes` and `links` properties:
@@ -53,107 +74,92 @@ export const draw = function (text: string, id: string, _version: string, diagOb
 
   const blocks: LayedBlock[] = [
     {
-      ID: "ApplicationLayer",
-      label: "Application Layer",
+      ID: 'ApplicationLayer',
+      label: 'Application Layer',
       x: 0,
       y: 0,
       children: [
         {
-          ID: "UserInterface",
-          label: "User Interface (WPF, HTML5/CSS3, Swing)",
+          ID: 'UserInterface',
+          label: 'User Interface (WPF, HTML5/CSS3, Swing)',
           x: 0,
-          y: 50,    
-        }
+          y: 50,
+        },
       ],
     },
     {
-      ID: "PresentationLayer",
-      label: "Presentation Layer",
+      ID: 'PresentationLayer',
+      label: 'Presentation Layer',
       x: 0,
       y: 50,
       children: [
         {
-          ID: "Smack",
-          label: "J2SE Mobil App (Smack)"
+          ID: 'Smack',
+          label: 'J2SE Mobil App (Smack)',
         },
         {
-          ID: "JsJAC",
-          label: "Java Script Browser App (JsJAC)",
+          ID: 'JsJAC',
+          label: 'Java Script Browser App (JsJAC)',
         },
         {
-          ID: "babelim",
-          label: ".NET Windows App (Babel-im)",
+          ID: 'babelim',
+          label: '.NET Windows App (Babel-im)',
         },
-      ]
+      ],
     },
     {
-      ID: "SessionLayer",
-      label: "Session Layer",
+      ID: 'SessionLayer',
+      label: 'Session Layer',
       x: 0,
       y: 100,
       children: [
         {
-          ID: "XMPP",
-          label: "XMPP Component"
+          ID: 'XMPP',
+          label: 'XMPP Component',
         },
         {
           children: [
             {
-              ID: "Authentication",
-              label: "Authentication",
+              ID: 'Authentication',
+              label: 'Authentication',
             },
             {
-              ID: "Authorization",
-              label: "Authorization",
+              ID: 'Authorization',
+              label: 'Authorization',
             },
-          ]
+          ],
         },
         {
-          ID: "LDAP",
-          label: "LDAP, DB, POP",
+          ID: 'LDAP',
+          label: 'LDAP, DB, POP',
         },
-      ]
+      ],
     },
     {
-      ID: "NetworkLayer",
-      label: "Network Layer",
+      ID: 'NetworkLayer',
+      label: 'Network Layer',
       x: 0,
       y: 150,
       children: [
-        { ID: "HTTP", label: "HTTP" },
-        { ID: "SOCK", label: "SOCK" },
-      ]
+        { ID: 'HTTP', label: 'HTTP' },
+        { ID: 'SOCK', label: 'SOCK' },
+      ],
     },
     {
-      ID: "DataLayer",
-      label: "Data Layer",
+      ID: 'DataLayer',
+      label: 'Data Layer',
       x: 0,
       y: 200,
       children: [
-        { ID: "XMPP", label: "XMPP" },
-        { ID: "BDB", label: "Business DB" },
-        { ID: "AD", label: "Active Directory" },
-      ]
+        { ID: 'XMPP', label: 'XMPP' },
+        { ID: 'BDB', label: 'Business DB' },
+        { ID: 'AD', label: 'Active Directory' },
+      ],
     },
   ];
 
   // Get color scheme for the graph
   const colorScheme = d3scaleOrdinal(d3schemeTableau10);
-
-  svg
-    .append('g')
-    .attr('class', 'block')
-    .selectAll('.block')
-    .data(blocks)
-    .join('rect')
-    .attr('x', (d: any) => d.x || 0)
-    .attr('y', (d: any) => d.y || 0)
-    .attr('class', 'block')
-    .attr('stroke', 'black')
-    .attr('height', (d: any) => 50)
-    .attr('width', (d: any) => 100)
-    .attr('fill', (d: any) => colorScheme(d.ID));
-
 };
 
 export default {
diff --git a/packages/mermaid/src/diagrams/block/blockTypes.ts b/packages/mermaid/src/diagrams/block/blockTypes.ts
index aca83f421..5a4431c0a 100644
--- a/packages/mermaid/src/diagrams/block/blockTypes.ts
+++ b/packages/mermaid/src/diagrams/block/blockTypes.ts
@@ -31,6 +31,13 @@ export interface Block {
   parent?: Block;
   type?: BlockType;
   children: Block[];
+  size?: {
+    width: number;
+    height: number;
+    x: number;
+    y: number;
+  };
+  node?: any;
   columns?: number; // | TBlockColumnsDefaultValue;
 }
 
diff --git a/packages/mermaid/src/diagrams/block/layout.ts b/packages/mermaid/src/diagrams/block/layout.ts
new file mode 100644
index 000000000..65b99c154
--- /dev/null
+++ b/packages/mermaid/src/diagrams/block/layout.ts
@@ -0,0 +1,108 @@
+import { BlockDB } from './blockDB.js';
+import type { Block } from './blockTypes.js';
+
+function layoutBLock(block: Block, db: BlockDB) {
+  if (block.children) {
+    for (const child of block.children) {
+      layoutBLock(child, db);
+    }
+    // find max width of children
+    let maxWidth = 0;
+    let maxHeight = 0;
+    for (const child of block.children) {
+      const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 };
+      if (width > maxWidth) {
+        maxWidth = width;
+      }
+      if (height > maxHeight) {
+        maxHeight = height;
+      }
+    }
+
+    // set width of block to max width of children
+    for (const child of block.children) {
+      if (child.size) {
+        child.size.width = maxWidth;
+        child.size.height = maxHeight;
+      }
+    }
+
+    // Position items
+    let x = 0;
+    let y = 0;
+    const padding = 10;
+    for (const child of block.children) {
+      if (child.size) {
+        child.size.x = x;
+        child.size.y = y;
+        x += maxWidth + padding;
+      }
+    }
+  }
+}
+
+function positionBlock(block: Block, db: BlockDB) {
+  console.log('Here Positioning', block?.size?.node);
+  // const o = db.getBlock(block.id);
+  // const node;
+  if (block?.size?.node) {
+    const node = block?.size?.node;
+    const size = block?.size;
+    console.log('Here as well', node);
+    if (node) {
+      node.attr(
+        'transform',
+        'translate(' + (size.x - size.width / 2) + ', ' + (size.y - size.height / 2) + ')'
+      );
+    }
+  }
+  if (block.children) {
+    for (const child of block.children) {
+      positionBlock(child, db);
+    }
+  }
+}
+let minX = 0;
+let minY = 0;
+let maxX = 0;
+let maxY = 0;
+
+function findBounds(block: Block) {
+  if (block.size) {
+    const { x, y, width, height } = block.size;
+    console.log('Here', minX, minY, x, y, width, height);
+    if (x - width < minX) {
+      minX = x - width;
+    }
+    if (y - height < minY) {
+      minY = y - height;
+    }
+    if (x > maxX) {
+      maxX = x;
+    }
+    if (y > maxY) {
+      maxY = y;
+    }
+  }
+  if (block.children) {
+    for (const child of block.children) {
+      findBounds(child);
+    }
+  }
+}
+
+export function layout(db: BlockDB) {
+  const blocks = db.getBlocks();
+  const root = { id: 'root', type: 'composite', children: blocks } as Block;
+  layoutBLock(root, db);
+  positionBlock(root, db);
+
+  minX = 0;
+  minY = 0;
+  maxX = 0;
+  maxY = 0;
+  findBounds(root);
+  const height = maxY - minY;
+  const width = maxX - minX;
+  return { x: minX, y: minY, width, height };
+}
diff --git a/packages/mermaid/src/diagrams/block/renderHelpers.ts b/packages/mermaid/src/diagrams/block/renderHelpers.ts
new file mode 100644
index 000000000..34d8baa05
--- /dev/null
+++ b/packages/mermaid/src/diagrams/block/renderHelpers.ts
@@ -0,0 +1,270 @@
+import { getStylesFromArray } from '../../utils.js';
+import { insertNode } from '../../dagre-wrapper/nodes.js';
+import { getConfig } from '../../config.js';
+import { ContainerElement } from 'd3';
+import type { Block } from './blockTypes.js';
+import { BlockDB } from './blockDB.js';
+
+function getNodeFromBlock(block: Block, db: BlockDB) {
+  const vertex = block;
+
+  /**
+   * Variable for storing the classes for the vertex
+   *
+   * @type {string}
+   */
+  let classStr = 'default';
+  if ((vertex?.classes?.length || []) > 0) {
+    classStr = vertex.classes.join(' ');
+  }
+  classStr = classStr + ' flowchart-label';
+
+  // We create a SVG label, either by delegating to addHtmlLabel or manually
+  let vertexNode;
+  const labelData = { width: 0, height: 0 };
+
+  let radious = 0;
+  let _shape = '';
+  let layoutOptions = {};
+  // Set the shape based parameters
+  switch (vertex.type) {
+    case 'round':
+      radious = 5;
+      _shape = 'rect';
+      break;
+    case 'square':
+      _shape = 'rect';
+      break;
+    case 'diamond':
+      _shape = 'question';
+      layoutOptions = {
+        portConstraints: 'FIXED_SIDE',
+      };
+      break;
+    case 'hexagon':
+      _shape = 'hexagon';
+      break;
+    case 'odd':
+      _shape = 'rect_left_inv_arrow';
+      break;
+    case 'lean_right':
+      _shape = 'lean_right';
+      break;
+    case 'lean_left':
+      _shape = 'lean_left';
+      break;
+    case 'trapezoid':
+      _shape = 'trapezoid';
+      break;
+    case 'inv_trapezoid':
+      _shape = 'inv_trapezoid';
+      break;
+    case 'odd_right':
+      _shape = 'rect_left_inv_arrow';
+      break;
+    case 'circle':
+      _shape = 'circle';
+      break;
+    case 'ellipse':
+      _shape = 'ellipse';
+      break;
+    case 'stadium':
+      _shape = 'stadium';
+      break;
+    case 'subroutine':
+      _shape = 'subroutine';
+      break;
+    case 'cylinder':
+      _shape = 'cylinder';
+      break;
+    case 'group':
+      _shape = 'rect';
+      break;
+    case 'doublecircle':
+      _shape = 'doublecircle';
+      break;
+    default:
+      _shape = 'rect';
+  }
+
+  // const styles = getStylesFromArray(vertex.styles);
+  const styles = getStylesFromArray([]);
+
+  // Use vertex id as text in the box if no text is provided by the graph definition
+  const vertexText = vertex.label;
+
+  // Add the node
+  const node = {
+    labelStyle: styles.labelStyle,
+    shape: _shape,
+    labelText: vertexText,
+    // labelType: vertex.labelType,
+    rx: radious,
+    ry: radious,
+    class: classStr,
+    style: styles.style,
+    id: vertex.id,
+    // link: vertex.link,
+    // linkTarget: vertex.linkTarget,
+    // tooltip: diagObj.db.getTooltip(vertex.id) || '',
+    // domId: diagObj.db.lookUpDomId(vertex.id),
+    // haveCallback: vertex.haveCallback,
+    // width: vertex.type === 'group' ? 500 : undefined,
+    // dir: vertex.dir,
+    type: vertex.type,
+    // props: vertex.props,
+    padding: getConfig()?.flowchart?.padding || 0,
+  };
+  return node;
+}
+
+async function calculateBlockSize(elem: any, block: any, db: any) {
+  console.log('Here befoire 3');
+  const node = getNodeFromBlock(block, db);
+  if (node.type === 'group') return;
+
+  // Add the element to the DOM to size it
+  const nodeEl = await insertNode(elem, node);
+  const boundingBox = nodeEl.node().getBBox();
+  const obj = db.getBlock(node.id);
+  console.log('Here el', nodeEl);
+  obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
+  db.setBlock(obj);
+  // nodeEl.remove();
+}
+
+export async function calculateBlockSizes(elem: ContainerElement, blocks: Block[], db: BlockDB) {
+  console.log('Here before 2');
+  for (const block of blocks) {
+    await calculateBlockSize(elem, block, db);
+    if (block.children) {
+      await calculateBlockSizes(elem, block.children, db);
+    }
+  }
+}
+export async function insertBlockPositioned(elem: any, block: any, db: any) {
+  const vertex = block;
+
+  /**
+   * Variable for storing the classes for the vertex
+   *
+   * @type {string}
+   */
+  let classStr = 'default';
+  if ((vertex?.classes?.length || []) > 0) {
+    classStr = vertex.classes.join(' ');
+  }
+  classStr = classStr + ' flowchart-label';
+
+  // We create a SVG label, either by delegating to addHtmlLabel or manually
+  let vertexNode;
+  const labelData = { width: 0, height: 0 };
+
+  let radious = 0;
+  let _shape = '';
+  let layoutOptions = {};
+  // Set the shape based parameters
+  switch (vertex.type) {
+    case 'round':
+      radious = 5;
+      _shape = 'rect';
+      break;
+    case 'square':
+      _shape = 'rect';
+      break;
+    case 'diamond':
+      _shape = 'question';
+      layoutOptions = {
+        portConstraints: 'FIXED_SIDE',
+      };
+      break;
+    case 'hexagon':
+      _shape = 'hexagon';
+      break;
+    case 'odd':
+      _shape = 'rect_left_inv_arrow';
+      break;
+    case 'lean_right':
+      _shape = 'lean_right';
+      break;
+    case 'lean_left':
+      _shape = 'lean_left';
+      break;
+    case 'trapezoid':
+      _shape = 'trapezoid';
+      break;
+    case 'inv_trapezoid':
+      _shape = 'inv_trapezoid';
+      break;
+    case 'odd_right':
+      _shape = 'rect_left_inv_arrow';
+      break;
+    case 'circle':
+      _shape = 'circle';
+      break;
+    case 'ellipse':
+      _shape = 'ellipse';
+      break;
+    case 'stadium':
+      _shape = 'stadium';
+      break;
+    case 'subroutine':
+      _shape = 'subroutine';
+      break;
+    case 'cylinder':
+      _shape = 'cylinder';
+      break;
+    case 'group':
+      _shape = 'rect';
+      break;
+    case 'doublecircle':
+      _shape = 'doublecircle';
+      break;
+    default:
+      _shape = 'rect';
+  }
+
+  // const styles = getStylesFromArray(vertex.styles);
+  const styles = getStylesFromArray([]);
+
+  // Use vertex id as text in the box if no text is provided by the graph definition
+  const vertexText = vertex.label;
+
+  // Add the node
+  const node = {
+    labelStyle: styles.labelStyle,
+    shape: _shape,
+    labelText: vertexText,
+    labelType: vertex.labelType,
+    rx: radious,
+    ry: radious,
+    class: classStr,
+    style: styles.style,
+    id: vertex.id,
+    link: vertex.link,
+    linkTarget: vertex.linkTarget,
+    // tooltip: diagObj.db.getTooltip(vertex.id) || '',
+    // domId: diagObj.db.lookUpDomId(vertex.id),
+    haveCallback: vertex.haveCallback,
+    width: vertex.width,
+    height: vertex.height,
+    dir: vertex.dir,
+    type: vertex.type,
+    props: vertex.props,
+    padding: getConfig()?.flowchart?.padding || 0,
+  };
+  let boundingBox;
+  let nodeEl;
+
+  // Add the element to the DOM
+  if (node.type !== 'group') {
+    nodeEl = await insertNode(elem, node, vertex.dir);
+    // nodeEl.remove();
+    boundingBox = nodeEl.node().getBBox();
+    if (node.id) {
+      const obj = db.getBlock(node.id);
+      obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
+      db.setBlock(obj);
+    }
+  }
+}
diff --git a/packages/mermaid/src/diagrams/block/styles.ts b/packages/mermaid/src/diagrams/block/styles.ts
new file mode 100644
index 000000000..a4af4f128
--- /dev/null
+++ b/packages/mermaid/src/diagrams/block/styles.ts
@@ -0,0 +1,144 @@
+// import khroma from 'khroma';
+import * as khroma from 'khroma';
+
+/** Returns the styles given options */
+export interface FlowChartStyleOptions {
+  arrowheadColor: string;
+  border2: string;
+  clusterBkg: string;
+  clusterBorder: string;
+  edgeLabelBackground: string;
+  fontFamily: string;
+  lineColor: string;
+  mainBkg: string;
+  nodeBorder: string;
+  nodeTextColor: string;
+  tertiaryColor: string;
+  textColor: string;
+  titleColor: string;
+}
+
+const fade = (color: string, opacity: number) => {
+  // @ts-ignore TODO: incorrect types from khroma
+  const channel = khroma.channel;
+
+  const r = channel(color, 'r');
+  const g = channel(color, 'g');
+  const b = channel(color, 'b');
+
+  // @ts-ignore incorrect types from khroma
+  return khroma.rgba(r, g, b, opacity);
+};
+
+const getStyles = (options: FlowChartStyleOptions) =>
+  `.label {
+    font-family: ${options.fontFamily};
+    color: ${options.nodeTextColor || options.textColor};
+  }
+  .cluster-label text {
+    fill: ${options.titleColor};
+  }
+  .cluster-label span,p {
+    color: ${options.titleColor};
+  }
+
+  .label text,span,p {
+    fill: ${options.nodeTextColor || options.textColor};
+    color: ${options.nodeTextColor || options.textColor};
+  }
+
+  .node rect,
+  .node circle,
+  .node ellipse,
+  .node polygon,
+  .node path {
+    fill: ${options.mainBkg};
+    stroke: ${options.nodeBorder};
+    stroke-width: 1px;
+  }
+  .flowchart-label text {
+    text-anchor: middle;
+  }
+  // .flowchart-label .text-outer-tspan {
+  //   text-anchor: middle;
+  // }
+  // .flowchart-label .text-inner-tspan {
+  //   text-anchor: start;
+  // }
+
+  .node .label {
+    text-align: center;
+  }
+  .node.clickable {
+    cursor: pointer;
+  }
+
+  .arrowheadPath {
+    fill: ${options.arrowheadColor};
+  }
+
+  .edgePath .path {
+    stroke: ${options.lineColor};
+    stroke-width: 2.0px;
+  }
+
+  .flowchart-link {
+    stroke: ${options.lineColor};
+    fill: none;
+  }
+
+  .edgeLabel {
+    background-color: ${options.edgeLabelBackground};
+    rect {
+      opacity: 0.5;
+      background-color: ${options.edgeLabelBackground};
+      fill: ${options.edgeLabelBackground};
+    }
+    text-align: center;
+  }
+
+  /* For html labels only */
+  .labelBkg {
+    background-color: ${fade(options.edgeLabelBackground, 0.5)};
+    // background-color:
+  }
+
+  .cluster rect {
+    fill: ${options.clusterBkg};
+    stroke: ${options.clusterBorder};
+    stroke-width: 1px;
+  }
+
+  .cluster text {
+    fill: ${options.titleColor};
+  }
+
+  .cluster span,p {
+    color: ${options.titleColor};
+  }
+  /* .cluster div {
+    color: ${options.titleColor};
+  } */
+
+  div.mermaidTooltip {
+    position: absolute;
+    text-align: center;
+    max-width: 200px;
+    padding: 2px;
+    font-family: ${options.fontFamily};
+    font-size: 12px;
+    background: ${options.tertiaryColor};
+    border: 1px solid ${options.border2};
+    border-radius: 2px;
+    pointer-events: none;
+    z-index: 100;
+  }
+
+  .flowchartTitleText {
+    text-anchor: middle;
+    font-size: 18px;
+    fill: ${options.textColor};
+  }
+`;
+
+export default getStyles;
diff --git a/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js
new file mode 100644
index 000000000..5b7d5976f
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js
@@ -0,0 +1,400 @@
+import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
+import { select, curveLinear, selectAll } from 'd3';
+import { swimlaneLayout } from './swimlane-layout.js';
+import { insertNode } from '../../../dagre-wrapper/nodes.js';
+import flowDb from '../flowDb.js';
+import { getConfig } from '../../../config.js';
+import { getStylesFromArray } from '../../../utils.js';
+import setupGraph, { addEdges, addVertices } from './setup-graph.js';
+import { render } from '../../../dagre-wrapper/index.js';
+import { log } from '../../../logger.js';
+import { setupGraphViewbox } from '../../../setupGraphViewbox.js';
+import common, { evaluate } from '../../common/common.js';
+import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
+import { insertEdge, positionEdgeLabel } from '../../../dagre-wrapper/edges.js';
+import {
+  clear as clearGraphlib,
+  clusterDb,
+  adjustClustersAndEdges,
+  findNonClusterChild,
+  sortNodesByHierarchy,
+} from '../../../dagre-wrapper/mermaid-graphlib.js';
+
+const conf = {};
+export const setConf = function (cnf) {
+  const keys = Object.keys(cnf);
+  for (const key of keys) {
+    conf[key] = cnf[key];
+  }
+};
+
+/**
+ *
+ * @param element
+ * @param graph
+ * @param layout
+ * @param elem
+ * @param conf
+ */
+async function swimlaneRender(layout, vert, elem, g, id, conf) {
+  let max;
+  // draw nodes from layout.graph to element
+  const nodes = layout.graph.nodes();
+
+  // lanes are the swimlanes
+  const lanes = layout.lanes;
+
+  const nodesElements = elem.insert('g').attr('class', 'nodes');
+  // for each node, draw a rect, with a child text inside as label
+  for (const node of nodes) {
+    const nodeFromLayout = layout.graph.node(node);
+    const vertex = vert[node];
+    //Initialise the node
+    /**
+     * Variable for storing the classes for the vertex
+     *
+     * @type {string}
+     */
+    let classStr = 'default';
+    if (vertex.classes.length > 0) {
+      classStr = vertex.classes.join(' ');
+    }
+    classStr = classStr + ' swimlane-label';
+    const styles = getStylesFromArray(vertex.styles);
+
+    // Use vertex id as text in the box if no text is provided by the graph definition
+    let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
+
+    // We create a SVG label, either by delegating to addHtmlLabel or manually
+    let vertexNode;
+    log.info('vertex', vertex, vertex.labelType);
+    if (vertex.labelType === 'markdown') {
+      log.info('vertex', vertex, vertex.labelType);
+    } else {
+      if (evaluate(getConfig().flowchart.htmlLabels)) {
+        // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
+        const node = {
+          label: vertexText.replace(
+            /fa[blrs]?:fa-[\w-]+/g,
+            (s) => ``
+          ),
+        };
+        vertexNode = addHtmlLabel(elem, node).node();
+        vertexNode.parentNode.removeChild(vertexNode);
+      } else {
+        const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
+        svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
+
+        const rows = vertexText.split(common.lineBreakRegex);
+
+        for (const row of rows) {
+          const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
+          tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
+          tspan.setAttribute('dy', '1em');
+          tspan.setAttribute('x', '1');
+          tspan.textContent = row;
+          svgLabel.appendChild(tspan);
+        }
+        vertexNode = svgLabel;
+      }
+    }
+
+    let radious = 0;
+    let _shape = '';
+    // Set the shape based parameters
+    switch (vertex.type) {
+      case 'round':
+        radious = 5;
+        _shape = 'rect';
+        break;
+      case 'square':
+        _shape = 'rect';
+        break;
+      case 'diamond':
+        _shape = 'question';
+        break;
+      case 'hexagon':
+        _shape = 'hexagon';
+        break;
+      case 'odd':
+        _shape = 'rect_left_inv_arrow';
+        break;
+      case 'lean_right':
+        _shape = 'lean_right';
+        break;
+      case 'lean_left':
+        _shape = 'lean_left';
+        break;
+      case 'trapezoid':
+        _shape = 'trapezoid';
+        break;
+      case 'inv_trapezoid':
+        _shape = 'inv_trapezoid';
+        break;
+      case 'odd_right':
+        _shape = 'rect_left_inv_arrow';
+        break;
+      case 'circle':
+        _shape = 'circle';
+        break;
+      case 'ellipse':
+        _shape = 'ellipse';
+        break;
+      case 'stadium':
+        _shape = 'stadium';
+        break;
+      case 'subroutine':
+        _shape = 'subroutine';
+        break;
+      case 'cylinder':
+        _shape = 'cylinder';
+        break;
+      case 'group':
+        _shape = 'rect';
+        break;
+      case 'doublecircle':
+        _shape = 'doublecircle';
+        break;
+      default:
+        _shape = 'rect';
+    }
+    // Add the node
+    let nodeObj = {
+      labelStyle: styles.labelStyle,
+      shape: _shape,
+      labelText: vertexText,
+      labelType: vertex.labelType,
+      rx: radious,
+      ry: radious,
+      class: classStr,
+      style: styles.style,
+      id: vertex.id,
+      link: vertex.link,
+      linkTarget: vertex.linkTarget,
+      // tooltip: diagObj.db.getTooltip(vertex.id) || '',
+      // domId: diagObj.db.lookUpDomId(vertex.id),
+      haveCallback: vertex.haveCallback,
+      width: vertex.type === 'group' ? 500 : undefined,
+      dir: vertex.dir,
+      type: vertex.type,
+      props: vertex.props,
+      padding: getConfig().flowchart.padding,
+      x: nodeFromLayout.x,
+      y: nodeFromLayout.y,
+    };
+
+    let boundingBox;
+    let nodeEl;
+
+    // Add the element to the DOM
+
+    nodeEl = await insertNode(nodesElements, nodeObj, vertex.dir);
+    boundingBox = nodeEl.node().getBBox();
+    nodeEl.attr('transform', `translate(${nodeObj.x}, ${nodeObj.y / 2})`);
+  }
+
+  return elem;
+}
+
+/**
+ * Returns the all the styles from classDef statements in the graph definition.
+ *
+ * @param text
+ * @param diagObj
+ * @returns {object} ClassDef styles
+ */
+export const getClasses = function (text, diagObj) {
+  log.info('Extracting classes');
+  diagObj.db.clear();
+  try {
+    // Parse the graph definition
+    diagObj.parse(text);
+    return diagObj.db.getClasses();
+  } catch (e) {
+    return;
+  }
+};
+
+/**
+ * Draws a flowchart in the tag with id: id based on the graph definition in text.
+ *
+ * @param text
+ * @param id
+ */
+
+export const draw = async function (text, id, _version, diagObj) {
+  log.info('Drawing flowchart');
+  diagObj.db.clear();
+  flowDb.setGen('gen-2');
+  // Parse the graph definition
+  diagObj.parser.parse(text);
+
+  const { securityLevel, flowchart: conf } = getConfig();
+
+  // Handle root and document for when rendering in sandbox mode
+  let sandboxElement;
+  if (securityLevel === 'sandbox') {
+    sandboxElement = select('#i' + id);
+  }
+  const root =
+    securityLevel === 'sandbox'
+      ? select(sandboxElement.nodes()[0].contentDocument.body)
+      : select('body');
+  const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
+
+  // create g as a graphlib graph using setupGraph from setup-graph.js
+  const g = setupGraph(diagObj, id, root, doc);
+
+  let subG;
+  const subGraphs = diagObj.db.getSubGraphs();
+  log.info('Subgraphs - ', subGraphs);
+  for (let i = subGraphs.length - 1; i >= 0; i--) {
+    subG = subGraphs[i];
+    log.info('Subgraph - ', subG);
+    diagObj.db.addVertex(
+      subG.id,
+      { text: subG.title, type: subG.labelType },
+      'group',
+      undefined,
+      subG.classes,
+      subG.dir
+    );
+  }
+
+  // Fetch the vertices/nodes and edges/links from the parsed graph definition
+  const vert = diagObj.db.getVertices();
+
+  const edges = diagObj.db.getEdges();
+
+  log.info('Edges', edges);
+
+  const svg = root.select('#' + id);
+
+  svg.append('g');
+
+  // Run the renderer. This is what draws the final graph.
+  // const element = root.select('#' + id + ' g');
+  console.log('diagObj', diagObj);
+  console.log('subGraphs', diagObj.db.getSubGraphs());
+  const layout = swimlaneLayout(g, diagObj);
+  console.log('custom layout', layout);
+
+  // draw lanes as vertical lines
+  const lanesElements = svg.insert('g').attr('class', 'lanes');
+
+  let laneCount = 0;
+
+  for (const lane of layout.lanes) {
+    laneCount++;
+
+    //draw lane header as rectangle with lane title centered in it
+    const laneHeader = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+
+    // Set attributes for the rectangle
+    laneHeader.setAttribute('x', lane.x); // x-coordinate of the top-left corner
+    laneHeader.setAttribute('y', -50); // y-coordinate of the top-left corner
+    laneHeader.setAttribute('width', lane.width); // width of the rectangle
+    laneHeader.setAttribute('height', '50'); // height of the rectangle
+    if (laneCount % 2 == 0) {
+      //set light blue color for even lanes
+      laneHeader.setAttribute('fill', 'blue'); // fill color of the rectangle
+    } else {
+      //set white color odd lanes
+      laneHeader.setAttribute('fill', 'grey'); // fill color of the rectangle
+    }
+
+    laneHeader.setAttribute('stroke', 'black'); // color of the stroke/border
+    laneHeader.setAttribute('stroke-width', '2'); // width of the stroke/border
+
+    // Append the rectangle to the SVG element
+    lanesElements.node().appendChild(laneHeader);
+
+    //draw lane title
+    const laneTitle = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+
+    // Set attributes for the rectangle
+    laneTitle.setAttribute('x', lane.x + lane.width / 2); // x-coordinate of the top-left corner
+    laneTitle.setAttribute('y', -50 + 50 / 2); // y-coordinate of the top-left corner
+    laneTitle.setAttribute('width', lane.width); // width of the rectangle
+    laneTitle.setAttribute('height', '50'); // height of the rectangle
+    laneTitle.setAttribute('fill', 'white'); // fill color of the rectangle
+    laneTitle.setAttribute('stroke-width', '1'); // width of the stroke/border
+    laneTitle.setAttribute('text-anchor', 'middle'); // width of the stroke/border
+    laneTitle.setAttribute('alignment-baseline', 'middle'); // width of the stroke/border
+    laneTitle.setAttribute('font-size', '20'); // width of the stroke/border
+    laneTitle.textContent = lane.title;
+
+    // Append the rectangle to the SVG element
+    lanesElements.node().appendChild(laneTitle);
+
+    //draw lane
+
+    // Create a  element
+    const rectangle = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+
+    // Set attributes for the rectangle
+    rectangle.setAttribute('x', lane.x); // x-coordinate of the top-left corner
+    rectangle.setAttribute('y', 0); // y-coordinate of the top-left corner
+    rectangle.setAttribute('width', lane.width); // width of the rectangle
+    rectangle.setAttribute('height', '500'); // height of the rectangle
+
+    if (laneCount % 2 == 0) {
+      //set light blue color for even lanes
+      rectangle.setAttribute('fill', 'lightblue'); // fill color of the rectangle
+    } else {
+      //set white color odd lanes
+      rectangle.setAttribute('fill', '#ffffff'); // fill color of the rectangle
+    }
+
+    rectangle.setAttribute('stroke', 'black'); // color of the stroke/border
+    rectangle.setAttribute('stroke-width', '2'); // width of the stroke/border
+
+    // Append the rectangle to the SVG element
+    lanesElements.node().appendChild(rectangle);
+  }
+
+  // append lanesElements to elem
+  svg.node().appendChild(lanesElements.node());
+
+  // add lane headers
+  const laneHeaders = svg.insert('g').attr('class', 'laneHeaders');
+
+  addEdges(edges, g, diagObj);
+
+  g.edges().forEach(function (e) {
+    const edge = g.edge(e);
+    log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
+    const edgePaths = svg.insert('g').attr('class', 'edgePaths');
+    //create edge points based on start and end node
+
+    //get start node x, y coordinates
+    const sourceNode = layout.graph.node(e.v);
+    //get end node x, y coordinates
+    sourceNode.x = sourceNode.x;
+    sourceNode.y = sourceNode.y;
+
+    const targetNode = layout.graph.node(e.w);
+    targetNode.x = targetNode.x;
+    targetNode.y = targetNode.y;
+
+    edge.points = [];
+    edge.points.push({ x: sourceNode.x, y: sourceNode.y / 2 });
+    edge.points.push({ x: targetNode.x, y: targetNode.y / 2 });
+
+    const paths = insertEdge(edgePaths, e, edge, clusterDb, 'flowchart', g);
+    //positionEdgeLabel(edge, paths);
+  });
+  await swimlaneRender(layout, vert, svg, g, id, conf);
+
+  // utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
+
+  setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
+};
+
+export default {
+  setConf,
+  addVertices,
+  addEdges,
+  getClasses,
+  draw,
+};