mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-16 13:59:54 +02:00
#1295 Updates mermaid-graphlib
This commit is contained in:
@@ -87,9 +87,19 @@ stateDiagram-v2
|
|||||||
</div>
|
</div>
|
||||||
<div class="mermaid" style="width: 100%; height: 100%;">
|
<div class="mermaid" style="width: 100%; height: 100%;">
|
||||||
flowchart TD
|
flowchart TD
|
||||||
subgraph Apa
|
subgraph A
|
||||||
a --> b
|
a
|
||||||
end
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
subgraph D
|
||||||
|
d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
A -- oAo --> B
|
||||||
|
A --> C
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
|
@@ -24,8 +24,6 @@ const rect = (parent, node) => {
|
|||||||
|
|
||||||
const padding = 0 * node.padding;
|
const padding = 0 * node.padding;
|
||||||
const halfPadding = padding / 2;
|
const halfPadding = padding / 2;
|
||||||
const width = node.width || 50;
|
|
||||||
const height = node.height || 50;
|
|
||||||
|
|
||||||
log.info('Data ', node, JSON.stringify(node));
|
log.info('Data ', node, JSON.stringify(node));
|
||||||
// center the rect around its coordinate
|
// center the rect around its coordinate
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
findNonClusterChild
|
findNonClusterChild
|
||||||
} 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, positionCluster } from './clusters';
|
import { insertCluster, clear as clearClusters } from './clusters';
|
||||||
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges';
|
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges';
|
||||||
import { logger as log } from '../logger';
|
import { logger as log } from '../logger';
|
||||||
|
|
||||||
@@ -24,11 +24,12 @@ const getAnchorId = id => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const recursiveRender = (_elem, graph, diagramtype) => {
|
const recursiveRender = (_elem, graph, diagramtype) => {
|
||||||
|
log.info('Graph in recursive render:', graphlib.json.write(graph));
|
||||||
const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line
|
const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line
|
||||||
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.edges());
|
log.info('Recursive render', 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]));
|
||||||
@@ -50,7 +51,7 @@ const recursiveRender = (_elem, graph, diagramtype) => {
|
|||||||
updateNodeBounds(node, newEl);
|
updateNodeBounds(node, newEl);
|
||||||
setNodeElem(newEl, node);
|
setNodeElem(newEl, node);
|
||||||
|
|
||||||
log.info('Recursice render complete', newEl, node);
|
log.warn('Recursive render complete', newEl, node);
|
||||||
} else {
|
} else {
|
||||||
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
|
||||||
@@ -65,7 +66,6 @@ const recursiveRender = (_elem, graph, diagramtype) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
log.info('Clusters ', clusterDb);
|
|
||||||
|
|
||||||
// Insert labels, this will insert them into the dom so that the width can be calculated
|
// 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
|
// Also figure out which edges point to/from clusters and adjust them accordingly
|
||||||
@@ -74,27 +74,24 @@ const recursiveRender = (_elem, graph, diagramtype) => {
|
|||||||
graph.edges().forEach(function(e) {
|
graph.edges().forEach(function(e) {
|
||||||
const edge = graph.edge(e.v, e.w, e.name);
|
const edge = graph.edge(e.v, e.w, e.name);
|
||||||
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||||
log.info(
|
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
|
||||||
'Edge ' + e.v + ' -> ' + e.w + ': ',
|
|
||||||
e,
|
|
||||||
' ',
|
|
||||||
+JSON.stringify(graph.edge(e.v, e.w, e.name))
|
|
||||||
);
|
|
||||||
|
|
||||||
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.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]) {
|
// Todo handle case with links
|
||||||
log.info('Fixing and trixing - removing', e.v, e.w, e.name);
|
|
||||||
v = getAnchorId(e.v, graph, nodes);
|
// if (clusterDb[e.v] || clusterDb[e.w]) {
|
||||||
w = getAnchorId(e.w, graph, nodes);
|
// log.info('Fixing and trixing - removing', e.v, e.w, e.name);
|
||||||
graph.removeEdge(e.v, e.w, e.name);
|
// v = getAnchorId(e.v, graph, nodes);
|
||||||
if (v !== e.v) edge.fromCluster = e.v;
|
// w = getAnchorId(e.w, graph, nodes);
|
||||||
if (w !== e.w) edge.toCluster = e.w;
|
// graph.removeEdge(e.v, e.w, e.name);
|
||||||
log.info('Fixing Replacing with', v, w, e.name);
|
// if (v !== e.v) edge.fromCluster = e.v;
|
||||||
graph.setEdge(v, w, edge, e.name);
|
// if (w !== e.w) edge.toCluster = e.w;
|
||||||
}
|
// log.info('Fixing Replacing with', v, w, e.name);
|
||||||
|
// graph.setEdge(v, w, edge, e.name);
|
||||||
|
// }
|
||||||
insertEdgeLabel(edgeLabels, edge);
|
insertEdgeLabel(edgeLabels, edge);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,14 +103,22 @@ const recursiveRender = (_elem, graph, diagramtype) => {
|
|||||||
log.info('#############################################');
|
log.info('#############################################');
|
||||||
log.info(graph);
|
log.info(graph);
|
||||||
dagre.layout(graph);
|
dagre.layout(graph);
|
||||||
|
log.warn('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) {
|
graph.nodes().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(
|
||||||
|
'Position ' + v + ': (' + node.x,
|
||||||
|
',' + node.y,
|
||||||
|
') width: ',
|
||||||
|
node.width,
|
||||||
|
' height: ',
|
||||||
|
node.height
|
||||||
|
);
|
||||||
if (node && node.clusterNode) {
|
if (node && node.clusterNode) {
|
||||||
// clusterDb[node.id].node = node;
|
// clusterDb[node.id].node = node;
|
||||||
// positionNode(node);
|
positionNode(node);
|
||||||
} else {
|
} else {
|
||||||
// Non cluster node
|
// Non cluster node
|
||||||
if (graph.children(v).length > 0) {
|
if (graph.children(v).length > 0) {
|
||||||
@@ -130,7 +135,7 @@ const recursiveRender = (_elem, graph, diagramtype) => {
|
|||||||
// Move the edge labels to the correct place after layout
|
// Move the edge labels to the correct place after layout
|
||||||
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(edge), edge);
|
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
|
||||||
|
|
||||||
insertEdge(edgePaths, edge, clusterDb, diagramtype);
|
insertEdge(edgePaths, edge, clusterDb, diagramtype);
|
||||||
positionEdgeLabel(edge);
|
positionEdgeLabel(edge);
|
||||||
|
@@ -1,58 +1,152 @@
|
|||||||
/**
|
/**
|
||||||
* Decorates with functions required by mermaids dagre-wrapper.
|
* Decorates with functions required by mermaids dagre-wrapper.
|
||||||
*/
|
*/
|
||||||
import { logger } from '../logger';
|
import { logger as log } from '../logger';
|
||||||
import graphlib from 'graphlib';
|
import graphlib from 'graphlib';
|
||||||
|
|
||||||
export let clusterDb = {};
|
export let clusterDb = {};
|
||||||
let decendants = {};
|
let decendants = {};
|
||||||
|
let parents = {};
|
||||||
|
let graphs = {};
|
||||||
|
|
||||||
export const clear = () => {
|
export const clear = () => {
|
||||||
decendants = {};
|
decendants = {};
|
||||||
|
parents = {};
|
||||||
clusterDb = {};
|
clusterDb = {};
|
||||||
|
graphs = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const copy = (clusterId, graph, newGraph, rootId) => {
|
const isDecendant = (id, ancenstorId) => {
|
||||||
logger.info('Copying ', clusterId, graph.node(clusterId));
|
// if (id === ancenstorId) return true;
|
||||||
|
|
||||||
|
log.info('In isDecendant', ancenstorId, ' ', id, ' = ', decendants[ancenstorId].indexOf(id) >= 0);
|
||||||
|
if (decendants[ancenstorId].indexOf(id) >= 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const edgeInCluster = (edge, clusterId) => {
|
||||||
|
// Edges to/from the cluster is not in the cluster, they are in the parent
|
||||||
|
if (!(edge.v === clusterId || edge.w === clusterId)) return false;
|
||||||
|
|
||||||
|
if (!decendants[clusterId]) {
|
||||||
|
log.info('Tilt, ', clustedId, ',not in decendants');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decendants[clusterId].indexOf(edge.v) >= 0) return true;
|
||||||
|
if (isDecendant(edge.v, clusterId)) return true;
|
||||||
|
if (isDecendant(edge.w, clusterId)) return true;
|
||||||
|
if (decendants[clusterId].indexOf(edge.w) >= 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyOld = (clusterId, graph, newGraph, rootId) => {
|
||||||
|
log.info('Copying to', rootId, ' from ', clusterId, graph.node(clusterId), rootId);
|
||||||
const nodes = graph.children(clusterId);
|
const nodes = graph.children(clusterId);
|
||||||
|
log.info('Copying (nodes)', nodes);
|
||||||
|
if (nodes) {
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (graph.children(node).length > 0) {
|
||||||
|
copy(node, graph, newGraph, rootId);
|
||||||
|
} else {
|
||||||
|
// if (clusterId === rootId) {
|
||||||
|
const data = graph.node(node);
|
||||||
|
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
|
||||||
|
newGraph.setNode(node, data);
|
||||||
|
newGraph.setParent(node, clusterId);
|
||||||
|
const edges = graph.edges(node);
|
||||||
|
log.trace('Copying Edges', edges);
|
||||||
|
edges.forEach(edge => {
|
||||||
|
log.trace('Edge', edge);
|
||||||
|
const data = graph.edge(edge.v, edge.w, edge.name);
|
||||||
|
log.trace('Edge data', data, rootId);
|
||||||
|
try {
|
||||||
|
// Do not copy edges in and out of the root cluster, they belong to the parent graph
|
||||||
|
if (edgeInCluster(edge, rootId)) {
|
||||||
|
log.trace('Copying as ', edge.v, edge.w, data, edge.name);
|
||||||
|
newGraph.setEdge(edge.v, edge.w, data, edge.name);
|
||||||
|
log.trace('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0]));
|
||||||
|
} else {
|
||||||
|
log.trace('Skipping copy of edge as ', rootId, edge.v, edge.w, clusterId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// } else {
|
||||||
|
// log.info('Skipping leaf as root ', rootId, ' !== ', clusterId, ' leaf id = ', node);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// log.info('Removing node', node, graphlib.json.write(graph));
|
||||||
|
log.info('Removing node', node);
|
||||||
|
graph.removeNode(node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// newGraph.setNode(clusterId, graph.node(clusterId));
|
||||||
|
};
|
||||||
|
const copy = (clusterId, graph, newGraph, rootId) => {
|
||||||
|
log.trace(
|
||||||
|
'Copying children of ',
|
||||||
|
clusterId,
|
||||||
|
rootId,
|
||||||
|
' from ',
|
||||||
|
clusterId,
|
||||||
|
graph.node(clusterId),
|
||||||
|
rootId
|
||||||
|
);
|
||||||
|
const nodes = graph.children(clusterId) || [];
|
||||||
|
|
||||||
|
// Include cluster node if it is not the root
|
||||||
|
if (clusterId !== rootId) {
|
||||||
|
nodes.push(clusterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('Copying (nodes)', nodes);
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
if (graph.children(node).length > 0) {
|
if (graph.children(node).length > 0) {
|
||||||
copy(node, graph, newGraph, rootId);
|
copy(node, graph, newGraph, rootId);
|
||||||
}
|
} else {
|
||||||
|
const data = graph.node(node);
|
||||||
const data = graph.node(node);
|
log.trace('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
|
||||||
logger.info(node, data, ' parent is ', clusterId);
|
newGraph.setNode(node, data);
|
||||||
newGraph.setNode(node, data);
|
if (clusterId !== rootId && node !== clusterId) {
|
||||||
newGraph.setParent(node, clusterId);
|
log.info('Setting parent', node, clusterId);
|
||||||
const edges = graph.edges(node);
|
newGraph.setParent(node, clusterId);
|
||||||
logger.info('Copying Edges', edges);
|
|
||||||
edges.forEach(edge => {
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
const edges = graph.edges(node);
|
||||||
|
log.info('Copying Edges', edges);
|
||||||
|
edges.forEach(edge => {
|
||||||
|
log.trace('Edge', edge);
|
||||||
|
const data = graph.edge(edge.v, edge.w, edge.name);
|
||||||
|
log.trace('Edge data', data, rootId);
|
||||||
|
try {
|
||||||
|
// Do not copy edges in and out of the root cluster, they belong to the parent graph
|
||||||
|
if (edgeInCluster(edge, rootId)) {
|
||||||
|
log.trace('Copying as ', edge.v, edge.w, data, edge.name);
|
||||||
|
newGraph.setEdge(edge.v, edge.w, data, edge.name);
|
||||||
|
log.trace('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0]));
|
||||||
|
} else {
|
||||||
|
log.trace('Skipping copy of edge as ', rootId, edge.v, edge.w, clusterId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
log.info('Removing node', node);
|
||||||
graph.removeNode(node);
|
graph.removeNode(node);
|
||||||
});
|
});
|
||||||
newGraph.setNode(clusterId, graph.node(clusterId));
|
|
||||||
};
|
};
|
||||||
|
export const extractDecendants = (id, graph) => {
|
||||||
const extractDecendants = (id, graph) => {
|
// log.info('Extracting ', id);
|
||||||
const children = graph.children(id);
|
const children = graph.children(id);
|
||||||
let res = [].concat(children);
|
let res = [].concat(children);
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
parents[children[i]] = id;
|
||||||
res = res.concat(extractDecendants(children[i], graph));
|
res = res.concat(extractDecendants(children[i], graph));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +154,7 @@ const extractDecendants = (id, graph) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const extractGraphFromCluster = (clusterId, graph) => {
|
export const extractGraphFromCluster = (clusterId, graph) => {
|
||||||
|
log.info('Extracting graph ', clusterId);
|
||||||
const clusterGraph = new graphlib.Graph({
|
const clusterGraph = new graphlib.Graph({
|
||||||
multigraph: true,
|
multigraph: true,
|
||||||
compound: true
|
compound: true
|
||||||
@@ -96,8 +191,25 @@ export const extractGraphFromCluster = (clusterId, graph) => {
|
|||||||
// return {};
|
// return {};
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
log.trace('Extracting before copy', graphlib.json.write(graph));
|
||||||
|
log.trace('Extracting before copy', graphlib.json.write(graph));
|
||||||
copy(clusterId, graph, clusterGraph, clusterId);
|
copy(clusterId, graph, clusterGraph, clusterId);
|
||||||
|
log.trace('Extracting after copy', graphlib.json.write(graph));
|
||||||
|
log.trace('Extracting after copy', clusterGraph.nodes());
|
||||||
|
graphs[clusterId] = clusterGraph;
|
||||||
|
|
||||||
|
// Remove references to extracted cluster
|
||||||
|
// graph.edges().forEach(edge => {
|
||||||
|
// if (isDecendant(edge.v, clusterId) || isDecendant(edge.w, clusterId)) {
|
||||||
|
// graph.removeEdge(edge);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// graph.nodes().forEach(node => {
|
||||||
|
// if (isDecendant(node, clusterId)) {
|
||||||
|
// log.info('Removing ', node, ' from ', clusterId);
|
||||||
|
// graph.removeNode(node);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
return clusterGraph;
|
return clusterGraph;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,14 +220,14 @@ export const extractGraphFromCluster = (clusterId, graph) => {
|
|||||||
*/
|
*/
|
||||||
export const validate = graph => {
|
export const validate = graph => {
|
||||||
const edges = graph.edges();
|
const edges = graph.edges();
|
||||||
logger.trace('Edges: ', edges);
|
log.trace('Edges: ', edges);
|
||||||
for (let i = 0; i < edges.length; i++) {
|
for (let i = 0; i < edges.length; i++) {
|
||||||
if (graph.children(edges[i].v).length > 0) {
|
if (graph.children(edges[i].v).length > 0) {
|
||||||
logger.trace('The node ', edges[i].v, ' is part of and edge even though it has children');
|
log.trace('The node ', edges[i].v, ' is part of and edge even though it has children');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (graph.children(edges[i].w).length > 0) {
|
if (graph.children(edges[i].w).length > 0) {
|
||||||
logger.trace('The node ', edges[i].w, ' is part of and edge even though it has children');
|
log.trace('The node ', edges[i].w, ' is part of and edge even though it has children');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,16 +241,16 @@ export const validate = graph => {
|
|||||||
*/
|
*/
|
||||||
export const findNonClusterChild = (id, graph) => {
|
export const findNonClusterChild = (id, graph) => {
|
||||||
// const node = graph.node(id);
|
// const node = graph.node(id);
|
||||||
logger.trace('Searching', id);
|
log.trace('Searching', id);
|
||||||
const children = graph.children(id);
|
const children = graph.children(id);
|
||||||
if (children.length < 1) {
|
if (children.length < 1) {
|
||||||
logger.trace('This is a valid node', id);
|
log.trace('This is a valid node', id);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const _id = findNonClusterChild(children[i], graph);
|
const _id = findNonClusterChild(children[i], graph);
|
||||||
if (_id) {
|
if (_id) {
|
||||||
logger.trace('Found replacement for', id, ' => ', _id);
|
log.trace('Found replacement for', id, ' => ', _id);
|
||||||
return _id;
|
return _id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,15 +272,19 @@ const getAnchorId = id => {
|
|||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adjustClustersAndEdges = graph => {
|
export const adjustClustersAndEdges = (graph, depth) => {
|
||||||
// calc decendants, sa
|
if (!graph || depth > 10) {
|
||||||
|
log.info('Opting out, no graph ');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log.info('Opting in, graph ');
|
||||||
|
}
|
||||||
// Go through the nodes and for each cluster found, save a replacment node, this can be used when
|
// Go through the nodes and for each cluster found, save a replacment node, this can be used when
|
||||||
// faking a link to a cluster
|
// faking a link to a cluster
|
||||||
graph.nodes().forEach(function(id) {
|
graph.nodes().forEach(function(id) {
|
||||||
const children = graph.children(id);
|
const children = graph.children(id);
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
logger.info(
|
log.trace(
|
||||||
'Cluster identified',
|
'Cluster identified',
|
||||||
id,
|
id,
|
||||||
' Replacement id in edges: ',
|
' Replacement id in edges: ',
|
||||||
@@ -184,13 +300,22 @@ export const adjustClustersAndEdges = graph => {
|
|||||||
const children = graph.children(id);
|
const children = graph.children(id);
|
||||||
const edges = graph.edges();
|
const edges = graph.edges();
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
logger.info('Cluster identified', id);
|
log.info('Cluster identified', id, decendants);
|
||||||
edges.forEach(edge => {
|
edges.forEach(edge => {
|
||||||
logger.info('Edge: ', edge, decendants[id]);
|
// log.info('Edge, decendants: ', edge, decendants[id]);
|
||||||
|
|
||||||
// Check if any edge leaves the cluster (not the actual cluster, thats a link from the box)
|
// 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 (edge.v !== id && edge.w !== id) {
|
||||||
if (decendants[id].indexOf(edge.v) < 0 || decendants[id].indexOf(edge.w) < 0) {
|
// Any edge where either the one of the nodes is decending to the cluster but not the other
|
||||||
logger.info('Edge: ', edge, ' leaves cluster ', id);
|
// if (decendants[id].indexOf(edge.v) < 0 && decendants[id].indexOf(edge.w) < 0) {
|
||||||
|
|
||||||
|
const d1 = isDecendant(edge.v, id);
|
||||||
|
const d2 = isDecendant(edge.w, id);
|
||||||
|
|
||||||
|
// d1 xor d2 - if either d1 is true and d2 is false or the other way around
|
||||||
|
if (d1 ^ d2) {
|
||||||
|
log.info('Edge: ', edge, ' leaves cluster ', id);
|
||||||
|
log.info('Decendants of ', id, ': ', decendants[id]);
|
||||||
clusterDb[id].externalConnections = true;
|
clusterDb[id].externalConnections = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,73 +323,265 @@ export const adjustClustersAndEdges = graph => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// For clusters without incoming and/or outgoing edges, create a new cluster-node
|
extractor(graph, 0);
|
||||||
// containing the nodes and edges in the custer in a new graph
|
|
||||||
// for (let i = 0;)
|
|
||||||
const clusters = Object.keys(clusterDb);
|
|
||||||
// clusters.forEach(clusterId => {
|
|
||||||
for (let i = 0; i < clusters.length; i++) {
|
|
||||||
const clusterId = clusters[i];
|
|
||||||
if (!clusterDb[clusterId].externalConnections) {
|
|
||||||
logger.trace('Cluster without external connections', clusterId);
|
|
||||||
const edges = graph.nodeEdges(clusterId);
|
|
||||||
|
|
||||||
// New graph with the nodes in the cluster
|
|
||||||
const clusterGraph = extractGraphFromCluster(clusterId, graph);
|
|
||||||
logger.trace('Cluster graph', clusterGraph.nodes());
|
|
||||||
logger.trace('Graph', graph.nodes());
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
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 => {
|
|
||||||
logger.trace('Setting edge', edge);
|
|
||||||
const data = graph.edge(edge);
|
|
||||||
graph.setEdge(edge.v, edge.w, data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.trace('Graph', graph.nodes());
|
|
||||||
// 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);
|
||||||
logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||||
logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
|
log.trace('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
|
||||||
logger.trace(
|
log.trace('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
|
||||||
'Fix',
|
|
||||||
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]) {
|
||||||
logger.trace('Fixing and trixing - removing', e.v, e.w, e.name);
|
log.trace('Fixing and trixing - removing', 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;
|
||||||
logger.trace('Replacing with', v, w, e.name);
|
log.trace('Replacing with', v, w, e.name);
|
||||||
graph.setEdge(v, w, edge, e.name);
|
graph.setEdge(v, w, edge, e.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
logger.trace('Graph', graph.nodes());
|
log.info('Adjusted Graph', graphlib.json.write(graph));
|
||||||
|
|
||||||
logger.trace(clusterDb);
|
log.trace(clusterDb);
|
||||||
|
|
||||||
|
// Remove references to extracted cluster
|
||||||
|
// graph.edges().forEach(edge => {
|
||||||
|
// if (isDecendant(edge.v, clusterId) || isDecendant(edge.w, clusterId)) {
|
||||||
|
// graph.removeEdge(edge);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformClustersToNodes = (graph, depth) => {
|
||||||
|
log.info('transformClustersToNodes - ', depth);
|
||||||
|
if (depth > 10) {
|
||||||
|
log.error('Bailing out');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// For clusters without incoming and/or outgoing edges, create a new cluster-node
|
||||||
|
// containing the nodes and edges in the custer in a new graph
|
||||||
|
// for (let i = 0;)
|
||||||
|
const nodes = graph.nodes();
|
||||||
|
let hasChildren = false;
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i];
|
||||||
|
const children = graph.children(node);
|
||||||
|
hasChildren = hasChildren || children.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasChildren) {
|
||||||
|
log.info('Done, no node has children', graph.nodes());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// const clusters = Object.keys(clusterDb);
|
||||||
|
// clusters.forEach(clusterId => {
|
||||||
|
log.info('Nodes = ', nodes);
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i];
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
'Handling node',
|
||||||
|
node,
|
||||||
|
clusterDb,
|
||||||
|
clusterDb[node] && !clusterDb[node].externalConnections,
|
||||||
|
!graph.parent(node),
|
||||||
|
graph.node(node)
|
||||||
|
);
|
||||||
|
// Note that the node might have been removed after the Object.keys call so better check
|
||||||
|
// that it still is in the game
|
||||||
|
if (clusterDb[node]) {
|
||||||
|
if (
|
||||||
|
!clusterDb[node].externalConnections &&
|
||||||
|
!graph.parent(node) &&
|
||||||
|
graph.children(node) &&
|
||||||
|
graph.children(node).length > 0
|
||||||
|
) {
|
||||||
|
log.info('Cluster without external connections', node);
|
||||||
|
// const parentGraph = parent && graphs[parent] ? graphs[parent] : graph;
|
||||||
|
// New graph with the nodes in the cluster
|
||||||
|
log.info('before Extracting ', node, ' parent is ', graph.parent(node));
|
||||||
|
const clusterGraph = extractGraphFromCluster(node, graph);
|
||||||
|
|
||||||
|
if (clusterGraph) {
|
||||||
|
log.trace('Cluster graph', clusterGraph.nodes());
|
||||||
|
log.trace('Graph', graph.edges());
|
||||||
|
|
||||||
|
log.info('Creating node in original', node, clusterGraph);
|
||||||
|
|
||||||
|
// Create a new node in the original graph, this new node is not a cluster
|
||||||
|
// but a regular node with the cluster content as a new attached graph
|
||||||
|
graph.setNode(node, {
|
||||||
|
clusterNode: true,
|
||||||
|
id: node,
|
||||||
|
clusterData: clusterDb[node],
|
||||||
|
labelText: clusterDb[node].labelText,
|
||||||
|
graph: clusterGraph
|
||||||
|
});
|
||||||
|
|
||||||
|
// if any node in the clusterGraph still has children
|
||||||
|
transformClustersToNodes(clusterGraph, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original edges in and out of the cluster is applied
|
||||||
|
// edges.forEach(edge => {
|
||||||
|
// log.info('Setting edge', edge);
|
||||||
|
// const data = graph.edge(edge);
|
||||||
|
// graph.setEdge(edge.v, edge.w, data);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info('Not a cluster ', node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractor = (graph, depth) => {
|
||||||
|
log.info('extractor - ', depth, graphlib.json.write(graph), graph.children('D'));
|
||||||
|
if (depth > 10) {
|
||||||
|
log.error('Bailing out');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// For clusters without incoming and/or outgoing edges, create a new cluster-node
|
||||||
|
// containing the nodes and edges in the custer in a new graph
|
||||||
|
// for (let i = 0;)
|
||||||
|
let nodes = graph.nodes();
|
||||||
|
let hasChildren = false;
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i];
|
||||||
|
const children = graph.children(node);
|
||||||
|
hasChildren = hasChildren || children.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasChildren) {
|
||||||
|
log.info('Done, no node has children', graph.nodes());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// const clusters = Object.keys(clusterDb);
|
||||||
|
// clusters.forEach(clusterId => {
|
||||||
|
log.info('Nodes = ', nodes, depth);
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i];
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
'Extracting node',
|
||||||
|
node,
|
||||||
|
clusterDb,
|
||||||
|
clusterDb[node] && !clusterDb[node].externalConnections,
|
||||||
|
!graph.parent(node),
|
||||||
|
graph.node(node),
|
||||||
|
graph.children('D'),
|
||||||
|
' Depth ',
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
// Note that the node might have been removed after the Object.keys call so better check
|
||||||
|
// that it still is in the game
|
||||||
|
if (!clusterDb[node]) {
|
||||||
|
// Skip if the node is not a cluster
|
||||||
|
log.info('Not a cluster', node, depth);
|
||||||
|
// break;
|
||||||
|
} else if (
|
||||||
|
!clusterDb[node].externalConnections &&
|
||||||
|
!graph.parent(node) &&
|
||||||
|
graph.children(node) &&
|
||||||
|
graph.children(node).length > 0
|
||||||
|
) {
|
||||||
|
log.info(
|
||||||
|
'Cluster without external connections, without a parent and with children',
|
||||||
|
node,
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
|
||||||
|
const clusterGraph = new graphlib.Graph({
|
||||||
|
multigraph: true,
|
||||||
|
compound: true
|
||||||
|
})
|
||||||
|
.setGraph({
|
||||||
|
rankdir: 'TB',
|
||||||
|
// Todo: set proper spacing
|
||||||
|
nodesep: 50,
|
||||||
|
ranksep: 50,
|
||||||
|
marginx: 8,
|
||||||
|
marginy: 8
|
||||||
|
})
|
||||||
|
.setDefaultEdgeLabel(function() {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
copy(node, graph, clusterGraph, node);
|
||||||
|
graph.setNode(node, {
|
||||||
|
clusterNode: true,
|
||||||
|
id: node,
|
||||||
|
clusterData: clusterDb[node],
|
||||||
|
labelText: clusterDb[node].labelText,
|
||||||
|
graph: clusterGraph
|
||||||
|
});
|
||||||
|
log.info('New graph after copy', graphlib.json.write(clusterGraph));
|
||||||
|
log.info('Old graph after copy', graphlib.json.write(graph));
|
||||||
|
|
||||||
|
/*
|
||||||
|
// New graph with the nodes in the cluster
|
||||||
|
log.info('before Extracting ', node, ' parent is ', graph.parent(node));
|
||||||
|
const clusterGraph = extractGraphFromCluster(node, graph);
|
||||||
|
|
||||||
|
if (clusterGraph) {
|
||||||
|
log.trace('Cluster graph', clusterGraph.nodes());
|
||||||
|
log.trace('Graph', graph.edges());
|
||||||
|
|
||||||
|
log.info('Creating node in original', node, clusterGraph);
|
||||||
|
|
||||||
|
// Create a new node in the original graph, this new node is not a cluster
|
||||||
|
// but a regular node with the cluster content as a new attached graph
|
||||||
|
graph.setNode(node, {
|
||||||
|
clusterNode: true,
|
||||||
|
id: node,
|
||||||
|
clusterData: clusterDb[node],
|
||||||
|
labelText: clusterDb[node].labelText,
|
||||||
|
graph: clusterGraph
|
||||||
|
});
|
||||||
|
|
||||||
|
// if any node in the clusterGraph still has children
|
||||||
|
transformClustersToNodes(clusterGraph, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original edges in and out of the cluster is applied
|
||||||
|
// edges.forEach(edge => {
|
||||||
|
// log.info('Setting edge', edge);
|
||||||
|
// const data = graph.edge(edge);
|
||||||
|
// graph.setEdge(edge.v, edge.w, data);
|
||||||
|
// });
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
'Cluster ** ',
|
||||||
|
node,
|
||||||
|
' **not meeting the criteria !externalConnections:',
|
||||||
|
!clusterDb[node].externalConnections,
|
||||||
|
' no parent: ',
|
||||||
|
!graph.parent(node),
|
||||||
|
' children ',
|
||||||
|
graph.children(node) && graph.children(node).length > 0,
|
||||||
|
graph.children('D'),
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
log.info(clusterDb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = graph.nodes();
|
||||||
|
log.info('New list of nodes', nodes);
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i];
|
||||||
|
const data = graph.node(node);
|
||||||
|
log.info(' Now next leveö', node, data);
|
||||||
|
if (data.clusterNode) {
|
||||||
|
extractor(data.graph, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import graphlib from 'graphlib';
|
import graphlib from 'graphlib';
|
||||||
import dagre from 'dagre';
|
import dagre from 'dagre';
|
||||||
import { validate, adjustClustersAndEdges, extractGraphFromCluster } from './mermaid-graphlib';
|
import { validate, adjustClustersAndEdges, extractGraphFromCluster, extractDecendants } from './mermaid-graphlib';
|
||||||
import { setLogLevel, logger } from '../logger';
|
import { setLogLevel, logger } from '../logger';
|
||||||
|
|
||||||
describe('Graphlib decorations', () => {
|
describe('Graphlib decorations', () => {
|
||||||
@@ -21,40 +21,6 @@ describe('Graphlib decorations', () => {
|
|||||||
g.setDefaultEdgeLabel(function () {
|
g.setDefaultEdgeLabel(function () {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
// // Add node 'a' to the graph with no label
|
|
||||||
// g.setNode('a');
|
|
||||||
|
|
||||||
// // Add node 'b' to the graph with a String label
|
|
||||||
// g.setNode('b', 'b's value');
|
|
||||||
|
|
||||||
// // Add node 'c' to the graph with an Object label
|
|
||||||
// g.setNode('c', { k: 123 });
|
|
||||||
|
|
||||||
// // What nodes are in the graph?
|
|
||||||
// g.nodes();
|
|
||||||
// // => `[ 'a', 'b', 'c' ]`
|
|
||||||
|
|
||||||
// // Add a directed edge from 'a' to 'b', but assign no label
|
|
||||||
// g.setEdge('a', 'b');
|
|
||||||
|
|
||||||
// // Add a directed edge from 'c' to 'd' with an Object label.
|
|
||||||
// // Since 'd' did not exist prior to this call it is automatically
|
|
||||||
// // created with an undefined label.
|
|
||||||
// g.setEdge('c', 'd', { k: 456 });
|
|
||||||
|
|
||||||
// // What edges are in the graph?
|
|
||||||
// g.edges();
|
|
||||||
// // => `[ { v: 'a', w: 'b' },
|
|
||||||
// // { v: 'c', w: 'd' } ]`.
|
|
||||||
|
|
||||||
// // Which edges leave node 'a'?
|
|
||||||
// g.outEdges('a');
|
|
||||||
// // => `[ { v: 'a', w: 'b' } ]`
|
|
||||||
|
|
||||||
// // Which edges enter and leave node 'd'?
|
|
||||||
// g.nodeEdges('d');
|
|
||||||
// // => `[ { v: 'c', w: 'd' } ]`
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validate', function () {
|
describe('validate', function () {
|
||||||
@@ -135,71 +101,6 @@ describe('Graphlib decorations', () => {
|
|||||||
expect(newGraph.edges('a')).toEqual([{ v: 'a', w: 'b' }]);
|
expect(newGraph.edges('a')).toEqual([{ v: 'a', w: 'b' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('It is possible to extract a clusters to a new graph 2 GLB1', function () {
|
|
||||||
/*
|
|
||||||
subgraph C1
|
|
||||||
a --> b
|
|
||||||
end
|
|
||||||
subgraph C2
|
|
||||||
c
|
|
||||||
end
|
|
||||||
C1 --> C2
|
|
||||||
*/
|
|
||||||
g.setNode('a', { data: 1 });
|
|
||||||
g.setNode('b', { data: 2 });
|
|
||||||
g.setNode('c', { data: 3 });
|
|
||||||
g.setNode('c', { data: 3 });
|
|
||||||
g.setParent('a', 'C1');
|
|
||||||
g.setParent('b', 'C1');
|
|
||||||
g.setParent('c', 'C2');
|
|
||||||
g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
|
||||||
g.setEdge('C1', 'C2', { name: 'C1-external-link' });
|
|
||||||
|
|
||||||
const C1 = extractGraphFromCluster('C1', g);
|
|
||||||
const C2 = extractGraphFromCluster('C2', g);
|
|
||||||
|
|
||||||
expect(g.nodes()).toEqual(['C1', 'C2']);
|
|
||||||
expect(g.children('C1')).toEqual([]);
|
|
||||||
expect(g.children('C2')).toEqual([]);
|
|
||||||
expect(g.edges()).toEqual([{ v: 'C1', w: 'C2' }]);
|
|
||||||
|
|
||||||
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' }]);
|
|
||||||
|
|
||||||
expect(C2.nodes()).toEqual(['c', 'C2']);
|
|
||||||
expect(C2.edges()).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('It is possible to extract a cluster from a graph so that the nodes are removed from original graph', function () {
|
|
||||||
/*
|
|
||||||
a --> b
|
|
||||||
subgraph C1
|
|
||||||
subgraph C2
|
|
||||||
a
|
|
||||||
end
|
|
||||||
b
|
|
||||||
end
|
|
||||||
C1 --> c
|
|
||||||
*/
|
|
||||||
g.setNode('a', { data: 1 });
|
|
||||||
g.setNode('b', { data: 2 });
|
|
||||||
g.setNode('c', { data: 3 });
|
|
||||||
g.setParent('a', 'C2');
|
|
||||||
g.setParent('b', 'C1');
|
|
||||||
g.setParent('C2', 'C1');
|
|
||||||
g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
|
||||||
g.setEdge('C1', 'c', { name: 'C1-external-link' });
|
|
||||||
|
|
||||||
const newGraph = extractGraphFromCluster('C1', g);
|
|
||||||
logger.info(g.nodes());
|
|
||||||
expect(g.nodes()).toEqual(['c','C1']);
|
|
||||||
expect(g.edges().length).toBe(1);
|
|
||||||
expect(g.children()).toEqual(['c','C1']);
|
|
||||||
expect(g.children('C1')).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('Validate should detect edges between clusters and transform clusters GLB4', function () {
|
it('Validate should detect edges between clusters and transform clusters GLB4', function () {
|
||||||
/*
|
/*
|
||||||
a --> b
|
a --> b
|
||||||
@@ -214,6 +115,8 @@ describe('Graphlib decorations', () => {
|
|||||||
g.setNode('a', { data: 1 });
|
g.setNode('a', { data: 1 });
|
||||||
g.setNode('b', { data: 2 });
|
g.setNode('b', { data: 2 });
|
||||||
g.setNode('c', { data: 3 });
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setNode('C1', { data: 4 });
|
||||||
|
g.setNode('C2', { data: 5 });
|
||||||
g.setParent('a', 'C2');
|
g.setParent('a', 'C2');
|
||||||
g.setParent('b', 'C1');
|
g.setParent('b', 'C1');
|
||||||
g.setParent('C2', 'C1');
|
g.setParent('C2', 'C1');
|
||||||
@@ -240,7 +143,6 @@ describe('Graphlib decorations', () => {
|
|||||||
g.setNode('b', { data: 2 });
|
g.setNode('b', { data: 2 });
|
||||||
g.setParent('a', 'C1');
|
g.setParent('a', 'C1');
|
||||||
g.setParent('b', 'C2');
|
g.setParent('b', 'C2');
|
||||||
g.setParent('C1', 'C2');
|
|
||||||
// g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
// g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
||||||
g.setEdge('C1', 'C2', { name: 'C1-external-link' });
|
g.setEdge('C1', 'C2', { name: 'C1-external-link' });
|
||||||
|
|
||||||
@@ -250,4 +152,248 @@ describe('Graphlib decorations', () => {
|
|||||||
expect(g.nodes().length).toBe(2);
|
expect(g.nodes().length).toBe(2);
|
||||||
expect(validate(g)).toBe(true);
|
expect(validate(g)).toBe(true);
|
||||||
});
|
});
|
||||||
|
it('adjustClustersAndEdges GLB6', function () {
|
||||||
|
/*
|
||||||
|
subgraph C1
|
||||||
|
a
|
||||||
|
end
|
||||||
|
C1 --> b
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('C1', { data: 3 });
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setEdge('C1', 'b', { data: 'link1' }, '1');
|
||||||
|
|
||||||
|
// logger.info(g.edges())
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
logger.info(g.edges())
|
||||||
|
expect(g.nodes()).toEqual(['b', 'C1']);
|
||||||
|
expect(g.edges().length).toBe(1);
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
expect(g.node('C1').clusterNode).toBe(true);
|
||||||
|
|
||||||
|
const C1Graph = g.node('C1').graph;
|
||||||
|
expect(C1Graph.nodes()).toEqual(['a']);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges GLB7', function () {
|
||||||
|
/*
|
||||||
|
subgraph C1
|
||||||
|
a
|
||||||
|
end
|
||||||
|
C1 --> b
|
||||||
|
C1 --> c
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setNode('C1', { data: 4 });
|
||||||
|
g.setEdge('C1', 'b', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('C1', 'c', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
logger.info(g.node('C1'))
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
logger.info(g.edges())
|
||||||
|
expect(g.nodes()).toEqual(['b', 'c', 'C1']);
|
||||||
|
expect(g.nodes().length).toBe(3);
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
const edgeData = g.edge(g.edges()[1]);
|
||||||
|
expect(edgeData.data).toBe('link2');
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
|
||||||
|
const C1Graph = g.node('C1').graph;
|
||||||
|
expect(C1Graph.nodes()).toEqual(['a']);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges GLB8', function () {
|
||||||
|
/*
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
c
|
||||||
|
end
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('c', 'C');
|
||||||
|
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
// logger.info(g.edges())
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
expect(g.nodes()).toEqual(['A', 'B', 'C']);
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
const edgeData = g.edge(g.edges()[1]);
|
||||||
|
expect(edgeData.data).toBe('link2');
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
|
||||||
|
const CGraph = g.node('C').graph;
|
||||||
|
expect(CGraph.nodes()).toEqual(['c']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB10', function () {
|
||||||
|
/*
|
||||||
|
subgraph C
|
||||||
|
subgraph D
|
||||||
|
d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
|
||||||
|
g.setNode('C', { data: 1 });
|
||||||
|
g.setNode('D', { data: 2 });
|
||||||
|
g.setNode('d', { data: 3 });
|
||||||
|
g.setParent('d', 'D');
|
||||||
|
g.setParent('D', 'C');
|
||||||
|
|
||||||
|
// logger.info('Graph before', g.node('D'))
|
||||||
|
// logger.info('Graph before', graphlib.json.write(g))
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
// logger.info('Graph after', graphlib.json.write(g), g.node('C').graph)
|
||||||
|
|
||||||
|
const CGraph = g.node('C').graph;
|
||||||
|
const DGraph = CGraph.node('D').graph;
|
||||||
|
|
||||||
|
expect(CGraph.nodes()).toEqual(['D']);
|
||||||
|
expect(DGraph.nodes()).toEqual(['d']);
|
||||||
|
|
||||||
|
expect(g.nodes()).toEqual(['C']);
|
||||||
|
expect(g.nodes().length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB11', function () {
|
||||||
|
/*
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
subgraph D
|
||||||
|
d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
*/
|
||||||
|
|
||||||
|
g.setNode('C', { data: 1 });
|
||||||
|
g.setNode('D', { data: 2 });
|
||||||
|
g.setNode('d', { data: 3 });
|
||||||
|
g.setNode('B', { data: 4 });
|
||||||
|
g.setNode('b', { data: 5 });
|
||||||
|
g.setNode('A', { data: 6 });
|
||||||
|
g.setNode('a', { data: 7 });
|
||||||
|
g.setParent('a', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('d', 'D');
|
||||||
|
g.setParent('D', 'C');
|
||||||
|
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
logger.info('Graph before', g.node('D'))
|
||||||
|
logger.info('Graph before', graphlib.json.write(g))
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
logger.trace('Graph after', graphlib.json.write(g))
|
||||||
|
expect(g.nodes()).toEqual(['C', 'B', 'A']);
|
||||||
|
expect(g.nodes().length).toBe(3);
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
const AGraph = g.node('A').graph;
|
||||||
|
const BGraph = g.node('B').graph;
|
||||||
|
const CGraph = g.node('C').graph;
|
||||||
|
// logger.info(CGraph.nodes());
|
||||||
|
const DGraph = CGraph.node('D').graph;
|
||||||
|
// logger.info('DG', CGraph.children('D'));
|
||||||
|
|
||||||
|
logger.info('A', AGraph.nodes());
|
||||||
|
expect(AGraph.nodes().length).toBe(1);
|
||||||
|
expect(AGraph.nodes()).toEqual(['a']);
|
||||||
|
logger.trace('Nodes', BGraph.nodes())
|
||||||
|
expect(BGraph.nodes().length).toBe(1);
|
||||||
|
expect(BGraph.nodes()).toEqual(['b']);
|
||||||
|
expect(CGraph.nodes()).toEqual(['D']);
|
||||||
|
expect(CGraph.nodes().length).toEqual(1);
|
||||||
|
|
||||||
|
expect(AGraph.edges().length).toBe(0);
|
||||||
|
expect(BGraph.edges().length).toBe(0);
|
||||||
|
expect(CGraph.edges().length).toBe(0);
|
||||||
|
expect(DGraph.nodes()).toEqual(['d']);
|
||||||
|
expect(DGraph.edges().length).toBe(0);
|
||||||
|
// expect(CGraph.node('D')).toEqual({ data: 2 });
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
// expect(g.edges().length).toBe(2);
|
||||||
|
// const edgeData = g.edge(g.edges()[1]);
|
||||||
|
// expect(edgeData.data).toBe('link2');
|
||||||
|
// expect(validate(g)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('extractDecendants', 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('Simple case of one level decendants GLB9', function () {
|
||||||
|
/*
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
c
|
||||||
|
end
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('c', 'C');
|
||||||
|
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
// logger.info(g.edges())
|
||||||
|
const d1 = extractDecendants('A',g)
|
||||||
|
const d2 = extractDecendants('B',g)
|
||||||
|
const d3 = extractDecendants('C',g)
|
||||||
|
|
||||||
|
expect(d1).toEqual(['a']);
|
||||||
|
expect(d2).toEqual(['b']);
|
||||||
|
expect(d3).toEqual(['c']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -396,5 +396,7 @@ export const clear = () => {
|
|||||||
|
|
||||||
export const positionNode = node => {
|
export const positionNode = node => {
|
||||||
const el = nodeElems[node.id];
|
const el = nodeElems[node.id];
|
||||||
|
logger.debug('Transforming node', node);
|
||||||
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||||
|
// el.attr('transform', 'translate(' + node.x / 2 + ', ' + 0 + ')');
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import graphlib from 'graphlib';
|
import graphlib from 'graphlib';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import dagre from 'dagre';
|
|
||||||
|
|
||||||
import flowDb from './flowDb';
|
import flowDb from './flowDb';
|
||||||
import flow from './parser/flow';
|
import flow from './parser/flow';
|
||||||
|
Reference in New Issue
Block a user