diff --git a/cSpell.json b/cSpell.json index b692c1ebf..c7630d7ee 100644 --- a/cSpell.json +++ b/cSpell.json @@ -39,6 +39,7 @@ "gitgraph", "globby", "graphlib", + "graphviz", "grav", "greywolf", "inkdrop", @@ -66,6 +67,7 @@ "plantuml", "playfair", "podlite", + "radious", "ranksep", "redmine", "sandboxed", diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index d55a23bc9..5522e9333 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -56,11 +56,77 @@
Security check
+cyto TD
+  %% I could not figure out how to use double quotes in labels in Mermaid
+  subgraph ibm[IBM Espresso CPU]
+    core0[IBM PowerPC Broadway Core 0]
+    core1[IBM PowerPC Broadway Core 1]
+    core2[IBM PowerPC Broadway Core 2]
+
+    rom[16 KB ROM]
+
+    core0 --- core2
+
+    rom --> core2
+  end
+
+  subgraph amd[AMD Latte GPU]
+    mem[Memory & I/O Bridge]
+    dram[DRAM Controller]
+    edram[32 MB EDRAM MEM1]
+    rom[512 B SEEPROM]
+
+    sata[SATA IF]
+    exi[EXI]
+
+    subgraph gx[GX]
+      sram[3 MB 1T-SRAM]
+    end
+
+    radeon[AMD Radeon R7xx GX2]
+
+    mem --- gx
+    mem --- radeon
+
+    rom --- mem
+
+    mem --- sata
+    mem --- exi
+
+    dram --- sata
+    dram --- exi
+  end
+
+  ddr3[2 GB DDR3 RAM MEM2]
+
+  mem --- ddr3
+  dram --- ddr3
+  edram --- ddr3
+
+  core1 --- mem
+
+  exi --- rtc
+
+
 cyto LR
-    inside1 --> inside2 & inside3 & inside4 & inside5 & inside6
-    a(letter a
a) ---> b(letter b)--> c(letter c) --> d -->e(letter e
e) --> a - b <--> d(letter b
d) -
+subgraph TOP + direction LR + subgraph B1 + direction RL + i1 -->f1 + end + subgraph B2 + direction BT + i2 -->f2 + end + end + B1 --> B2 + F --> f1 + + inside1 --> inside2 & inside3 & inside4 & inside5 & inside6 a(letter a
a) ---> b(letter + b)--> c(letter c) --> d -->e(letter e
e) --> a b <--> d(letter b
d)
       mindmap
   root
diff --git a/packages/mermaid-flowchart-v3/src/flowRenderer-v3.js b/packages/mermaid-flowchart-v3/src/flowRenderer-v3.js
index 9b0f3092b..b3a56f73f 100644
--- a/packages/mermaid-flowchart-v3/src/flowRenderer-v3.js
+++ b/packages/mermaid-flowchart-v3/src/flowRenderer-v3.js
@@ -37,7 +37,7 @@ const nodeDb = {};
 //  * @param doc
 //  * @param diagObj
 //  */
