diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index 7374dff53..34af8da15 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -125,4 +125,32 @@ describe('Flowchart v2', () => {
expect(svg).to.not.have.attr('style');
});
});
+ it('50: handle nested subgraphs in reverse order', () => {
+ imgSnapshotTest(
+ `flowchart LR
+ a -->b
+ subgraph A
+ B
+ end
+ subgraph B
+ b
+ end
+ `,
+ {htmlLabels: true, flowchart: {htmlLabels: true}, securityLevel: 'loose'}
+ );
+ });
+ it('51: handle nested subgraphs in reverse order', () => {
+ imgSnapshotTest(
+ `flowchart LR
+ a -->b
+ subgraph A
+ B
+ end
+ subgraph B
+ b
+ end
+ `,
+ {htmlLabels: true, flowchart: {htmlLabels: true}, securityLevel: 'loose'}
+ );
+ });
});
diff --git a/cypress/platform/current.html b/cypress/platform/current.html
index 6297d39b6..348c2e986 100644
--- a/cypress/platform/current.html
+++ b/cypress/platform/current.html
@@ -75,19 +75,48 @@ stateDiagram-v2
+%% this does not produce the desired result
+flowchart TB
+ subgraph container_Beta
+ process_C-->Process_D
+ end
+ subgraph container_Alpha
+ process_A-->process_B
+ process_B-->|via_AWSBatch|container_Beta
+ process_A-->|messages|process_C
+ end
+
+
+
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%%
-classDiagram
- Animal <|-- Duck
+flowchart TB
+ b-->B
+ a-->c
+ subgraph O
+ A
+ end
+ subgraph B
+ c
+ end
+ subgraph A
+ a
+ b
+ B
+ end
-graph TD
- A(Start) --> B[/Another/]
- A[/Another/] --> C[End]
- subgraph section
- B
- C
- end
+flowchart TB
+ subgraph O
+ A
+ end
+ subgraph A
+ b-->B
+ a-->c
+ end
+ subgraph B
+ c
+ end
+flowchart BT
+ subgraph two
+ b1
+ end
+ subgraph three
+ c1-->c2
+ end
+ c1 --apa apa apa--> b1
+ two --> c2
+
+
+sequenceDiagram
+ Alice->>Bob:Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+
+
+ %%{init: {'securityLevel': 'loose'}}%%
+ graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{{Let me think... Do I want something for work, something to spend every free second with, or something to get around?}}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[Car]
+ click A "index.html#link-clicked" "link test"
+ click B callback "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+
+
+
+ flowchart BT
+ subgraph a
+ b1 -- ok --> b2
+ end
+ a -- sert --> c
+ c --> d
+ b1 --> d
+ a --asd123 --> d
+
+
+stateDiagram-v2
+ state A {
+ B1 --> B2: ok
+ }
+ A --> C: sert
+ C --> D
+ B1 --> D
+ A --> D: asd123
+
+
+
+ %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%%
+flowchart LR
+ a -->b
+ subgraph A
+ B
+ end
+ subgraph B
+ b
+ end
+
+
+
+flowchart TB
+ subgraph A
+ b-->B
+ a-->c
+ end
+ subgraph B
+ c
+ end
+
+
+
+sequenceDiagram
+Alice->Bob: Hello Bob, how are you?
+Note over Alice,Bob: Looks
+Note over Bob,Alice: Looks back
+
+
+
+
+
+
diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js
index 712fee30f..1cdda0265 100644
--- a/src/dagre-wrapper/index.js
+++ b/src/dagre-wrapper/index.js
@@ -6,7 +6,8 @@ import {
clear as clearGraphlib,
clusterDb,
adjustClustersAndEdges,
- findNonClusterChild
+ findNonClusterChild,
+ sortNodesByHierarchy
} from './mermaid-graphlib';
import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './nodes';
import { insertCluster, clear as clearClusters } from './clusters';
@@ -14,7 +15,7 @@ import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } f
import { logger as log } from '../logger';
const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
- log.info('Graph in recursive render:', graphlib.json.write(graph), parentCluster);
+ log.info('Graph in recursive render: XXX', graphlib.json.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.warn('Dir in recursive render - dir:', dir);
@@ -22,7 +23,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
if (!graph.nodes()) {
log.info('No nodes found for', graph);
} else {
- log.info('Recursive render', graph.nodes());
+ log.info('Recursive render XXX', graph.nodes());
}
if (graph.edges().length > 0) {
log.info('Recursive edges', graph.edge(graph.edges()[0]));
@@ -39,11 +40,14 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
if (typeof parentCluster !== 'undefined') {
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
// data.clusterPositioning = true;
- log.info('Setting data for cluster', data);
+ log.info('Setting data for cluster XXX (', v, ') ', data, parentCluster);
graph.setNode(parentCluster.id, data);
- graph.setParent(v, parentCluster.id, data);
+ if (!graph.parent(v)) {
+ log.warn('Setting parent', v, parentCluster.id);
+ graph.setParent(v, parentCluster.id, data);
+ }
}
- log.info('(Insert) Node ' + v + ': ' + JSON.stringify(graph.node(v)));
+ log.info('(Insert) Node XXX' + v + ': ' + JSON.stringify(graph.node(v)));
if (node && node.clusterNode) {
// const children = graph.children(v);
log.info('Cluster identified', v, node, graph.node(v));
@@ -56,7 +60,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
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('Cluster - the non recursive path XXX', 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));
@@ -91,7 +95,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
dagre.layout(graph);
log.info('Graph after layout:', graphlib.json.write(graph));
// Move the nodes to the correct place
- graph.nodes().forEach(function(v) {
+ sortNodesByHierarchy(graph).forEach(function(v) {
const node = graph.node(v);
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
log.info(
@@ -138,10 +142,10 @@ export const render = (elem, graph, markers, diagramtype, id) => {
clearClusters();
clearGraphlib();
- log.warn('Graph before:', graphlib.json.write(graph));
+ log.warn('Graph at first:', graphlib.json.write(graph));
adjustClustersAndEdges(graph);
log.warn('Graph after:', graphlib.json.write(graph));
- log.warn('Graph ever after:', graph.graph());
+ // log.warn('Graph ever after:', graphlib.json.write(graph.node('A').graph));
recursiveRender(elem, graph, diagramtype);
};
diff --git a/src/dagre-wrapper/mermaid-graphlib.js b/src/dagre-wrapper/mermaid-graphlib.js
index a9b7956c7..dfa187958 100644
--- a/src/dagre-wrapper/mermaid-graphlib.js
+++ b/src/dagre-wrapper/mermaid-graphlib.js
@@ -52,7 +52,7 @@ const edgeInCluster = (edge, clusterId) => {
};
const copy = (clusterId, graph, newGraph, rootId) => {
- log.info(
+ log.warn(
'Copying children of ',
clusterId,
'root',
@@ -68,7 +68,7 @@ const copy = (clusterId, graph, newGraph, rootId) => {
nodes.push(clusterId);
}
- log.debug('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
+ log.warn('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
nodes.forEach(node => {
if (graph.children(node).length > 0) {
@@ -77,8 +77,8 @@ const copy = (clusterId, graph, newGraph, rootId) => {
const data = graph.node(node);
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
newGraph.setNode(node, data);
- log.debug('Setting parent', node, graph.parent(node));
if (rootId !== graph.parent(node)) {
+ log.warn('Setting parent', node, graph.parent(node));
newGraph.setParent(node, graph.parent(node));
}
@@ -245,40 +245,51 @@ export const adjustClustersAndEdges = (graph, depth) => {
// d1 xor d2 - if either d1 is true and d2 is false or the other way around
if (d1 ^ d2) {
- log.debug('Edge: ', edge, ' leaves cluster ', id);
- log.debug('Decendants of ', id, ': ', decendants[id]);
+ log.warn('Edge: ', edge, ' leaves cluster ', id);
+ log.warn('Decendants of XXX ', id, ': ', decendants[id]);
clusterDb[id].externalConnections = true;
}
}
});
+ } else {
+ log.debug('Not a cluster ', id, decendants);
}
});
- extractor(graph, 0);
-
// For clusters with incoming and/or outgoing edges translate those edges to a real node
// in the cluster inorder to fake the edge
graph.edges().forEach(function(e) {
const edge = graph.edge(e);
- log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
- log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
+ log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
+ log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
let v = e.v;
let w = e.w;
// Check if link is either from or to a cluster
- log.trace('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
+ log.warn(
+ 'Fix XXX',
+ clusterDb,
+ 'ids:',
+ e.v,
+ e.w,
+ 'Translateing: ',
+ clusterDb[e.v],
+ ' --- ',
+ clusterDb[e.w]
+ );
if (clusterDb[e.v] || clusterDb[e.w]) {
- log.warn('Fixing and trixing - removing', e.v, e.w, e.name);
+ log.warn('Fixing and trixing - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
if (v !== e.v) edge.fromCluster = e.v;
if (w !== e.w) edge.toCluster = e.w;
- log.warn('Replacing with', v, w, e.name);
+ log.warn('Fix Replacing with XXX', v, w, e.name);
graph.setEdge(v, w, edge, e.name);
}
});
log.warn('Adjusted Graph', graphlib.json.write(graph));
+ extractor(graph, 0);
log.trace(clusterDb);
@@ -291,7 +302,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
};
export const extractor = (graph, depth) => {
- log.debug('extractor - ', depth, graphlib.json.write(graph), graph.children('D'));
+ log.warn('extractor - ', depth, graphlib.json.write(graph), graph.children('D'));
if (depth > 10) {
log.error('Bailing out');
return;
@@ -340,7 +351,7 @@ export const extractor = (graph, depth) => {
graph.children(node) &&
graph.children(node).length > 0
) {
- log.debug(
+ log.warn(
'Cluster without external connections, without a parent and with children',
node,
depth
@@ -364,7 +375,7 @@ export const extractor = (graph, depth) => {
return {};
});
- log.debug('Old graph before copy', graphlib.json.write(graph));
+ log.warn('Old graph before copy', graphlib.json.write(graph));
copy(node, graph, clusterGraph, node);
graph.setNode(node, {
clusterNode: true,
@@ -373,10 +384,10 @@ export const extractor = (graph, depth) => {
labelText: clusterDb[node].labelText,
graph: clusterGraph
});
- log.debug('New graph after copy', graphlib.json.write(clusterGraph));
+ log.warn('New graph after copy node: (', node, ')', graphlib.json.write(clusterGraph));
log.debug('Old graph after copy', graphlib.json.write(graph));
} else {
- log.debug(
+ log.warn(
'Cluster ** ',
node,
' **not meeting the criteria !externalConnections:',
@@ -393,13 +404,27 @@ export const extractor = (graph, depth) => {
}
nodes = graph.nodes();
- log.debug('New list of nodes', nodes);
+ log.warn('New list of nodes', nodes);
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const data = graph.node(node);
- log.debug(' Now next leveƶ', node, data);
+ log.warn(' Now next level', node, data);
if (data.clusterNode) {
extractor(data.graph, depth + 1);
}
}
};
+
+const sorter = (graph, nodes) => {
+ if (nodes.length === 0) return [];
+ let result = Object.assign(nodes);
+ nodes.forEach(node => {
+ const children = graph.children(node);
+ const sorted = sorter(graph, children);
+ result = result.concat(sorted);
+ });
+
+ return result;
+};
+
+export const sortNodesByHierarchy = graph => sorter(graph, graph.children());
diff --git a/src/dagre-wrapper/mermaid-graphlib.spec.js b/src/dagre-wrapper/mermaid-graphlib.spec.js
index 6dcda35db..e6a9506e8 100644
--- a/src/dagre-wrapper/mermaid-graphlib.spec.js
+++ b/src/dagre-wrapper/mermaid-graphlib.spec.js
@@ -1,6 +1,6 @@
import graphlib from 'graphlib';
import dagre from 'dagre';
-import { validate, adjustClustersAndEdges, extractDecendants } from './mermaid-graphlib';
+import { validate, adjustClustersAndEdges, extractDecendants, sortNodesByHierarchy } from './mermaid-graphlib';
import { setLogLevel, logger } from '../logger';
describe('Graphlib decorations', () => {
@@ -373,6 +373,31 @@ describe('Graphlib decorations', () => {
});
});
+ it('adjustClustersAndEdges should handle nesting GLB77', function () {
+ /*
+flowchart TB
+ subgraph A
+ b-->B
+ a-->c
+ end
+ subgraph B
+ c
+ end
+ */
+
+ const exportedGraph = JSON.parse('{"options":{"directed":true,"multigraph":true,"compound":true},"nodes":[{"v":"A","value":{"labelStyle":"","shape":"rect","labelText":"A","rx":0,"ry":0,"class":"default","style":"","id":"A","width":500,"type":"group","padding":15}},{"v":"B","value":{"labelStyle":"","shape":"rect","labelText":"B","rx":0,"ry":0,"class":"default","style":"","id":"B","width":500,"type":"group","padding":15},"parent":"A"},{"v":"b","value":{"labelStyle":"","shape":"rect","labelText":"b","rx":0,"ry":0,"class":"default","style":"","id":"b","padding":15},"parent":"A"},{"v":"c","value":{"labelStyle":"","shape":"rect","labelText":"c","rx":0,"ry":0,"class":"default","style":"","id":"c","padding":15},"parent":"B"},{"v":"a","value":{"labelStyle":"","shape":"rect","labelText":"a","rx":0,"ry":0,"class":"default","style":"","id":"a","padding":15},"parent":"A"}],"edges":[{"v":"b","w":"B","name":"1","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-b-B","classes":"flowchart-link LS-b LE-B"}},{"v":"a","w":"c","name":"2","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-a-c","classes":"flowchart-link LS-a LE-c"}}],"value":{"rankdir":"TB","nodesep":50,"ranksep":50,"marginx":8,"marginy":8}}');
+ const gr = graphlib.json.read(exportedGraph)
+
+ logger.info('Graph before', graphlib.json.write(gr))
+ adjustClustersAndEdges(gr);
+ const aGraph = gr.node('A').graph;
+ const bGraph = aGraph.node('B').graph;
+ logger.info('Graph after', graphlib.json.write(aGraph));
+ // logger.trace('Graph after', graphlib.json.write(g))
+ expect(aGraph.parent('c')).toBe('B');
+ expect(aGraph.parent('B')).toBe(undefined);
+ });
+
});
describe('extractDecendants', function () {
let g;
@@ -426,3 +451,57 @@ describe('extractDecendants', function () {
expect(d3).toEqual(['c']);
});
});
+describe('sortNodesByHierarchy', function () {
+ let g;
+ beforeEach(function () {
+ setLogLevel(1);
+ g = new graphlib.Graph({
+ multigraph: true,
+ compound: true
+ });
+ g.setGraph({
+ rankdir: 'TB',
+ nodesep: 10,
+ ranksep: 10,
+ marginx: 8,
+ marginy: 8
+ });
+ g.setDefaultEdgeLabel(function () {
+ return {};
+ });
+ });
+ it('it should sort proper en nodes are in reverse order', function () {
+ /*
+ a -->b
+ subgraph B
+ b
+ end
+ subgraph A
+ B
+ end
+ */
+ g.setNode('a', { data: 1 });
+ g.setNode('b', { data: 2 });
+ g.setParent('b', 'B');
+ g.setParent('B', 'A');
+ g.setEdge('a', 'b', '1');
+ expect(sortNodesByHierarchy(g)).toEqual(['a', 'A', 'B', 'b']);
+ });
+ it('it should sort proper en nodes are in correct order', function () {
+ /*
+ a -->b
+ subgraph B
+ b
+ end
+ subgraph A
+ B
+ end
+ */
+ g.setNode('a', { data: 1 });
+ g.setParent('B', 'A');
+ g.setParent('b', 'B');
+ g.setNode('b', { data: 2 });
+ g.setEdge('a', 'b', '1');
+ expect(sortNodesByHierarchy(g)).toEqual(['a', 'A', 'B', 'b']);
+ });
+});
diff --git a/src/diagrams/flowchart/flowDb.js b/src/diagrams/flowchart/flowDb.js
index a18e23c43..6be1f2c18 100644
--- a/src/diagrams/flowchart/flowDb.js
+++ b/src/diagrams/flowchart/flowDb.js
@@ -421,6 +421,22 @@ export const addSubGraph = function(_id, list, _title) {
title = common.sanitizeText(title, config);
subCount = subCount + 1;
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
+
+ /**
+ * Deletes an id from all subgraphs
+ */
+ const del = _id => {
+ subGraphs.forEach(sg => {
+ const pos = sg.nodes.indexOf(_id);
+ if (pos >= 0) {
+ console.log(sg.nodes, pos, _id);
+ sg.nodes.splice(pos, 1);
+ }
+ });
+ };
+ // Removes the members of this subgraph from any other subgraphs, a node only belong to one subgraph
+ subGraph.nodes.forEach(_id => del(_id));
+ console.log(subGraph.nodes);
subGraphs.push(subGraph);
subGraphLookup[id] = subGraph;
return id;
diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js
index d89ef118b..6d0d48359 100644
--- a/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/src/diagrams/flowchart/flowRenderer-v2.js
@@ -382,11 +382,13 @@ export const draw = function(text, id) {
logger.info(edges);
let i = 0;
for (i = subGraphs.length - 1; i >= 0; i--) {
+ // for (let i = 0; i < subGraphs.length; i++) {
subG = subGraphs[i];
selectAll('cluster').append('text');
for (let j = 0; j < subG.nodes.length; j++) {
+ logger.info('Setting up subgraphs', subG.nodes[j], subG.id);
g.setParent(subG.nodes[j], subG.id);
}
}
diff --git a/src/diagrams/flowchart/parser/subgraph.spec.js b/src/diagrams/flowchart/parser/subgraph.spec.js
index e2e412b40..d83d62d22 100644
--- a/src/diagrams/flowchart/parser/subgraph.spec.js
+++ b/src/diagrams/flowchart/parser/subgraph.spec.js
@@ -1,5 +1,6 @@
import flowDb from '../flowDb';
import flow from './flow';
+import filter from 'lodash/filter';
import { setConfig } from '../../../config';
setConfig({
@@ -238,4 +239,75 @@ describe('when parsing subgraphs', function() {
expect(edges[0].type).toBe('arrow_point');
});
+ it('should handle nested subgraphs 1', function() {
+ const res = flow.parser.parse(`flowchart TB
+ subgraph A
+ b-->B
+ a-->c
+ end
+ subgraph B
+ c
+ end`);
+
+ const subgraphs = flow.parser.yy.getSubGraphs();
+ expect(subgraphs.length).toBe(2);
+
+ const subgraphA = filter(subgraphs,o => o.id === 'A')[0];
+ const subgraphB = filter(subgraphs,o => o.id === 'B')[0];
+
+ expect(subgraphB.nodes[0]).toBe('c');
+ expect(subgraphA.nodes).toContain('B');
+ expect(subgraphA.nodes).toContain('b');
+ expect(subgraphA.nodes).toContain('a');
+ expect(subgraphA.nodes).not.toContain('c');
+ });
+ it('should handle nested subgraphs 2', function() {
+ const res = flow.parser.parse(`flowchart TB
+ b-->B
+ a-->c
+ subgraph B
+ c
+ end
+ subgraph A
+ a
+ b
+ B
+ end`);
+
+ const subgraphs = flow.parser.yy.getSubGraphs();
+ expect(subgraphs.length).toBe(2);
+
+ const subgraphA = filter(subgraphs,o => o.id === 'A')[0];
+ const subgraphB = filter(subgraphs,o => o.id === 'B')[0];
+
+ expect(subgraphB.nodes[0]).toBe('c');
+ expect(subgraphA.nodes).toContain('B');
+ expect(subgraphA.nodes).toContain('b');
+ expect(subgraphA.nodes).toContain('a');
+ expect(subgraphA.nodes).not.toContain('c');
+ });
+ it('should handle nested subgraphs 3', function() {
+ console.log('#3');
+ const res = flow.parser.parse(`flowchart TB
+ subgraph B
+ c
+ end
+ a-->c
+ subgraph A
+ b-->B
+ a
+ end`);
+
+ const subgraphs = flow.parser.yy.getSubGraphs();
+ expect(subgraphs.length).toBe(2);
+
+ const subgraphA = filter(subgraphs,o => o.id === 'A')[0];
+ const subgraphB = filter(subgraphs,o => o.id === 'B')[0];
+ console.log(subgraphB.nodes)
+ expect(subgraphB.nodes[0]).toBe('c');
+ expect(subgraphA.nodes).toContain('B');
+ expect(subgraphA.nodes).toContain('b');
+ expect(subgraphA.nodes).toContain('a');
+ expect(subgraphA.nodes).not.toContain('c');
+ });
});