mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-14 04:49:44 +02:00
#1295 Edges between subgraphs
This commit is contained in:
@@ -14,6 +14,9 @@
|
||||
.arrowheadPath {fill: red;}
|
||||
|
||||
.edgePath .path {stroke: red;}
|
||||
.mermaid2 {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -33,14 +36,72 @@
|
||||
C --> D
|
||||
C --> D
|
||||
</div>
|
||||
<div class="mermaid" style="width: 100%; height: 100%">
|
||||
flowchart LR
|
||||
G-->H
|
||||
G-->H
|
||||
<div class="mermaid2" style="width: 100%; height: 100%; display: none">
|
||||
graph LR
|
||||
a --> b
|
||||
|
||||
subgraph id1 [Test]
|
||||
H
|
||||
a --apa--> c
|
||||
b
|
||||
c-->b
|
||||
b-->H
|
||||
end
|
||||
G-->H
|
||||
G-->c
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart TB
|
||||
a --> b
|
||||
|
||||
subgraph id1 [Test]
|
||||
a --apa--> c
|
||||
b
|
||||
c-->b
|
||||
b-->H
|
||||
end
|
||||
G-->H
|
||||
G-->id1
|
||||
id1 --> I
|
||||
I --> G
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart RL
|
||||
a --> b
|
||||
|
||||
subgraph id1 [Test]
|
||||
a --apa--> c
|
||||
b
|
||||
c-->b
|
||||
b-->H
|
||||
end
|
||||
G-->H
|
||||
G-->id1
|
||||
id1 --> I
|
||||
I --> G
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart RL
|
||||
|
||||
subgraph id1 [Test]
|
||||
a
|
||||
end
|
||||
b-->id1
|
||||
</div>
|
||||
<div class="mermaid" style="width: 100%; height: 100%">
|
||||
flowchart RL
|
||||
|
||||
subgraph id1 [Test1]
|
||||
a
|
||||
end
|
||||
subgraph id2 [Test2]
|
||||
b
|
||||
end
|
||||
a --> id2
|
||||
a --> b
|
||||
b-->id1
|
||||
id1 --> id2
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="./mermaid.js"></script>
|
||||
<script>
|
||||
@@ -53,7 +114,8 @@
|
||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
fontFamily: '"arial", sans-serif'
|
||||
fontFamily: '"arial", sans-serif',
|
||||
curve: 'linear',
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
@@ -32,7 +32,7 @@ const rect = (parent, node) => {
|
||||
.attr('width', node.width + padding)
|
||||
.attr('height', node.height + padding);
|
||||
|
||||
logger.info('bbox', bbox.width, node.x, node.width);
|
||||
// logger.info('bbox', bbox.width, node.x, node.width);
|
||||
// Center the label
|
||||
// label.attr('transform', 'translate(' + adj + ', ' + (node.y - node.height / 2) + ')');
|
||||
label.attr(
|
||||
@@ -57,7 +57,7 @@ const rect = (parent, node) => {
|
||||
|
||||
const shapes = { rect };
|
||||
|
||||
const clusterElems = {};
|
||||
let clusterElems = {};
|
||||
|
||||
export const insertCluster = (elem, node) => {
|
||||
clusterElems[node.id] = shapes[node.shape](elem, node);
|
||||
@@ -70,6 +70,10 @@ export const getClusterTitleWidth = (elem, node) => {
|
||||
return width;
|
||||
};
|
||||
|
||||
export const clear = () => {
|
||||
clusterElems = {};
|
||||
};
|
||||
|
||||
export const positionCluster = node => {
|
||||
const el = clusterElems[node.id];
|
||||
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||
|
@@ -1,9 +1,14 @@
|
||||
import { logger } from '../logger'; // eslint-disable-line
|
||||
import createLabel from './createLabel';
|
||||
import * as d3 from 'd3';
|
||||
import inter from './intersect/index.js';
|
||||
import { getConfig } from '../config';
|
||||
|
||||
const edgeLabels = {};
|
||||
let edgeLabels = {};
|
||||
|
||||
export const clear = () => {
|
||||
edgeLabels = {};
|
||||
};
|
||||
|
||||
export const insertEdgeLabel = (elem, edge) => {
|
||||
// Create the actual text element
|
||||
@@ -30,7 +35,6 @@ export const insertEdgeLabel = (elem, edge) => {
|
||||
|
||||
export const positionEdgeLabel = edge => {
|
||||
const el = edgeLabels[edge.id];
|
||||
logger.info(edge.id, el);
|
||||
el.attr('transform', 'translate(' + edge.x + ', ' + edge.y + ')');
|
||||
};
|
||||
|
||||
@@ -47,9 +51,128 @@ export const positionEdgeLabel = edge => {
|
||||
// }
|
||||
// };
|
||||
|
||||
export const insertEdge = function(elem, edge) {
|
||||
const outsideNode = (node, point) => {
|
||||
const x = node.x;
|
||||
const y = node.y;
|
||||
const dx = Math.abs(point.x - x);
|
||||
const dy = Math.abs(point.y - y);
|
||||
const w = node.width / 2;
|
||||
const h = node.height / 2;
|
||||
if (dx > w || dy > h) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// const intersection = (node, outsidePoint, insidePoint) => {
|
||||
// const x = node.x;
|
||||
// const y = node.y;
|
||||
|
||||
// const dx = Math.abs(x - insidePoint.x);
|
||||
// const w = node.width / 2;
|
||||
// let r = w - dx;
|
||||
// const dy = Math.abs(y - insidePoint.y);
|
||||
// const h = node.height / 2;
|
||||
// const q = h - dy;
|
||||
|
||||
// const Q = Math.abs(outsidePoint.y - insidePoint.y);
|
||||
// const R = Math.abs(outsidePoint.x - insidePoint.x);
|
||||
// r = (R * q) / Q;
|
||||
|
||||
// return { x: insidePoint.x + r, y: insidePoint.y + q };
|
||||
// };
|
||||
const intersection = (node, outsidePoint, insidePoint) => {
|
||||
const x = node.x;
|
||||
const y = node.y;
|
||||
|
||||
const dx = Math.abs(x - insidePoint.x);
|
||||
const w = node.width / 2;
|
||||
let r = w - dx;
|
||||
const dy = Math.abs(y - insidePoint.y);
|
||||
const h = node.height / 2;
|
||||
let q = h - dy;
|
||||
|
||||
logger.info('q och r', q, r);
|
||||
|
||||
const Q = Math.abs(outsidePoint.y - insidePoint.y);
|
||||
const R = Math.abs(outsidePoint.x - insidePoint.x);
|
||||
// if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h || false) { // eslint-disable-line
|
||||
// // Intersection is top or bottom of rect.
|
||||
|
||||
// r = (R * q) / Q;
|
||||
|
||||
// return {
|
||||
// x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - r,
|
||||
// y: insidePoint.y + q
|
||||
// };
|
||||
// } else {
|
||||
q = (Q * r) / R;
|
||||
|
||||
return {
|
||||
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - r,
|
||||
y: insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q
|
||||
};
|
||||
// }
|
||||
};
|
||||
|
||||
export const insertEdge = function(elem, edge, clusterDb) {
|
||||
let points = edge.points;
|
||||
if (edge.toCluster) {
|
||||
logger.trace('edge', edge);
|
||||
logger.trace('cluster', clusterDb[edge.toCluster]);
|
||||
points = [];
|
||||
let lastPointOutside;
|
||||
let isInside = false;
|
||||
edge.points.forEach(point => {
|
||||
const node = clusterDb[edge.toCluster].node;
|
||||
|
||||
if (!outsideNode(node, point) && !isInside) {
|
||||
logger.info('inside', edge.toCluster, point);
|
||||
|
||||
// First point inside the rect
|
||||
const insterection = intersection(node, lastPointOutside, point);
|
||||
logger.info('intersect', inter.rect(node, lastPointOutside));
|
||||
points.push(insterection);
|
||||
// points.push(insterection);
|
||||
isInside = true;
|
||||
} else {
|
||||
if (!isInside) points.push(point);
|
||||
}
|
||||
lastPointOutside = point;
|
||||
});
|
||||
}
|
||||
|
||||
if (edge.fromCluster) {
|
||||
logger.info('edge', edge);
|
||||
logger.info('cluster', clusterDb[edge.toCluster]);
|
||||
const updatedPoints = [];
|
||||
let lastPointOutside;
|
||||
let isInside = false;
|
||||
for (let i = points.length - 1; i >= 0; i--) {
|
||||
const point = points[i];
|
||||
const node = clusterDb[edge.fromCluster].node;
|
||||
|
||||
if (!outsideNode(node, point) && !isInside) {
|
||||
logger.info('inside', edge.toCluster, point);
|
||||
|
||||
// First point inside the rect
|
||||
const insterection = intersection(node, lastPointOutside, point);
|
||||
logger.info('intersect', inter.rect(node, lastPointOutside));
|
||||
updatedPoints.unshift(insterection);
|
||||
// points.push(insterection);
|
||||
isInside = true;
|
||||
} else {
|
||||
if (!isInside) updatedPoints.unshift(point);
|
||||
}
|
||||
lastPointOutside = point;
|
||||
}
|
||||
points = updatedPoints;
|
||||
}
|
||||
|
||||
logger.info('Points', points);
|
||||
|
||||
// The data for our line
|
||||
const lineData = edge.points.filter(p => !Number.isNaN(p.y));
|
||||
const lineData = points.filter(p => !Number.isNaN(p.y));
|
||||
|
||||
// This is the accessor function we talked about above
|
||||
const lineFunction = d3
|
||||
@@ -59,14 +182,25 @@ export const insertEdge = function(elem, edge) {
|
||||
})
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.curve(d3.curveBasis);
|
||||
});
|
||||
// .curve(d3.curveBasis);
|
||||
|
||||
const svgPath = elem
|
||||
.append('path')
|
||||
.attr('d', lineFunction(lineData))
|
||||
.attr('id', edge.id)
|
||||
.attr('class', 'transition');
|
||||
|
||||
// edge.points.forEach(point => {
|
||||
// elem
|
||||
// .append('circle')
|
||||
// .style('stroke', 'red')
|
||||
// .style('fill', 'red')
|
||||
// .attr('r', 1)
|
||||
// .attr('cx', point.x)
|
||||
// .attr('cy', point.y);
|
||||
// });
|
||||
|
||||
let url = '';
|
||||
if (getConfig().state.arrowMarkerAbsolute) {
|
||||
url =
|
||||
@@ -79,6 +213,6 @@ export const insertEdge = function(elem, edge) {
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + 'extensionEnd' + ')');
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + 'extensionStart' + ')');
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + 'normalEnd' + ')');
|
||||
// svgPath.attr('marker-start', 'url(' + url + '#' + 'normalStart' + ')');
|
||||
};
|
||||
|
@@ -1,12 +1,23 @@
|
||||
import dagre from 'dagre';
|
||||
import insertMarkers from './markers';
|
||||
import { insertNode, positionNode } from './nodes';
|
||||
import { insertCluster } from './clusters';
|
||||
import { insertEdgeLabel, positionEdgeLabel, insertEdge } from './edges';
|
||||
import { insertNode, positionNode, clearNodes } from './nodes';
|
||||
import { insertCluster, clearClusters } from './clusters';
|
||||
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clearEdges } from './edges';
|
||||
import { logger } from '../logger';
|
||||
|
||||
let clusterDb = {};
|
||||
|
||||
const translateClusterId = id => {
|
||||
if (clusterDb[id]) return clusterDb[id].id;
|
||||
return id;
|
||||
};
|
||||
|
||||
export const render = (elem, graph) => {
|
||||
insertMarkers(elem);
|
||||
clusterDb = {};
|
||||
clearNodes();
|
||||
clearEdges();
|
||||
clearClusters();
|
||||
|
||||
const clusters = elem.insert('g').attr('class', 'clusters'); // eslint-disable-line
|
||||
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
|
||||
@@ -17,27 +28,41 @@ export const render = (elem, graph) => {
|
||||
// to the abstract node and is later used by dagre for the layout
|
||||
graph.nodes().forEach(function(v) {
|
||||
const node = graph.node(v);
|
||||
logger.info('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
if (node.type !== 'group') {
|
||||
insertNode(nodes, graph.node(v));
|
||||
} else {
|
||||
// const width = getClusterTitleWidth(clusters, node);
|
||||
// const children = graph.children(v);
|
||||
const children = graph.children(v);
|
||||
logger.info('Cluster identified', node.id, children[0]);
|
||||
// nodes2expand.push({ id: children[0], width });
|
||||
clusterDb[node.id] = { id: children[0] };
|
||||
logger.info('Clusters ', clusterDb);
|
||||
}
|
||||
});
|
||||
|
||||
// nodes2expand.forEach(item => {
|
||||
// const node = graph.node(item.id);
|
||||
// node.width = item.width;
|
||||
// });
|
||||
|
||||
// Inster 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
|
||||
// Edges from/to clusters really points to the first child in the cluster.
|
||||
// TODO: pick optimal child in the cluster to us as link anchor
|
||||
graph.edges().forEach(function(e) {
|
||||
logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
|
||||
insertEdgeLabel(edgeLabels, graph.edge(e));
|
||||
const edge = graph.edge(e);
|
||||
// logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||
// logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
|
||||
const v = translateClusterId(e.v);
|
||||
const w = translateClusterId(e.w);
|
||||
if (v !== e.v || w !== 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;
|
||||
graph.setEdge(v, w, edge, e.name);
|
||||
}
|
||||
insertEdgeLabel(edgeLabels, edge);
|
||||
});
|
||||
|
||||
// graph.edges().forEach(function(e) {
|
||||
// logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||
// });
|
||||
logger.info('#############################################');
|
||||
logger.info('### Layout ###');
|
||||
logger.info('#############################################');
|
||||
@@ -46,11 +71,12 @@ export const render = (elem, graph) => {
|
||||
// Move the nodes to the correct place
|
||||
graph.nodes().forEach(function(v) {
|
||||
const node = graph.node(v);
|
||||
logger.info('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
if (node.type !== 'group') {
|
||||
positionNode(node);
|
||||
} else {
|
||||
insertCluster(clusters, node);
|
||||
clusterDb[node.id].node = node;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,7 +85,7 @@ export const render = (elem, graph) => {
|
||||
const edge = graph.edge(e);
|
||||
logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge));
|
||||
|
||||
insertEdge(edgePaths, edge);
|
||||
insertEdge(edgePaths, edge, clusterDb);
|
||||
positionEdgeLabel(edge);
|
||||
});
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ import ellipse from './intersect-ellipse';
|
||||
import polygon from './intersect-polygon';
|
||||
import rect from './intersect-rect';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
node,
|
||||
circle,
|
||||
ellipse,
|
||||
|
@@ -2,6 +2,7 @@ const intersectRect = (node, point) => {
|
||||
var x = node.x;
|
||||
var y = node.y;
|
||||
|
||||
console.log(node, point);
|
||||
// Rectangle intersection algorithm from:
|
||||
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
|
||||
var dx = point.x - x;
|
||||
|
@@ -101,6 +101,37 @@ const insertMarkers = elem => {
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', 'normalEnd')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||
.attr('class', 'arrowheadPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', 'normalStart')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||
.attr('class', 'arrowheadPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
};
|
||||
|
||||
export default insertMarkers;
|
||||
|
@@ -47,11 +47,14 @@ const rect = (parent, node) => {
|
||||
|
||||
const shapes = { rect };
|
||||
|
||||
const nodeElems = {};
|
||||
let nodeElems = {};
|
||||
|
||||
export const insertNode = (elem, node) => {
|
||||
nodeElems[node.id] = shapes[node.shape](elem, node);
|
||||
};
|
||||
export const clear = () => {
|
||||
nodeElems = {};
|
||||
};
|
||||
|
||||
export const positionNode = node => {
|
||||
const el = nodeElems[node.id];
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as d3 from 'd3';
|
||||
import { logger } from '../../logger';
|
||||
import { logger } from '../../logger'; // eslint-disable-line
|
||||
import utils from '../../utils';
|
||||
import { getConfig } from '../../config';
|
||||
import common from '../common/common';
|
||||
@@ -88,7 +88,7 @@ export const addSingleLink = function(_start, _end, type, linktext) {
|
||||
let end = _end;
|
||||
if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
|
||||
if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
|
||||
logger.info('Got edge...', start, end);
|
||||
// logger.info('Got edge...', start, end);
|
||||
|
||||
const edge = { start: start, end: end, type: undefined, text: '' };
|
||||
linktext = type.text;
|
||||
|
Reference in New Issue
Block a user