-export const addVertices = function (vert, svgId, root, doc, diagObj, parentLookUpDb, graph) {
+export const addVertices = function (vert, svgId, root, doc, diagObj, parentLookupDb, graph) {
   const svg = root.select(`[id="${svgId}"]`);
   const nodes = svg.insert('g').attr('class', 'nodes');
   const keys = Object.keys(vert);
@@ -170,55 +170,60 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
       props: vertex.props,
       padding: getConfig().flowchart.padding,
     };
-    const nodeEl = insertNode(nodes, node, vertex.dir);
-    const boundingBox = nodeEl.node().getBBox();
+    let boundingBox;
+    let nodeEl;
+    if (node.type !== 'group') {
+      nodeEl = insertNode(nodes, node, vertex.dir);
+      boundingBox = nodeEl.node().getBBox();
+    }
     const data = {
       id: vertex.id,
-      labelStyle: styles.labelStyle,
-      shape: _shape,
-      labelText: vertexText,
-      rx: radious,
-      ry: radious,
-      class: classStr,
-      style: styles.style,
-      link: vertex.link,
-      linkTarget: vertex.linkTarget,
-      tooltip: diagObj.db.getTooltip(vertex.id) || '',
+      // labelStyle: styles.labelStyle,
+      // shape: _shape,
+      // labelText: vertexText,
+      labels: [{ text: vertexText }, { text: vertexText }],
+      // rx: radious,
+      // ry: radious,
+      // class: classStr,
+      // style: styles.style,
+      // link: vertex.link,
+      // linkTarget: vertex.linkTarget,
+      // tooltip: diagObj.db.getTooltip(vertex.id) || '',
       domId: diagObj.db.lookUpDomId(vertex.id),
-      haveCallback: vertex.haveCallback,
-      width: boundingBox.width,
-      height: boundingBox.height,
-      dir: vertex.dir,
+      // haveCallback: vertex.haveCallback,
+      width: boundingBox?.width,
+      height: boundingBox?.height,
+      // dir: vertex.dir,
       type: vertex.type,
-      props: vertex.props,
-      padding: getConfig().flowchart.padding,
-      boundingBox,
+      // props: vertex.props,
+      // padding: getConfig().flowchart.padding,
+      // boundingBox,
       el: nodeEl,
-      parent: parentLookUpDb.parentById[vertex.id],
+      parent: parentLookupDb.parentById[vertex.id],
     };
-    // if (!Object.keys(parentLookUpDb.childrenById).includes(vertex.id)) {
-    graph.children.push({
-      ...data,
-    });
+    // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) {
+    // graph.children.push({
+    //   ...data,
+    // });
     // }
     nodeDb[node.id] = data;
-    log.trace('setNode', {
-      labelStyle: styles.labelStyle,
-      shape: _shape,
-      labelText: vertexText,
-      rx: radious,
-      ry: radious,
-      class: classStr,
-      style: styles.style,
-      id: vertex.id,
-      domId: diagObj.db.lookUpDomId(vertex.id),
-      width: vertex.type === 'group' ? 500 : undefined,
-      type: vertex.type,
-      dir: vertex.dir,
-      props: vertex.props,
-      padding: getConfig().flowchart.padding,
-      parent: parentLookUpDb.parentById[vertex.id],
-    });
+    // log.trace('setNode', {
+    //   labelStyle: styles.labelStyle,
+    //   shape: _shape,
+    //   labelText: vertexText,
+    //   rx: radious,
+    //   ry: radious,
+    //   class: classStr,
+    //   style: styles.style,
+    //   id: vertex.id,
+    //   domId: diagObj.db.lookUpDomId(vertex.id),
+    //   width: vertex.type === 'group' ? 500 : undefined,
+    //   type: vertex.type,
+    //   dir: vertex.dir,
+    //   props: vertex.props,
+    //   padding: getConfig().flowchart.padding,
+    //   parent: parentLookupDb.parentById[vertex.id],
+    // });
   });
   return graph;
 };
@@ -368,7 +373,7 @@ export const addEdges = function (edges, diagObj, graph) {
       sources: [edge.start],
       targets: [edge.end],
       edgeData,
-      targetPort: 'PortSide.NORTH',
+      // targetPort: 'PortSide.NORTH',
       // id: cnt,
     });
   });
