diff --git a/cypress/platform/current.html b/cypress/platform/current.html index abc63ea48..50cb3ed3c 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -70,7 +70,7 @@ Moving --> Crash Crash --> [*] -
+
stateDiagram-v2 [*] --> First First --> Second @@ -84,6 +84,12 @@ stateDiagram-v2 [*] --> sec sec --> [*] } +
+
+flowchart TD + subgraph Apa + a --> b + end
stateDiagram-v2 diff --git a/src/dagre-wrapper/clusters.js b/src/dagre-wrapper/clusters.js index 55db0b32e..02da85c58 100644 --- a/src/dagre-wrapper/clusters.js +++ b/src/dagre-wrapper/clusters.js @@ -1,8 +1,10 @@ import intersectRect from './intersect/intersect-rect'; -import { logger } from '../logger'; // eslint-disable-line +import { logger as log } from '../logger'; // eslint-disable-line import createLabel from './createLabel'; const rect = (parent, node) => { + log.info('Creating subgraph rect for ', node.id, node); + // Add outer g element const shapeSvg = parent .insert('g') @@ -22,7 +24,10 @@ const rect = (parent, node) => { const padding = 0 * node.padding; const halfPadding = padding / 2; + const width = node.width || 50; + const height = node.height || 50; + log.info('Data ', node, JSON.stringify(node)); // center the rect around its coordinate rect .attr('rx', node.rx) @@ -32,7 +37,7 @@ const rect = (parent, node) => { .attr('width', node.width + padding) .attr('height', node.height + padding); - // logger.info('bbox', bbox.width, node.x, node.width); + // log.info('bbox', bbox.width, node.x, node.width); // Center the label // label.attr('transform', 'translate(' + adj + ', ' + (node.y - node.height / 2) + ')'); label.attr( @@ -127,7 +132,7 @@ const roundedWithTitle = (parent, node) => { .attr('width', node.width + padding) .attr('height', node.height + padding - bbox.height - 3); - // logger.info('bbox', bbox.width, node.x, node.width); + // log.info('bbox', bbox.width, node.x, node.width); // Center the label // label.attr('transform', 'translate(' + adj + ', ' + (node.y - node.height / 2) + ')'); label.attr( @@ -155,7 +160,9 @@ const shapes = { rect, roundedWithTitle, noteGroup }; let clusterElems = {}; export const insertCluster = (elem, node) => { - clusterElems[node.id] = shapes[node.shape](elem, node); + log.info('Inserting cluster'); + const shape = node.shape || 'rect'; + clusterElems[node.id] = shapes[shape](elem, node); }; export const getClusterTitleWidth = (elem, node) => { const label = createLabel(node.labelText, node.labelStyle); @@ -170,6 +177,8 @@ export const clear = () => { }; export const positionCluster = node => { + log.info('Position cluster'); const el = clusterElems[node.id]; + el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')'); }; diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js index eb0427215..f3dd6c885 100644 --- a/src/dagre-wrapper/index.js +++ b/src/dagre-wrapper/index.js @@ -1,62 +1,38 @@ import dagre from 'dagre'; +import graphlib from 'graphlib'; import insertMarkers from './markers'; -import { clear as cleargraphlib, clusterDb, adjustClustersAndEdges } from './mermaid-graphlib'; -import { insertNode, positionNode, clear as clearNodes } from './nodes'; -import { insertCluster, clear as clearClusters } from './clusters'; +import { updateNodeBounds } from './shapes/util'; +import { + clear as clearGraphlib, + clusterDb, + adjustClustersAndEdges, + findNonClusterChild +} from './mermaid-graphlib'; +import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './nodes'; +import { insertCluster, clear as clearClusters, positionCluster } from './clusters'; import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges'; -import { logger } from '../logger'; +import { logger as log } from '../logger'; // let clusterDb = {}; const getAnchorId = id => { // Only insert an achor once if (clusterDb[id]) { - // if (!clusterDb[id].inserted) { - // // Create anchor node for cluster - // const anchorData = { - // shape: 'start', - // labelText: '', - // classes: '', - // style: '', - // id: id + '_anchor', - // type: 'anchor', - // padding: 0 - // }; - // insertNode(nodes, anchorData); - - // graph.setNode(anchorData.id, anchorData); - // graph.setParent(anchorData.id, id); - // clusterDb[id].inserted = true; - // } return clusterDb[id].id; } return id; }; -const findNonClusterChild = (id, graph) => { - const node = graph.node(id); - logger.info('identified node', node); - if (node.type !== 'group') { - return node.id; +const recursiveRender = (_elem, graph, diagramtype) => { + const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line + if (!graph.nodes()) { + log.info('No nodes found for', graph); + } else { + log.info('Recursive render', graph.edges()); } - logger.info('identified node Not', node.id); - const children = graph.children(id); - for (let i = 0; i < children.length; i++) { - const _id = findNonClusterChild(children[i], graph); - if (_id) { - return _id; - } + if (graph.edges().length > 0) { + log.info('Recursive edges', graph.edge(graph.edges()[0])); } -}; - -export const render = (elem, graph, markers, diagramtype, id) => { - insertMarkers(elem, markers, diagramtype, id); - clearNodes(); - clearEdges(); - clearClusters(); - - adjustClustersAndEdges(graph); - const clusters = elem.insert('g').attr('class', 'clusters'); // eslint-disable-line const edgePaths = elem.insert('g').attr('class', 'edgePaths'); const edgeLabels = elem.insert('g').attr('class', 'edgeLabels'); @@ -66,81 +42,115 @@ export const render = (elem, graph, markers, diagramtype, id) => { // to the abstract node and is later used by dagre for the layout graph.nodes().forEach(function(v) { const node = graph.node(v); - logger.info('Node ' + v + ': ' + JSON.stringify(graph.node(v))); - if (node.type !== 'group') { - insertNode(nodes, graph.node(v)); - } else { + log.info('(Insert) Node ' + v + ': ' + JSON.stringify(graph.node(v))); + if (node.clusterNode) { // const children = graph.children(v); - // logger.info('Cluster identified', node.id, children[0], findNonClusterChild(node.id, graph)); - // clusterDb[node.id] = { id: findNonClusterChild(node.id, graph) }; + log.info('Cluster identified', v, node, graph.node(v)); + const newEl = recursiveRender(clusters, node.graph, diagramtype); + updateNodeBounds(node, newEl); + setNodeElem(newEl, node); + + log.info('Recursice render complete', newEl, node); + } else { + if (graph.children(v).length > 0) { + // This is a cluster but not to be rendered recusively + // Render as before + log.info('Cluster - the non recursive path', v, node.id, node, graph); + log.info(findNonClusterChild(node.id, graph)); + clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node }; + // insertCluster(clusters, graph.node(v)); + } else { + log.info('Node - the non recursive path', v, node.id, node); + insertNode(nodes, graph.node(v)); + } } }); - logger.info('Clusters ', clusterDb); + log.info('Clusters ', clusterDb); // Insert labels, this will insert them into the dom so that the width can be calculated // Also figure out which edges point to/from clusters and adjust them accordingly // Edges from/to clusters really points to the first child in the cluster. // TODO: pick optimal child in the cluster to us as link anchor graph.edges().forEach(function(e) { - const edge = graph.edge(e); - logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); - logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e))); + const edge = graph.edge(e.v, e.w, e.name); + log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); + log.info( + 'Edge ' + e.v + ' -> ' + e.w + ': ', + e, + ' ', + +JSON.stringify(graph.edge(e.v, e.w, e.name)) + ); let v = e.v; let w = e.w; // Check if link is either from or to a cluster - logger.info( - 'Fix', - clusterDb, - 'ids:', - e.v, - e.w, - 'Translateing: ', - clusterDb[e.v], - clusterDb[e.w] - ); + log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]); if (clusterDb[e.v] || clusterDb[e.w]) { - logger.info('Fixing and trixing - rwemoving', e.v, e.w, e.name); + log.info('Fixing and trixing - removing', e.v, e.w, e.name); v = getAnchorId(e.v, graph, nodes); w = getAnchorId(e.w, graph, nodes); graph.removeEdge(e.v, e.w, e.name); if (v !== e.v) edge.fromCluster = e.v; if (w !== e.w) edge.toCluster = e.w; - logger.info('Fixing Replacing with', v, w, e.name); + log.info('Fixing Replacing with', v, w, e.name); graph.setEdge(v, w, edge, e.name); } insertEdgeLabel(edgeLabels, edge); }); graph.edges().forEach(function(e) { - logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); + log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); }); - logger.info('#############################################'); - logger.info('### Layout ###'); - logger.info('#############################################'); - logger.info(graph); + log.info('#############################################'); + log.info('### Layout ###'); + log.info('#############################################'); + log.info(graph); dagre.layout(graph); // Move the nodes to the correct place graph.nodes().forEach(function(v) { const node = graph.node(v); - logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v))); - if (node.type !== 'group') { - positionNode(node); + log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v))); + if (node && node.clusterNode) { + // clusterDb[node.id].node = node; + // positionNode(node); } else { - insertCluster(clusters, node); - clusterDb[node.id].node = node; + // Non cluster node + if (graph.children(v).length > 0) { + // A cluster in the non-recurive way + // positionCluster(node); + insertCluster(clusters, node); + clusterDb[node.id].node = node; + } else { + positionNode(node); + } } }); // Move the edge labels to the correct place after layout graph.edges().forEach(function(e) { const edge = graph.edge(e); - logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); + log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); insertEdge(edgePaths, edge, clusterDb, diagramtype); positionEdgeLabel(edge); }); + + return elem; +}; + +export const render = (elem, graph, markers, diagramtype, id) => { + insertMarkers(elem, markers, diagramtype, id); + clearNodes(); + clearEdges(); + clearClusters(); + clearGraphlib(); + + log.warn('Graph before:', graphlib.json.write(graph)); + adjustClustersAndEdges(graph); + log.warn('Graph after:', graphlib.json.write(graph)); + + recursiveRender(elem, graph, diagramtype); }; // const shapeDefinitions = {}; diff --git a/src/dagre-wrapper/mermaid-graphlib.js b/src/dagre-wrapper/mermaid-graphlib.js index ae4e4086c..a232c09db 100644 --- a/src/dagre-wrapper/mermaid-graphlib.js +++ b/src/dagre-wrapper/mermaid-graphlib.js @@ -13,7 +13,7 @@ export const clear = () => { }; const copy = (clusterId, graph, newGraph, rootId) => { - logger.trace('Copying ', clusterId); + logger.info('Copying ', clusterId, graph.node(clusterId)); const nodes = graph.children(clusterId); nodes.forEach(node => { if (graph.children(node).length > 0) { @@ -21,22 +21,29 @@ const copy = (clusterId, graph, newGraph, rootId) => { } const data = graph.node(node); - logger.trace(node, data, ' parent is ', clusterId); + logger.info(node, data, ' parent is ', clusterId); newGraph.setNode(node, data); newGraph.setParent(node, clusterId); const edges = graph.edges(node); - graph.removeNode(node); - logger.trace('Edges', edges); + logger.info('Copying Edges', edges); edges.forEach(edge => { - const data = graph.edge(edge); - // Do not copy edges in and out of the root cluster, they belong to the parent graph - if (!(edge.v === rootId || edge.w === rootId)) { - logger.trace('Copying as ', rootId, edge.v, edge.w, clusterId); - newGraph.setEdge(edge.v, edge.w, data); - } else { - logger.trace('Skipping copy of edge as ', rootId, edge.v, edge.w, clusterId); + logger.info('Edge', edge); + const data = graph.edge(edge.v, edge.w, edge.name); + logger.info('Edge data', data, rootId); + try { + // Do not copy edges in and out of the root cluster, they belong to the parent graph + if (!(edge.v === rootId || edge.w === rootId)) { + logger.info('Copying as ', edge.v, edge.w, data, edge.name); + newGraph.setEdge(edge.v, edge.w, data, edge.name); + logger.info('newgrapg edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0])); + } else { + logger.info('Skipping copy of edge as ', rootId, edge.v, edge.w, clusterId); + } + } catch (e) { + logger.error(e); } }); + graph.removeNode(node); }); newGraph.setNode(clusterId, graph.node(clusterId)); }; @@ -60,8 +67,8 @@ export const extractGraphFromCluster = (clusterId, graph) => { .setGraph({ rankdir: 'TB', // Todo: set proper spacing - nodesep: 10, - ranksep: 10, + nodesep: 50, + ranksep: 50, marginx: 8, marginy: 8 }) @@ -69,6 +76,26 @@ export const extractGraphFromCluster = (clusterId, graph) => { return {}; }); + // const conf = getConfig().flowchart; + // const nodeSpacing = conf.nodeSpacing || 50; + // const rankSpacing = conf.rankSpacing || 50; + + // // Create the input mermaid.graph + // const g = new graphlib.Graph({ + // multigraph: true, + // compound: true + // }) + // .setGraph({ + // rankdir: 'TB', + // nodesep: nodeSpacing, + // ranksep: rankSpacing, + // marginx: 8, + // marginy: 8 + // }) + // .setDefaultEdgeLabel(function() { + // return {}; + // }); + copy(clusterId, graph, clusterGraph, clusterId); return clusterGraph; @@ -100,7 +127,7 @@ export const validate = graph => { * @param {Finds a } id * @param {*} graph */ -const findNonClusterChild = (id, graph) => { +export const findNonClusterChild = (id, graph) => { // const node = graph.node(id); logger.trace('Searching', id); const children = graph.children(id); @@ -141,7 +168,7 @@ export const adjustClustersAndEdges = graph => { graph.nodes().forEach(function(id) { const children = graph.children(id); if (children.length > 0) { - logger.trace( + logger.info( 'Cluster identified', id, ' Replacement id in edges: ', @@ -157,13 +184,13 @@ export const adjustClustersAndEdges = graph => { const children = graph.children(id); const edges = graph.edges(); if (children.length > 0) { - logger.trace('Cluster identified', id); + logger.info('Cluster identified', id); edges.forEach(edge => { - logger.trace('Edge: ', edge, decendants[id]); + logger.info('Edge: ', edge, decendants[id]); // Check if any edge leaves the cluster (not the actual cluster, thats a link from the box) if (edge.v !== id && edge.w !== id) { if (decendants[id].indexOf(edge.v) < 0 || decendants[id].indexOf(edge.w) < 0) { - logger.trace('Edge: ', edge, ' leaves cluster ', id); + logger.info('Edge: ', edge, ' leaves cluster ', id); clusterDb[id].externalConnections = true; } } @@ -189,7 +216,13 @@ export const adjustClustersAndEdges = graph => { // Create a new node in the original graph, this new node is not a cluster // but a regular node with the cluster dontent as a new attached graph - graph.setNode(clusterId, { clusterNode: true, graph: clusterGraph }); + graph.setNode(clusterId, { + clusterNode: true, + id: clusterId, + clusterData: clusterDb[clusterId], + labelText: clusterDb[clusterId].labelText, + graph: clusterGraph + }); // The original edges in and out of the cluster is applied edges.forEach(edge => { @@ -205,7 +238,7 @@ export const adjustClustersAndEdges = graph => { graph.edges().forEach(function(e) { const edge = graph.edge(e); logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); - logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e))); + logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e))); let v = e.v; let w = e.w; diff --git a/src/dagre-wrapper/mermaid-graphlib.spec.js b/src/dagre-wrapper/mermaid-graphlib.spec.js index b842a455b..cd0c29b34 100644 --- a/src/dagre-wrapper/mermaid-graphlib.spec.js +++ b/src/dagre-wrapper/mermaid-graphlib.spec.js @@ -135,7 +135,7 @@ describe('Graphlib decorations', () => { expect(newGraph.edges('a')).toEqual([{ v: 'a', w: 'b' }]); }); - it('It is possible to extract a clusters to a new graph 2', function () { + it('It is possible to extract a clusters to a new graph 2 GLB1', function () { /* subgraph C1 a --> b @@ -163,7 +163,7 @@ describe('Graphlib decorations', () => { expect(g.children('C2')).toEqual([]); expect(g.edges()).toEqual([{ v: 'C1', w: 'C2' }]); - logger.info(C1.nodes()); + logger.info(g.nodes()); expect(C1.nodes()).toEqual(['a', 'C1', 'b']); expect(C1.children('C1')).toEqual(['a', 'b']); expect(C1.edges()).toEqual([{ v: 'a', w: 'b' }]); @@ -200,7 +200,7 @@ describe('Graphlib decorations', () => { expect(g.children('C1')).toEqual([]); }); }); - it('Validate should detect edges between clusters and transform clusters', function () { + it('Validate should detect edges between clusters and transform clusters GLB4', function () { /* a --> b subgraph C1 @@ -225,4 +225,29 @@ describe('Graphlib decorations', () => { expect(g.nodes().length).toBe(2); expect(validate(g)).toBe(true); }); + it('Validate should detect edges between clusters and transform clusters GLB5', function () { + /* + a --> b + subgraph C1 + a + end + subgraph C2 + b + end + C1 --> + */ + g.setNode('a', { data: 1 }); + g.setNode('b', { data: 2 }); + g.setParent('a', 'C1'); + g.setParent('b', 'C2'); + g.setParent('C1', 'C2'); + // g.setEdge('a', 'b', { name: 'C1-internal-link' }); + g.setEdge('C1', 'C2', { name: 'C1-external-link' }); + + logger.info(g.nodes()) + adjustClustersAndEdges(g); + logger.info(g.nodes()) + expect(g.nodes().length).toBe(2); + expect(validate(g)).toBe(true); + }); }); diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index f1db09a43..47be56991 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -387,6 +387,9 @@ let nodeElems = {}; export const insertNode = (elem, node) => { nodeElems[node.id] = shapes[node.shape](elem, node); }; +export const setNodeElem = (elem, node) => { + nodeElems[node.id] = elem; +}; export const clear = () => { nodeElems = {}; }; diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js index e0cce8df8..f66bf947c 100644 --- a/src/diagrams/flowchart/flowRenderer-v2.js +++ b/src/diagrams/flowchart/flowRenderer-v2.js @@ -314,8 +314,10 @@ export const draw = function(text, id) { let subG; const subGraphs = flowDb.getSubGraphs(); + logger.info('Subgraphs - ', subGraphs); for (let i = subGraphs.length - 1; i >= 0; i--) { subG = subGraphs[i]; + logger.info('Subgraph - ', subG); flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes); } @@ -347,7 +349,7 @@ export const draw = function(text, id) { // Run the renderer. This is what draws the final graph. const element = d3.select('#' + id + ' g'); render(element, g, ['point', 'circle', 'cross'], 'flowchart', id); - dagre.layout(g); + // dagre.layout(g); element.selectAll('g.node').attr('title', function() { return flowDb.getTooltip(this.id); @@ -378,27 +380,27 @@ export const draw = function(text, id) { // Index nodes flowDb.indexNodes('subGraph' + i); - // reposition labels - for (i = 0; i < subGraphs.length; i++) { - subG = subGraphs[i]; + // // reposition labels + // for (i = 0; i < subGraphs.length; i++) { + // subG = subGraphs[i]; - if (subG.title !== 'undefined') { - const clusterRects = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"] rect'); - const clusterEl = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"]'); + // if (subG.title !== 'undefined') { + // const clusterRects = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"] rect'); + // const clusterEl = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"]'); - const xPos = clusterRects[0].x.baseVal.value; - const yPos = clusterRects[0].y.baseVal.value; - const width = clusterRects[0].width.baseVal.value; - const cluster = d3.select(clusterEl[0]); - const te = cluster.select('.label'); - te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`); - te.attr('id', id + 'Text'); + // const xPos = clusterRects[0].x.baseVal.value; + // const yPos = clusterRects[0].y.baseVal.value; + // const width = clusterRects[0].width.baseVal.value; + // const cluster = d3.select(clusterEl[0]); + // const te = cluster.select('.label'); + // te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`); + // te.attr('id', id + 'Text'); - for (let j = 0; j < subG.classes.length; j++) { - clusterEl[0].classList.add(subG.classes[j]); - } - } - } + // for (let j = 0; j < subG.classes.length; j++) { + // clusterEl[0].classList.add(subG.classes[j]); + // } + // } + // } // Add label rects for non html labels if (!conf.htmlLabels) {