mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 06:49:47 +02:00
#1602 Update of handling of nested subgraphs
This commit is contained in:
@@ -125,4 +125,32 @@ describe('Flowchart v2', () => {
|
|||||||
expect(svg).to.not.have.attr('style');
|
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'}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -75,18 +75,47 @@ stateDiagram-v2
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid" style="width: 50%; height: 20%;">
|
<div class="mermaid" style="width: 50%; height: 20%;">
|
||||||
|
%% 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
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="mermaid" style="width: 50%; height: 20%;">
|
||||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%%
|
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%%
|
||||||
classDiagram
|
flowchart TB
|
||||||
Animal <|-- Duck
|
b-->B
|
||||||
|
a-->c
|
||||||
|
subgraph O
|
||||||
|
A
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
c
|
||||||
|
end
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
b
|
||||||
|
B
|
||||||
|
end
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid2" style="width: 50%; height: 20%;">
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
graph TD
|
flowchart TB
|
||||||
A(Start) --> B[/Another/]
|
subgraph O
|
||||||
A[/Another/] --> C[End]
|
A
|
||||||
subgraph section
|
end
|
||||||
B
|
subgraph A
|
||||||
C
|
b-->B
|
||||||
|
a-->c
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
c
|
||||||
end
|
end
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid2" style="width: 50%; height: 20%;">
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
|
129
cypress/platform/current2.html
Normal file
129
cypress/platform/current2.html
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Montserrat&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
/* background: rgb(221, 208, 208); */
|
||||||
|
/* background:#333; */
|
||||||
|
font-family: 'Arial';
|
||||||
|
}
|
||||||
|
h1 { color: grey;}
|
||||||
|
.mermaid2 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>info below</h1>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
|
flowchart BT
|
||||||
|
subgraph two
|
||||||
|
b1
|
||||||
|
end
|
||||||
|
subgraph three
|
||||||
|
c1-->c2
|
||||||
|
end
|
||||||
|
c1 --apa apa apa--> b1
|
||||||
|
two --> c2
|
||||||
|
</div>
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 200px;">
|
||||||
|
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
|
||||||
|
</div>
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 200px;">
|
||||||
|
%%{init: {'securityLevel': 'loose'}}%%
|
||||||
|
graph TD
|
||||||
|
A[Christmas] -->|Get money| B(Go shopping)
|
||||||
|
B --> C{{Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />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;
|
||||||
|
</div>
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 200px;">
|
||||||
|
|
||||||
|
flowchart BT
|
||||||
|
subgraph a
|
||||||
|
b1 -- ok --> b2
|
||||||
|
end
|
||||||
|
a -- sert --> c
|
||||||
|
c --> d
|
||||||
|
b1 --> d
|
||||||
|
a --asd123 --> d
|
||||||
|
</div>
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
|
stateDiagram-v2
|
||||||
|
state A {
|
||||||
|
B1 --> B2: ok
|
||||||
|
}
|
||||||
|
A --> C: sert
|
||||||
|
C --> D
|
||||||
|
B1 --> D
|
||||||
|
A --> D: asd123
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
|
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
a -->b
|
||||||
|
subgraph A
|
||||||
|
B
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="mermaid" style="width: 50%; height: 20%;">
|
||||||
|
flowchart TB
|
||||||
|
subgraph A
|
||||||
|
b-->B
|
||||||
|
a-->c
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
c
|
||||||
|
end
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->Bob: Hello Bob, how are you?
|
||||||
|
Note over Alice,Bob: Looks
|
||||||
|
Note over Bob,Alice: Looks back
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="./mermaid.js"></script>
|
||||||
|
<script>
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
// console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
mermaid.initialize({
|
||||||
|
// theme: 'forest',
|
||||||
|
// themeVariables:{primaryColor: '#ff0000'},
|
||||||
|
// arrowMarkerAbsolute: true,
|
||||||
|
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
|
||||||
|
logLevel: 0,
|
||||||
|
flowchart: { curve: 'cardinal', "htmlLabels": false },
|
||||||
|
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||||
|
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
||||||
|
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||||
|
fontFamily: '"arial", sans-serif',
|
||||||
|
curve: 'cardinal',
|
||||||
|
securityLevel: 'strict'
|
||||||
|
});
|
||||||
|
function callback(){alert('It worked');}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -6,7 +6,8 @@ import {
|
|||||||
clear as clearGraphlib,
|
clear as clearGraphlib,
|
||||||
clusterDb,
|
clusterDb,
|
||||||
adjustClustersAndEdges,
|
adjustClustersAndEdges,
|
||||||
findNonClusterChild
|
findNonClusterChild,
|
||||||
|
sortNodesByHierarchy
|
||||||
} from './mermaid-graphlib';
|
} from './mermaid-graphlib';
|
||||||
import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './nodes';
|
import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './nodes';
|
||||||
import { insertCluster, clear as clearClusters } from './clusters';
|
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';
|
import { logger as log } from '../logger';
|
||||||
|
|
||||||
const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
|
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;
|
const dir = graph.graph().rankdir;
|
||||||
log.warn('Dir in recursive render - dir:', dir);
|
log.warn('Dir in recursive render - dir:', dir);
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
|
|||||||
if (!graph.nodes()) {
|
if (!graph.nodes()) {
|
||||||
log.info('No nodes found for', graph);
|
log.info('No nodes found for', graph);
|
||||||
} else {
|
} else {
|
||||||
log.info('Recursive render', graph.nodes());
|
log.info('Recursive render XXX', graph.nodes());
|
||||||
}
|
}
|
||||||
if (graph.edges().length > 0) {
|
if (graph.edges().length > 0) {
|
||||||
log.info('Recursive edges', graph.edge(graph.edges()[0]));
|
log.info('Recursive edges', graph.edge(graph.edges()[0]));
|
||||||
@@ -39,11 +40,14 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
|
|||||||
if (typeof parentCluster !== 'undefined') {
|
if (typeof parentCluster !== 'undefined') {
|
||||||
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
|
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
|
||||||
// data.clusterPositioning = true;
|
// 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.setNode(parentCluster.id, data);
|
||||||
|
if (!graph.parent(v)) {
|
||||||
|
log.warn('Setting parent', v, parentCluster.id);
|
||||||
graph.setParent(v, parentCluster.id, data);
|
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) {
|
if (node && node.clusterNode) {
|
||||||
// const children = graph.children(v);
|
// const children = graph.children(v);
|
||||||
log.info('Cluster identified', v, node, graph.node(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) {
|
if (graph.children(v).length > 0) {
|
||||||
// This is a cluster but not to be rendered recusively
|
// This is a cluster but not to be rendered recusively
|
||||||
// Render as before
|
// 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));
|
log.info(findNonClusterChild(node.id, graph));
|
||||||
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
|
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
|
||||||
// insertCluster(clusters, graph.node(v));
|
// insertCluster(clusters, graph.node(v));
|
||||||
@@ -91,7 +95,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
|
|||||||
dagre.layout(graph);
|
dagre.layout(graph);
|
||||||
log.info('Graph after layout:', graphlib.json.write(graph));
|
log.info('Graph after layout:', graphlib.json.write(graph));
|
||||||
// Move the nodes to the correct place
|
// Move the nodes to the correct place
|
||||||
graph.nodes().forEach(function(v) {
|
sortNodesByHierarchy(graph).forEach(function(v) {
|
||||||
const node = graph.node(v);
|
const node = graph.node(v);
|
||||||
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
|
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
log.info(
|
log.info(
|
||||||
@@ -138,10 +142,10 @@ export const render = (elem, graph, markers, diagramtype, id) => {
|
|||||||
clearClusters();
|
clearClusters();
|
||||||
clearGraphlib();
|
clearGraphlib();
|
||||||
|
|
||||||
log.warn('Graph before:', graphlib.json.write(graph));
|
log.warn('Graph at first:', graphlib.json.write(graph));
|
||||||
adjustClustersAndEdges(graph);
|
adjustClustersAndEdges(graph);
|
||||||
log.warn('Graph after:', graphlib.json.write(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);
|
recursiveRender(elem, graph, diagramtype);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -52,7 +52,7 @@ const edgeInCluster = (edge, clusterId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const copy = (clusterId, graph, newGraph, rootId) => {
|
const copy = (clusterId, graph, newGraph, rootId) => {
|
||||||
log.info(
|
log.warn(
|
||||||
'Copying children of ',
|
'Copying children of ',
|
||||||
clusterId,
|
clusterId,
|
||||||
'root',
|
'root',
|
||||||
@@ -68,7 +68,7 @@ const copy = (clusterId, graph, newGraph, rootId) => {
|
|||||||
nodes.push(clusterId);
|
nodes.push(clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
|
log.warn('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
if (graph.children(node).length > 0) {
|
if (graph.children(node).length > 0) {
|
||||||
@@ -77,8 +77,8 @@ const copy = (clusterId, graph, newGraph, rootId) => {
|
|||||||
const data = graph.node(node);
|
const data = graph.node(node);
|
||||||
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
|
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
|
||||||
newGraph.setNode(node, data);
|
newGraph.setNode(node, data);
|
||||||
log.debug('Setting parent', node, graph.parent(node));
|
|
||||||
if (rootId !== graph.parent(node)) {
|
if (rootId !== graph.parent(node)) {
|
||||||
|
log.warn('Setting parent', node, graph.parent(node));
|
||||||
newGraph.setParent(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
|
// d1 xor d2 - if either d1 is true and d2 is false or the other way around
|
||||||
if (d1 ^ d2) {
|
if (d1 ^ d2) {
|
||||||
log.debug('Edge: ', edge, ' leaves cluster ', id);
|
log.warn('Edge: ', edge, ' leaves cluster ', id);
|
||||||
log.debug('Decendants of ', id, ': ', decendants[id]);
|
log.warn('Decendants of XXX ', id, ': ', decendants[id]);
|
||||||
clusterDb[id].externalConnections = true;
|
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
|
// For clusters with incoming and/or outgoing edges translate those edges to a real node
|
||||||
// in the cluster inorder to fake the edge
|
// in the cluster inorder to fake the edge
|
||||||
graph.edges().forEach(function(e) {
|
graph.edges().forEach(function(e) {
|
||||||
const edge = graph.edge(e);
|
const edge = graph.edge(e);
|
||||||
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
log.warn('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(graph.edge(e)));
|
||||||
|
|
||||||
let v = e.v;
|
let v = e.v;
|
||||||
let w = e.w;
|
let w = e.w;
|
||||||
// Check if link is either from or to a cluster
|
// 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]) {
|
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);
|
v = getAnchorId(e.v);
|
||||||
w = getAnchorId(e.w);
|
w = getAnchorId(e.w);
|
||||||
graph.removeEdge(e.v, e.w, e.name);
|
graph.removeEdge(e.v, e.w, e.name);
|
||||||
if (v !== e.v) edge.fromCluster = e.v;
|
if (v !== e.v) edge.fromCluster = e.v;
|
||||||
if (w !== e.w) edge.toCluster = e.w;
|
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);
|
graph.setEdge(v, w, edge, e.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
log.warn('Adjusted Graph', graphlib.json.write(graph));
|
log.warn('Adjusted Graph', graphlib.json.write(graph));
|
||||||
|
extractor(graph, 0);
|
||||||
|
|
||||||
log.trace(clusterDb);
|
log.trace(clusterDb);
|
||||||
|
|
||||||
@@ -291,7 +302,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const extractor = (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) {
|
if (depth > 10) {
|
||||||
log.error('Bailing out');
|
log.error('Bailing out');
|
||||||
return;
|
return;
|
||||||
@@ -340,7 +351,7 @@ export const extractor = (graph, depth) => {
|
|||||||
graph.children(node) &&
|
graph.children(node) &&
|
||||||
graph.children(node).length > 0
|
graph.children(node).length > 0
|
||||||
) {
|
) {
|
||||||
log.debug(
|
log.warn(
|
||||||
'Cluster without external connections, without a parent and with children',
|
'Cluster without external connections, without a parent and with children',
|
||||||
node,
|
node,
|
||||||
depth
|
depth
|
||||||
@@ -364,7 +375,7 @@ export const extractor = (graph, depth) => {
|
|||||||
return {};
|
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);
|
copy(node, graph, clusterGraph, node);
|
||||||
graph.setNode(node, {
|
graph.setNode(node, {
|
||||||
clusterNode: true,
|
clusterNode: true,
|
||||||
@@ -373,10 +384,10 @@ export const extractor = (graph, depth) => {
|
|||||||
labelText: clusterDb[node].labelText,
|
labelText: clusterDb[node].labelText,
|
||||||
graph: clusterGraph
|
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));
|
log.debug('Old graph after copy', graphlib.json.write(graph));
|
||||||
} else {
|
} else {
|
||||||
log.debug(
|
log.warn(
|
||||||
'Cluster ** ',
|
'Cluster ** ',
|
||||||
node,
|
node,
|
||||||
' **not meeting the criteria !externalConnections:',
|
' **not meeting the criteria !externalConnections:',
|
||||||
@@ -393,13 +404,27 @@ export const extractor = (graph, depth) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodes = graph.nodes();
|
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++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i];
|
const node = nodes[i];
|
||||||
const data = graph.node(node);
|
const data = graph.node(node);
|
||||||
log.debug(' Now next leveö', node, data);
|
log.warn(' Now next level', node, data);
|
||||||
if (data.clusterNode) {
|
if (data.clusterNode) {
|
||||||
extractor(data.graph, depth + 1);
|
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());
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import graphlib from 'graphlib';
|
import graphlib from 'graphlib';
|
||||||
import dagre from 'dagre';
|
import dagre from 'dagre';
|
||||||
import { validate, adjustClustersAndEdges, extractDecendants } from './mermaid-graphlib';
|
import { validate, adjustClustersAndEdges, extractDecendants, sortNodesByHierarchy } from './mermaid-graphlib';
|
||||||
import { setLogLevel, logger } from '../logger';
|
import { setLogLevel, logger } from '../logger';
|
||||||
|
|
||||||
describe('Graphlib decorations', () => {
|
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 () {
|
describe('extractDecendants', function () {
|
||||||
let g;
|
let g;
|
||||||
@@ -426,3 +451,57 @@ describe('extractDecendants', function () {
|
|||||||
expect(d3).toEqual(['c']);
|
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']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@@ -421,6 +421,22 @@ export const addSubGraph = function(_id, list, _title) {
|
|||||||
title = common.sanitizeText(title, config);
|
title = common.sanitizeText(title, config);
|
||||||
subCount = subCount + 1;
|
subCount = subCount + 1;
|
||||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
|
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);
|
subGraphs.push(subGraph);
|
||||||
subGraphLookup[id] = subGraph;
|
subGraphLookup[id] = subGraph;
|
||||||
return id;
|
return id;
|
||||||
|
@@ -382,11 +382,13 @@ export const draw = function(text, id) {
|
|||||||
logger.info(edges);
|
logger.info(edges);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (i = subGraphs.length - 1; i >= 0; i--) {
|
for (i = subGraphs.length - 1; i >= 0; i--) {
|
||||||
|
// for (let i = 0; i < subGraphs.length; i++) {
|
||||||
subG = subGraphs[i];
|
subG = subGraphs[i];
|
||||||
|
|
||||||
selectAll('cluster').append('text');
|
selectAll('cluster').append('text');
|
||||||
|
|
||||||
for (let j = 0; j < subG.nodes.length; j++) {
|
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);
|
g.setParent(subG.nodes[j], subG.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import flowDb from '../flowDb';
|
import flowDb from '../flowDb';
|
||||||
import flow from './flow';
|
import flow from './flow';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
import { setConfig } from '../../../config';
|
import { setConfig } from '../../../config';
|
||||||
|
|
||||||
setConfig({
|
setConfig({
|
||||||
@@ -238,4 +239,75 @@ describe('when parsing subgraphs', function() {
|
|||||||
|
|
||||||
expect(edges[0].type).toBe('arrow_point');
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user