@@ -470,40 +475,69 @@ export const getClasses = function (text, diagObj) {
 };
 
 const addSubGraphs = function (db) {
-  const parentLookUpDb = { parentById: {}, childrenById: {} };
+  const parentLookupDb = { parentById: {}, childrenById: {} };
   const subgraphs = db.getSubGraphs();
   log.info('Subgraphs - ', subgraphs);
   subgraphs.forEach(function (subgraph) {
     subgraph.nodes.forEach(function (node) {
-      parentLookUpDb.parentById[node] = subgraph.id;
-      if (parentLookUpDb.childrenById[subgraph.id] === undefined) {
-        parentLookUpDb.childrenById[subgraph.id] = [];
+      parentLookupDb.parentById[node] = subgraph.id;
+      if (parentLookupDb.childrenById[subgraph.id] === undefined) {
+        parentLookupDb.childrenById[subgraph.id] = [];
       }
-      parentLookUpDb.childrenById[subgraph.id].push(node);
+      parentLookupDb.childrenById[subgraph.id].push(node);
     });
   });
 
   subgraphs.forEach(function (subgraph) {
     const data = { id: subgraph.id };
-    if (parentLookUpDb.parentById[subgraph.id] !== undefined) {
-      data.parent = parentLookUpDb.parentById[subgraph.id];
+    if (parentLookupDb.parentById[subgraph.id] !== undefined) {
+      data.parent = parentLookupDb.parentById[subgraph.id];
     }
     // cy.add({
     //   group: 'nodes',
     //   data,
     // });
   });
-  return parentLookUpDb;
+  return parentLookupDb;
+};
+
+/* Reverse engineered with trial and error */
+const calcOffset = function (src, dest, sourceId, targetId) {
+  if (src === dest) {
+    return src;
+  }
+  return 0;
 };
 
 const insertEdge = function (edgesEl, edge, edgeData, diagObj) {
+  const srcOffset = nodeDb[edge.sources[0]].offset;
+  const targetOffset = nodeDb[edge.targets[0]].offset;
+  const offset = {
+    x: calcOffset(
+      srcOffset.x,
+      targetOffset.x,
+      nodeDb[edge.sources[0]].id,
+      nodeDb[edge.targets[0]].id
+    ),
+    y: calcOffset(
+      srcOffset.y,
+      targetOffset.y,
+      nodeDb[edge.sources[0]].id,
+      nodeDb[edge.targets[0]].id
+    ),
+  };
+  // console.log('srcOffset', srcOffset.x, targetOffset.x, srcOffset.y, targetOffset.y);
   const src = edge.sections[0].startPoint;
   const dest = edge.sections[0].endPoint;
   const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];
   // const dest = edge.target().position();
   // const dest = edge.targetEndpoint();
-  const segPoints = segments.map((segment) => [segment.x, segment.y]);
-  const points = [[src.x, src.y], ...segPoints, [dest.x, dest.y]];
+  const segPoints = segments.map((segment) => [segment.x + offset.x, segment.y + offset.y]);
+  const points = [
+    [src.x + offset.x, src.y + offset.y],
+    ...segPoints,
+    [dest.x + offset.x, dest.y + offset.y],
+  ];
   // console.log('Edge ctrl points:', edge.segmentPoints(), 'Bounds:', bounds, edge.source(), points);
   // console.log('Edge ctrl points:', points);
   // const curve = line().curve(curveCardinal);
@@ -538,6 +572,28 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj) {
   //   .attr('cy', dest.y);
 };
 
+/**
+ *
+ * @param {*} graph
+ * @param nodeArray
+ * @param parentLookupDb
+ */
+const insertChildren = (nodeArray, parentLookupDb) => {
+  nodeArray.forEach((node) => {
+    if (!node.children) {
+      node.children = [];
+    }
+    const childIds = parentLookupDb.childrenById[node.id];
+    // console.log('UGH', node.id, childIds);
+    if (childIds) {
+      childIds.forEach((childId) => {
+        node.children.push(nodeDb[childId]);
+      });
+    }
+    insertChildren(node.children, parentLookupDb);
+  });
+};
+
 /**
  * Draws a flowchart in the tag with id: id based on the graph definition in text.
  *
@@ -558,16 +614,18 @@ export const draw = function (text, id, _version, diagObj) {
     let graph = {
       id: 'root',
       layoutOptions: {
-        'elk.algorithm': 'layered',
-        'elk.direction': 'DOWN',
-        'elk.port.side': 'SOUTH',
+        'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
+        //   'elk.algorithm': 'layered',
+        //   'elk.direction': 'DOWN',
+        //   'elk.port.side': 'SOUTH',
         // 'nodePlacement.strategy': 'SIMPLE',
-        'org.eclipse.elk.graphviz.concentrate': true,
+        // 'org.eclipse.elk.spacing.labelLabel': 120,
+        // 'org.eclipse.elk.graphviz.concentrate': true,
         // 'org.eclipse.elk.spacing.nodeNode': 120,
         // 'org.eclipse.elk.spacing.edgeEdge': 120,
         // 'org.eclipse.elk.spacing.edgeNode': 120,
         // 'org.eclipse.elk.spacing.nodeEdge': 120,
-        'org.eclipse.elk.spacing.componentComponent': 120,
+        // 'org.eclipse.elk.spacing.componentComponent': 120,
       },
       children: [],
       edges: [],
@@ -606,29 +664,59 @@ export const draw = function (text, id, _version, diagObj) {
       log.info('Subgraph - ', subG);
       diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
     }
+    const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
 
-    const parentLookUpDb = addSubGraphs(diagObj.db);
-    graph = addVertices(vert, id, root, doc, diagObj, parentLookUpDb, graph);
+    const parentLookupDb = addSubGraphs(diagObj.db);
+    graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
     const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
     const edges = diagObj.db.getEdges();
     graph = addEdges(edges, diagObj, graph);
 
+    // Iterate through all nodes and add the top level nodes to the graph
+    const nodes = Object.keys(nodeDb);
+    nodes.forEach((nodeId) => {
+      const node = nodeDb[nodeId];
+      if (!node.parent) {
+        graph.children.push(node);
+      }
+      if (parentLookupDb.childrenById[nodeId] !== undefined) {
+        // console.log('UGH node', node);
+        delete node.x;
+        delete node.y;
+        delete node.width;
+        delete node.height;
+      }
+    });
+    insertChildren(graph.children, parentLookupDb);
+    // console.log('Graph (UGH)- ', JSON.parse(JSON.stringify(graph)), JSON.stringify(graph));
+    // const graph2 = {
+    //   id: 'root',
+    //   layoutOptions: { 'elk.algorithm': 'layered' },
+    //   children: [
+    //     {
+    //       id: 'N1',
+    //       width: 30,
+    //       height: 30,
+    //       padding: 12,
+    //       children: [
+    //         { id: 'n1', width: 30, height: 30 },
+    //         { id: 'n2', width: 30, height: 30 },
+    //         { id: 'n3', width: 30, height: 30 },
+    //       ],
+    //     },
+    //   ],
+    //   edges: [
+    //     { id: 'e1', sources: ['n1'], targets: ['n2'] },
+    //     { id: 'e2', sources: ['n1'], targets: ['n3'] },
+    //   ],
+    // };
     elk.layout(graph).then(function (g) {
-      g.children.forEach(function (node) {
-        const data = nodeDb[node.id];
-        if (data) {
-          data.el.attr(
-            'transform',
-            `translate(${node.x + node.width / 2}, ${node.y + node.height / 2})`
-          );
-          // document
-          //   .querySelector(`[id="${data.domId}"]`)
-          //   .setAttribute('transform', `translate(${node.position().x}, ${node.position().y})`);
-          log.info('Id = ', data.domId, svg.select(`[id="${data.domId}"]`), data.el.node());
-        }
-      });
+      // elk.layout(graph2).then(function (g) {
+      // console.log('Layout (UGH)- ', g);
+      drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj);
 
       g.edges.map((edge, id) => {
+        // console.log('Edge (UGH)- ', edge);
         insertEdge(edgesEl, edge, edge.edgeData, diagObj);
       });
       setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
@@ -639,6 +727,41 @@ export const draw = function (text, id, _version, diagObj) {
   });
 };
 
+const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj) => {
+  nodeArray.forEach(function (node) {
+    if (node) {
+      if (node.type === 'group') {
+        subgraphsEl
+          .insert('rect')
+          .attr('class', 'subgraph node')
+          .attr('style', 'fill:#ccc;stroke:black;stroke-width:1')
+          .attr('x', node.x + relX)
+          .attr('y', node.y + relY)
+          .attr('width', node.width)
+          .attr('height', node.height);
+        log.info('Id (UGH)= ', node.type, node.domId, svg.select(`[id="${node.domId}"]`));
+      } else {
+        log.info('Id (UGH)= ', node.id);
+        node.el.attr(
+          'transform',
+          `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
+        );
+      }
+      nodeDb[node.id].offset = {
+        posX: node.x + relX,
+        posY: node.y + relY,
+        x: relX,
+        y: relY,
+      };
+    }
+  });
+  nodeArray.forEach(function (node) {
+    if (node && node.type === 'group') {
+      drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj);
+    }
+  });
+};
+
 export default {
   // setConf,
   // addVertices,