Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-7609-numeric-subgraph-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix(flowchart): render nested subgraphs with numeric ids without dagre rank crash (#7609)
17 changes: 17 additions & 0 deletions cypress/integration/rendering/flowchart/flowchart.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,23 @@ graph TD
}
);
});
it('#7609: should render nested subgraph with numeric id without crashing', () => {
imgSnapshotTest(
`
graph LR
subgraph outer
subgraph 1 ["inner"]
external
subgraph sub
internal
end
sub-->external
end
end
`,
{ fontFamily: 'courier' }
);
});
});
it('#5824: should be able to render string and markdown labels', () => {
imgSnapshotTest(
Expand Down
13 changes: 13 additions & 0 deletions packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,19 @@ export const extractor = (graph, depth) => {
// containing the nodes and edges in the cluster in a new graph
// for (let i = 0;)
let nodes = graph.nodes();
// graphlib backs nodes with a plain object, so integer-like string ids
// ("1") sort ahead of others ("outer") and reorder iteration. Process
// outer clusters first so nested extraction is order-independent. (#7609)
const nodeDepth = (v) => {
let d = 0;
let cur = graph.parent(v);
while (cur != null) {
d++;
cur = graph.parent(cur);
}
return d;
};
nodes = [...nodes].sort((a, b) => nodeDepth(a) - nodeDepth(b));
let hasChildren = false;
for (const node of nodes) {
const children = graph.children(node);
Expand Down
34 changes: 34 additions & 0 deletions packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,40 @@ describe('Graphlib decorations', () => {
expect(bGraph.edges().length).toBe(0);
});
});
it('adjustClustersAndEdges should extract nested clusters when a subgraph has a numeric id (issue #7609)', function () {
/*
graph LR
subgraph outer
subgraph 1 ["inner"]
external
subgraph sub
internal
end
sub-->external
end
end
*/
g.setNode('outer', { id: 'outer' });
g.setNode('1', { id: '1' });
g.setNode('sub', { id: 'sub' });
g.setNode('external', { id: 'external' });
g.setNode('internal', { id: 'internal' });
g.setParent('1', 'outer');
g.setParent('sub', '1');
g.setParent('external', '1');
g.setParent('internal', 'sub');
g.setEdge('sub', 'external', { data: 'sub-external' });

adjustClustersAndEdges(g);

const outerGraph = g.node('outer').graph;
const innerGraph = outerGraph.node('1').graph;
expect(innerGraph.node('sub')?.clusterNode).toBe(true);
const subGraph = innerGraph.node('sub').graph;
expect(subGraph.nodes()).toEqual(['internal']);
expect(innerGraph.children('sub')).toEqual([]);
});

it('adjustClustersAndEdges should handle nesting GLB77', function () {
/*
flowchart TB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,19 @@ export const extractor = (graph, depth) => {
return;
}
let nodes = graph.nodes();
// graphlib backs nodes with a plain object, so integer-like string ids
// ("1") sort ahead of others ("outer") and reorder iteration. Process
// outer clusters first so nested extraction is order-independent. (#7609)
const nodeDepth = (v) => {
let d = 0;
let cur = graph.parent(v);
while (cur != null) {
d++;
cur = graph.parent(cur);
}
return d;
};
nodes = [...nodes].sort((a, b) => nodeDepth(a) - nodeDepth(b));
let hasChildren = false;
for (const node of nodes) {
const children = graph.children(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,40 @@ describe('Graphlib decorations', () => {
expect(bGraph.edges().length).toBe(0);
});
});
it('adjustClustersAndEdges should extract nested clusters when a subgraph has a numeric id (issue #7609)', function () {
/*
graph LR
subgraph outer
subgraph 1 ["inner"]
external
subgraph sub
internal
end
sub-->external
end
end
*/
g.setNode('outer', { id: 'outer' });
g.setNode('1', { id: '1' });
g.setNode('sub', { id: 'sub' });
g.setNode('external', { id: 'external' });
g.setNode('internal', { id: 'internal' });
g.setParent('1', 'outer');
g.setParent('sub', '1');
g.setParent('external', '1');
g.setParent('internal', 'sub');
g.setEdge('sub', 'external', { data: 'sub-external' });

adjustClustersAndEdges(g);

const outerGraph = g.node('outer').graph;
const innerGraph = outerGraph.node('1').graph;
expect(innerGraph.node('sub')?.clusterNode).toBe(true);
const subGraph = innerGraph.node('sub').graph;
expect(subGraph.nodes()).toEqual(['internal']);
expect(innerGraph.children('sub')).toEqual([]);
});

it('adjustClustersAndEdges should handle nesting GLB77', function () {
/*
flowchart TB
Expand Down
Loading