mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-02-04 07:13:25 +08:00
#1295 First draft of generic renderer applied to flowcharts.
This commit is contained in:
parent
25e2d78311
commit
aa32d454c9
@ -8,7 +8,7 @@
|
|||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: white;
|
background: white;
|
||||||
font-family: 'Noto Sans SC', sans-serif;
|
font-family: 'Arial';
|
||||||
}
|
}
|
||||||
h1 { color: white;}
|
h1 { color: white;}
|
||||||
.arrowheadPath {fill: red;}
|
.arrowheadPath {fill: red;}
|
||||||
@ -19,9 +19,27 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>info below</h1>
|
<h1>info below</h1>
|
||||||
<div style="display: flex;width: 100%; height: 100%">
|
<div style="display: flex;width: 100%; height: 100%">
|
||||||
<div class="mermaid" style="width: 100%; height: 100%">
|
<div class="mermaid2" style="width: 100%; height: 100%;display: none;">
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A --> B
|
A --> B & C & D
|
||||||
|
subgraph s1
|
||||||
|
B
|
||||||
|
C
|
||||||
|
end
|
||||||
|
subgraph s2
|
||||||
|
D
|
||||||
|
end
|
||||||
|
C --> D
|
||||||
|
C --> D
|
||||||
|
C --> D
|
||||||
|
</div>
|
||||||
|
<div class="mermaid" style="width: 100%; height: 100%">
|
||||||
|
graph TB
|
||||||
|
A[apan klättrar]-- i träd -->B
|
||||||
|
</div>
|
||||||
|
<div class="mermaid" style="width: 100%; height: 100%">
|
||||||
|
flowchart TB
|
||||||
|
A[apan klättrar]-- i träd -->B
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="./mermaid.js"></script>
|
<script src="./mermaid.js"></script>
|
||||||
@ -35,7 +53,7 @@
|
|||||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||||
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
||||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||||
fontFamily: '"Noto Sans SC", sans-serif'
|
fontFamily: '"arial", sans-serif'
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</script>
|
</script>
|
||||||
|
18
src/dagre-wrapper/createLabel.js
Normal file
18
src/dagre-wrapper/createLabel.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const createLabel = (vertexText, style) => {
|
||||||
|
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
|
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
|
||||||
|
|
||||||
|
const rows = vertexText.split(/<br\s*\/?>/gi);
|
||||||
|
|
||||||
|
for (let j = 0; j < rows.length; j++) {
|
||||||
|
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||||
|
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||||
|
tspan.setAttribute('dy', '1em');
|
||||||
|
tspan.setAttribute('x', '1');
|
||||||
|
tspan.textContent = rows[j];
|
||||||
|
svgLabel.appendChild(tspan);
|
||||||
|
}
|
||||||
|
return svgLabel;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createLabel;
|
84
src/dagre-wrapper/edges.js
Normal file
84
src/dagre-wrapper/edges.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { logger } from '../logger'; // eslint-disable-line
|
||||||
|
import createLabel from './createLabel';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
import { getConfig } from '../config';
|
||||||
|
|
||||||
|
const edgeLabels = {};
|
||||||
|
|
||||||
|
export const insertEdgeLabel = (elem, edge) => {
|
||||||
|
// Create the actual text element
|
||||||
|
const labelElement = createLabel(edge.label, edge.labelStyle);
|
||||||
|
|
||||||
|
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||||
|
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
|
||||||
|
|
||||||
|
// Create inner g, label, this will be positioned now for centering the text
|
||||||
|
const label = edgeLabel.insert('g').attr('class', 'label');
|
||||||
|
label.node().appendChild(labelElement);
|
||||||
|
|
||||||
|
// Center the label
|
||||||
|
const bbox = labelElement.getBBox();
|
||||||
|
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
|
||||||
|
// Make element accessible by id for positioning
|
||||||
|
edgeLabels[edge.id] = edgeLabel;
|
||||||
|
|
||||||
|
// Update the abstract data of the edge with the new information about its width and height
|
||||||
|
edge.width = bbox.width;
|
||||||
|
edge.height = bbox.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const positionEdgeLabel = edge => {
|
||||||
|
const el = edgeLabels[edge.id];
|
||||||
|
logger.info(edge.id, el);
|
||||||
|
el.attr('transform', 'translate(' + edge.x + ', ' + edge.y + ')');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRelationType = function(type) {
|
||||||
|
switch (type) {
|
||||||
|
case stateDb.relationType.AGGREGATION:
|
||||||
|
return 'aggregation';
|
||||||
|
case stateDb.relationType.EXTENSION:
|
||||||
|
return 'extension';
|
||||||
|
case stateDb.relationType.COMPOSITION:
|
||||||
|
return 'composition';
|
||||||
|
case stateDb.relationType.DEPENDENCY:
|
||||||
|
return 'dependency';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const insertEdge = function(elem, edge) {
|
||||||
|
// The data for our line
|
||||||
|
const lineData = edge.points.filter(p => !Number.isNaN(p.y));
|
||||||
|
|
||||||
|
// This is the accessor function we talked about above
|
||||||
|
const lineFunction = d3
|
||||||
|
.line()
|
||||||
|
.x(function(d) {
|
||||||
|
return d.x;
|
||||||
|
})
|
||||||
|
.y(function(d) {
|
||||||
|
return d.y;
|
||||||
|
})
|
||||||
|
.curve(d3.curveBasis);
|
||||||
|
|
||||||
|
const svgPath = elem
|
||||||
|
.append('path')
|
||||||
|
.attr('d', lineFunction(lineData))
|
||||||
|
.attr('id', edge.id)
|
||||||
|
.attr('class', 'transition');
|
||||||
|
let url = '';
|
||||||
|
if (getConfig().state.arrowMarkerAbsolute) {
|
||||||
|
url =
|
||||||
|
window.location.protocol +
|
||||||
|
'//' +
|
||||||
|
window.location.host +
|
||||||
|
window.location.pathname +
|
||||||
|
window.location.search;
|
||||||
|
url = url.replace(/\(/g, '\\(');
|
||||||
|
url = url.replace(/\)/g, '\\)');
|
||||||
|
}
|
||||||
|
|
||||||
|
svgPath.attr('marker-end', 'url(' + url + '#' + 'extensionEnd' + ')');
|
||||||
|
svgPath.attr('marker-start', 'url(' + url + '#' + 'extensionStart' + ')');
|
||||||
|
};
|
54
src/dagre-wrapper/index.js
Normal file
54
src/dagre-wrapper/index.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import dagre from 'dagre';
|
||||||
|
import insertMarkers from './markers';
|
||||||
|
import { insertNode, positionNode } from './nodes';
|
||||||
|
import { insertEdgeLabel, positionEdgeLabel, insertEdge } from './edges';
|
||||||
|
import { logger } from '../logger';
|
||||||
|
|
||||||
|
export const render = (elem, graph) => {
|
||||||
|
insertMarkers(elem);
|
||||||
|
|
||||||
|
const clusters = elem.insert('g').attr('class', 'clusters');
|
||||||
|
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
|
||||||
|
const edgeLabels = elem.insert('g').attr('class', 'edgeLabels');
|
||||||
|
const nodes = elem.insert('g').attr('class', 'nodes');
|
||||||
|
|
||||||
|
// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
|
||||||
|
// to the abstract node and is later used by dagre for the layout
|
||||||
|
graph.nodes().forEach(function(v) {
|
||||||
|
logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
|
insertNode(nodes, graph.node(v));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inster labels, this will insert them into the dom so that the width can be calculated
|
||||||
|
graph.edges().forEach(function(e) {
|
||||||
|
logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
|
||||||
|
insertEdgeLabel(edgeLabels, graph.edge(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
dagre.layout(graph);
|
||||||
|
|
||||||
|
// Move the nodes to the correct place
|
||||||
|
graph.nodes().forEach(function(v) {
|
||||||
|
logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
|
positionNode(graph.node(v));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Move the edge labels to the correct place after layout
|
||||||
|
graph.edges().forEach(function(e) {
|
||||||
|
const edge = graph.edge(e);
|
||||||
|
logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge));
|
||||||
|
|
||||||
|
insertEdge(edgePaths, edge);
|
||||||
|
positionEdgeLabel(edge);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// const shapeDefinitions = {};
|
||||||
|
// export const addShape = ({ shapeType: fun }) => {
|
||||||
|
// shapeDefinitions[shapeType] = fun;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const arrowDefinitions = {};
|
||||||
|
// export const addArrow = ({ arrowType: fun }) => {
|
||||||
|
// arrowDefinitions[arrowType] = fun;
|
||||||
|
// };
|
7
src/dagre-wrapper/intersect.js
Normal file
7
src/dagre-wrapper/intersect.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
node: require('./intersect-node'),
|
||||||
|
circle: require('./intersect-circle'),
|
||||||
|
ellipse: require('./intersect-ellipse'),
|
||||||
|
polygon: require('./intersect-polygon'),
|
||||||
|
rect: require('./intersect-rect')
|
||||||
|
};
|
17
src/dagre-wrapper/intersect/index.js
Normal file
17
src/dagre-wrapper/intersect/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Borrowed with love from from dagrge-d3. Many thanks to cpettitt!
|
||||||
|
*/
|
||||||
|
|
||||||
|
import node from './intersect-node';
|
||||||
|
import circle from './intersect-circle';
|
||||||
|
import ellipse from './intersect-ellipse';
|
||||||
|
import polygon from './intersect-polygon';
|
||||||
|
import rect from './intersect-rect';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
node,
|
||||||
|
circle,
|
||||||
|
ellipse,
|
||||||
|
polygon,
|
||||||
|
rect
|
||||||
|
};
|
7
src/dagre-wrapper/intersect/intersect-circle.js
Normal file
7
src/dagre-wrapper/intersect/intersect-circle.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
var intersectEllipse = require("./intersect-ellipse");
|
||||||
|
|
||||||
|
module.exports = intersectCircle;
|
||||||
|
|
||||||
|
function intersectCircle(node, rx, point) {
|
||||||
|
return intersectEllipse(node, rx, rx, point);
|
||||||
|
}
|
25
src/dagre-wrapper/intersect/intersect-ellipse.js
Normal file
25
src/dagre-wrapper/intersect/intersect-ellipse.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
module.exports = intersectEllipse;
|
||||||
|
|
||||||
|
function intersectEllipse(node, rx, ry, point) {
|
||||||
|
// Formulae from: http://mathworld.wolfram.com/Ellipse-LineIntersection.html
|
||||||
|
|
||||||
|
var cx = node.x;
|
||||||
|
var cy = node.y;
|
||||||
|
|
||||||
|
var px = cx - point.x;
|
||||||
|
var py = cy - point.y;
|
||||||
|
|
||||||
|
var det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
|
||||||
|
|
||||||
|
var dx = Math.abs(rx * ry * px / det);
|
||||||
|
if (point.x < cx) {
|
||||||
|
dx = -dx;
|
||||||
|
}
|
||||||
|
var dy = Math.abs(rx * ry * py / det);
|
||||||
|
if (point.y < cy) {
|
||||||
|
dy = -dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {x: cx + dx, y: cy + dy};
|
||||||
|
}
|
||||||
|
|
70
src/dagre-wrapper/intersect/intersect-line.js
Normal file
70
src/dagre-wrapper/intersect/intersect-line.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
module.exports = intersectLine;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the point at which two lines, p and q, intersect or returns
|
||||||
|
* undefined if they do not intersect.
|
||||||
|
*/
|
||||||
|
function intersectLine(p1, p2, q1, q2) {
|
||||||
|
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
|
||||||
|
// p7 and p473.
|
||||||
|
|
||||||
|
var a1, a2, b1, b2, c1, c2;
|
||||||
|
var r1, r2 , r3, r4;
|
||||||
|
var denom, offset, num;
|
||||||
|
var x, y;
|
||||||
|
|
||||||
|
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
|
||||||
|
// b1 y + c1 = 0.
|
||||||
|
a1 = p2.y - p1.y;
|
||||||
|
b1 = p1.x - p2.x;
|
||||||
|
c1 = (p2.x * p1.y) - (p1.x * p2.y);
|
||||||
|
|
||||||
|
// Compute r3 and r4.
|
||||||
|
r3 = ((a1 * q1.x) + (b1 * q1.y) + c1);
|
||||||
|
r4 = ((a1 * q2.x) + (b1 * q2.y) + c1);
|
||||||
|
|
||||||
|
// Check signs of r3 and r4. If both point 3 and point 4 lie on
|
||||||
|
// same side of line 1, the line segments do not intersect.
|
||||||
|
if ((r3 !== 0) && (r4 !== 0) && sameSign(r3, r4)) {
|
||||||
|
return /*DONT_INTERSECT*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
|
||||||
|
a2 = q2.y - q1.y;
|
||||||
|
b2 = q1.x - q2.x;
|
||||||
|
c2 = (q2.x * q1.y) - (q1.x * q2.y);
|
||||||
|
|
||||||
|
// Compute r1 and r2
|
||||||
|
r1 = (a2 * p1.x) + (b2 * p1.y) + c2;
|
||||||
|
r2 = (a2 * p2.x) + (b2 * p2.y) + c2;
|
||||||
|
|
||||||
|
// Check signs of r1 and r2. If both point 1 and point 2 lie
|
||||||
|
// on same side of second line segment, the line segments do
|
||||||
|
// not intersect.
|
||||||
|
if ((r1 !== 0) && (r2 !== 0) && (sameSign(r1, r2))) {
|
||||||
|
return /*DONT_INTERSECT*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line segments intersect: compute intersection point.
|
||||||
|
denom = (a1 * b2) - (a2 * b1);
|
||||||
|
if (denom === 0) {
|
||||||
|
return /*COLLINEAR*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = Math.abs(denom / 2);
|
||||||
|
|
||||||
|
// The denom/2 is to get rounding instead of truncating. It
|
||||||
|
// is added or subtracted to the numerator, depending upon the
|
||||||
|
// sign of the numerator.
|
||||||
|
num = (b1 * c2) - (b2 * c1);
|
||||||
|
x = (num < 0) ? ((num - offset) / denom) : ((num + offset) / denom);
|
||||||
|
|
||||||
|
num = (a2 * c1) - (a1 * c2);
|
||||||
|
y = (num < 0) ? ((num - offset) / denom) : ((num + offset) / denom);
|
||||||
|
|
||||||
|
return { x: x, y: y };
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameSign(r1, r2) {
|
||||||
|
return r1 * r2 > 0;
|
||||||
|
}
|
5
src/dagre-wrapper/intersect/intersect-node.js
Normal file
5
src/dagre-wrapper/intersect/intersect-node.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = intersectNode;
|
||||||
|
|
||||||
|
function intersectNode(node, point) {
|
||||||
|
return node.intersect(point);
|
||||||
|
}
|
57
src/dagre-wrapper/intersect/intersect-polygon.js
Normal file
57
src/dagre-wrapper/intersect/intersect-polygon.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/* eslint "no-console": off */
|
||||||
|
|
||||||
|
var intersectLine = require("./intersect-line");
|
||||||
|
|
||||||
|
module.exports = intersectPolygon;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the point ({x, y}) at which the point argument intersects with the
|
||||||
|
* node argument assuming that it has the shape specified by polygon.
|
||||||
|
*/
|
||||||
|
function intersectPolygon(node, polyPoints, point) {
|
||||||
|
var x1 = node.x;
|
||||||
|
var y1 = node.y;
|
||||||
|
|
||||||
|
var intersections = [];
|
||||||
|
|
||||||
|
var minX = Number.POSITIVE_INFINITY;
|
||||||
|
var minY = Number.POSITIVE_INFINITY;
|
||||||
|
polyPoints.forEach(function(entry) {
|
||||||
|
minX = Math.min(minX, entry.x);
|
||||||
|
minY = Math.min(minY, entry.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
var left = x1 - node.width / 2 - minX;
|
||||||
|
var top = y1 - node.height / 2 - minY;
|
||||||
|
|
||||||
|
for (var i = 0; i < polyPoints.length; i++) {
|
||||||
|
var p1 = polyPoints[i];
|
||||||
|
var p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
|
||||||
|
var intersect = intersectLine(node, point,
|
||||||
|
{x: left + p1.x, y: top + p1.y}, {x: left + p2.x, y: top + p2.y});
|
||||||
|
if (intersect) {
|
||||||
|
intersections.push(intersect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!intersections.length) {
|
||||||
|
console.log("NO INTERSECTION FOUND, RETURN NODE CENTER", node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersections.length > 1) {
|
||||||
|
// More intersections, find the one nearest to edge end point
|
||||||
|
intersections.sort(function(p, q) {
|
||||||
|
var pdx = p.x - point.x;
|
||||||
|
var pdy = p.y - point.y;
|
||||||
|
var distp = Math.sqrt(pdx * pdx + pdy * pdy);
|
||||||
|
|
||||||
|
var qdx = q.x - point.x;
|
||||||
|
var qdy = q.y - point.y;
|
||||||
|
var distq = Math.sqrt(qdx * qdx + qdy * qdy);
|
||||||
|
|
||||||
|
return (distp < distq) ? -1 : (distp === distq ? 0 : 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return intersections[0];
|
||||||
|
}
|
32
src/dagre-wrapper/intersect/intersect-rect.js
Normal file
32
src/dagre-wrapper/intersect/intersect-rect.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const intersectRect = (node, point) => {
|
||||||
|
var x = node.x;
|
||||||
|
var y = node.y;
|
||||||
|
|
||||||
|
// Rectangle intersection algorithm from:
|
||||||
|
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
|
||||||
|
var dx = point.x - x;
|
||||||
|
var dy = point.y - y;
|
||||||
|
var w = node.width / 2;
|
||||||
|
var h = node.height / 2;
|
||||||
|
|
||||||
|
var sx, sy;
|
||||||
|
if (Math.abs(dy) * w > Math.abs(dx) * h) {
|
||||||
|
// Intersection is top or bottom of rect.
|
||||||
|
if (dy < 0) {
|
||||||
|
h = -h;
|
||||||
|
}
|
||||||
|
sx = dy === 0 ? 0 : (h * dx) / dy;
|
||||||
|
sy = h;
|
||||||
|
} else {
|
||||||
|
// Intersection is left or right of rect.
|
||||||
|
if (dx < 0) {
|
||||||
|
w = -w;
|
||||||
|
}
|
||||||
|
sx = w;
|
||||||
|
sy = dx === 0 ? 0 : (w * dy) / dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: x + sx, y: y + sy };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default intersectRect;
|
106
src/dagre-wrapper/markers.js
Normal file
106
src/dagre-wrapper/markers.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||||
|
*/
|
||||||
|
const insertMarkers = elem => {
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'extensionStart')
|
||||||
|
.attr('class', 'extension')
|
||||||
|
.attr('refX', 0)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 190)
|
||||||
|
.attr('markerHeight', 240)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 1,7 L18,13 V 1 Z');
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'extensionEnd')
|
||||||
|
.attr('refX', 19)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 20)
|
||||||
|
.attr('markerHeight', 28)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'compositionStart')
|
||||||
|
.attr('class', 'extension')
|
||||||
|
.attr('refX', 0)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 190)
|
||||||
|
.attr('markerHeight', 240)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'compositionEnd')
|
||||||
|
.attr('refX', 19)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 20)
|
||||||
|
.attr('markerHeight', 28)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'aggregationStart')
|
||||||
|
.attr('class', 'extension')
|
||||||
|
.attr('refX', 0)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 190)
|
||||||
|
.attr('markerHeight', 240)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'aggregationEnd')
|
||||||
|
.attr('refX', 19)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 20)
|
||||||
|
.attr('markerHeight', 28)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'dependencyStart')
|
||||||
|
.attr('class', 'extension')
|
||||||
|
.attr('refX', 0)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 190)
|
||||||
|
.attr('markerHeight', 240)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z');
|
||||||
|
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'dependencyEnd')
|
||||||
|
.attr('refX', 19)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 20)
|
||||||
|
.attr('markerHeight', 28)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default insertMarkers;
|
59
src/dagre-wrapper/nodes.js
Normal file
59
src/dagre-wrapper/nodes.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import intersectRect from './intersect/intersect-rect';
|
||||||
|
import { logger } from '../logger'; // eslint-disable-line
|
||||||
|
import createLabel from './createLabel';
|
||||||
|
|
||||||
|
const rect = (parent, node) => {
|
||||||
|
// Add outer g element
|
||||||
|
const shapeSvg = parent
|
||||||
|
.insert('g')
|
||||||
|
.attr('class', 'node default')
|
||||||
|
.attr('id', node.id);
|
||||||
|
|
||||||
|
// add the rect
|
||||||
|
const rect = shapeSvg.insert('rect', ':first-child');
|
||||||
|
|
||||||
|
// Create the label and insert it after the rect
|
||||||
|
const label = shapeSvg.insert('g').attr('class', 'label');
|
||||||
|
|
||||||
|
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
|
||||||
|
|
||||||
|
// Get the size of the label
|
||||||
|
const bbox = text.getBBox();
|
||||||
|
|
||||||
|
const halfPadding = node.padding / 2;
|
||||||
|
|
||||||
|
// center the rect around its coordinate
|
||||||
|
rect
|
||||||
|
.attr('rx', node.rx)
|
||||||
|
.attr('ry', node.ry)
|
||||||
|
.attr('x', -bbox.width / 2 - halfPadding)
|
||||||
|
.attr('y', -bbox.height / 2 - halfPadding)
|
||||||
|
.attr('width', bbox.width + node.padding)
|
||||||
|
.attr('height', bbox.height + node.padding);
|
||||||
|
|
||||||
|
// Center the label
|
||||||
|
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
|
||||||
|
const rectBox = rect.node().getBBox();
|
||||||
|
node.width = rectBox.width;
|
||||||
|
node.height = rectBox.height;
|
||||||
|
|
||||||
|
node.intersect = function(point) {
|
||||||
|
return intersectRect(node, point);
|
||||||
|
};
|
||||||
|
|
||||||
|
return shapeSvg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shapes = { rect };
|
||||||
|
|
||||||
|
const nodeElems = {};
|
||||||
|
|
||||||
|
export const insertNode = (elem, node) => {
|
||||||
|
nodeElems[node.id] = shapes[node.shape](elem, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const positionNode = node => {
|
||||||
|
const el = nodeElems[node.id];
|
||||||
|
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||||
|
};
|
@ -1,261 +0,0 @@
|
|||||||
import dagreD3 from 'dagre-d3';
|
|
||||||
|
|
||||||
function question(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const s = (w + h) * 0.9;
|
|
||||||
const points = [
|
|
||||||
{ x: s / 2, y: 0 },
|
|
||||||
{ x: s, y: -s / 2 },
|
|
||||||
{ x: s / 2, y: -s },
|
|
||||||
{ x: 0, y: -s / 2 }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, s, s, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hexagon(parent, bbox, node) {
|
|
||||||
const f = 4;
|
|
||||||
const h = bbox.height;
|
|
||||||
const m = h / f;
|
|
||||||
const w = bbox.width + 2 * m;
|
|
||||||
const points = [
|
|
||||||
{ x: m, y: 0 },
|
|
||||||
{ x: w - m, y: 0 },
|
|
||||||
{ x: w, y: -h / 2 },
|
|
||||||
{ x: w - m, y: -h },
|
|
||||||
{ x: m, y: -h },
|
|
||||||
{ x: 0, y: -h / 2 }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rect_left_inv_arrow(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const points = [
|
|
||||||
{ x: -h / 2, y: 0 },
|
|
||||||
{ x: w, y: 0 },
|
|
||||||
{ x: w, y: -h },
|
|
||||||
{ x: -h / 2, y: -h },
|
|
||||||
{ x: 0, y: -h / 2 }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lean_right(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const points = [
|
|
||||||
{ x: (-2 * h) / 6, y: 0 },
|
|
||||||
{ x: w - h / 6, y: 0 },
|
|
||||||
{ x: w + (2 * h) / 6, y: -h },
|
|
||||||
{ x: h / 6, y: -h }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lean_left(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const points = [
|
|
||||||
{ x: (2 * h) / 6, y: 0 },
|
|
||||||
{ x: w + h / 6, y: 0 },
|
|
||||||
{ x: w - (2 * h) / 6, y: -h },
|
|
||||||
{ x: -h / 6, y: -h }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function trapezoid(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const points = [
|
|
||||||
{ x: (-2 * h) / 6, y: 0 },
|
|
||||||
{ x: w + (2 * h) / 6, y: 0 },
|
|
||||||
{ x: w - h / 6, y: -h },
|
|
||||||
{ x: h / 6, y: -h }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inv_trapezoid(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const points = [
|
|
||||||
{ x: h / 6, y: 0 },
|
|
||||||
{ x: w - h / 6, y: 0 },
|
|
||||||
{ x: w + (2 * h) / 6, y: -h },
|
|
||||||
{ x: (-2 * h) / 6, y: -h }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rect_right_inv_arrow(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const h = bbox.height;
|
|
||||||
const points = [
|
|
||||||
{ x: 0, y: 0 },
|
|
||||||
{ x: w + h / 2, y: 0 },
|
|
||||||
{ x: w, y: -h / 2 },
|
|
||||||
{ x: w + h / 2, y: -h },
|
|
||||||
{ x: 0, y: -h }
|
|
||||||
];
|
|
||||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.polygon(node, points, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stadium(parent, bbox, node) {
|
|
||||||
const h = bbox.height;
|
|
||||||
const w = bbox.width + h / 4;
|
|
||||||
|
|
||||||
const shapeSvg = parent
|
|
||||||
.insert('rect', ':first-child')
|
|
||||||
.attr('rx', h / 2)
|
|
||||||
.attr('ry', h / 2)
|
|
||||||
.attr('x', -w / 2)
|
|
||||||
.attr('y', -h / 2)
|
|
||||||
.attr('width', w)
|
|
||||||
.attr('height', h);
|
|
||||||
|
|
||||||
node.intersect = function(point) {
|
|
||||||
return dagreD3.intersect.rect(node, point);
|
|
||||||
};
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cylinder(parent, bbox, node) {
|
|
||||||
const w = bbox.width;
|
|
||||||
const rx = w / 2;
|
|
||||||
const ry = rx / (2.5 + w / 50);
|
|
||||||
const h = bbox.height + ry;
|
|
||||||
|
|
||||||
const shape =
|
|
||||||
'M 0,' +
|
|
||||||
ry +
|
|
||||||
' a ' +
|
|
||||||
rx +
|
|
||||||
',' +
|
|
||||||
ry +
|
|
||||||
' 0,0,0 ' +
|
|
||||||
w +
|
|
||||||
' 0 a ' +
|
|
||||||
rx +
|
|
||||||
',' +
|
|
||||||
ry +
|
|
||||||
' 0,0,0 ' +
|
|
||||||
-w +
|
|
||||||
' 0 l 0,' +
|
|
||||||
h +
|
|
||||||
' a ' +
|
|
||||||
rx +
|
|
||||||
',' +
|
|
||||||
ry +
|
|
||||||
' 0,0,0 ' +
|
|
||||||
w +
|
|
||||||
' 0 l 0,' +
|
|
||||||
-h;
|
|
||||||
|
|
||||||
const shapeSvg = parent
|
|
||||||
.attr('label-offset-y', ry)
|
|
||||||
.insert('path', ':first-child')
|
|
||||||
.attr('d', shape)
|
|
||||||
.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2 + ry) + ')');
|
|
||||||
|
|
||||||
node.intersect = function(point) {
|
|
||||||
const pos = dagreD3.intersect.rect(node, point);
|
|
||||||
const x = pos.x - node.x;
|
|
||||||
|
|
||||||
if (
|
|
||||||
rx != 0 &&
|
|
||||||
(Math.abs(x) < node.width / 2 ||
|
|
||||||
(Math.abs(x) == node.width / 2 && Math.abs(pos.y - node.y) > node.height / 2 - ry))
|
|
||||||
) {
|
|
||||||
// ellipsis equation: x*x / a*a + y*y / b*b = 1
|
|
||||||
// solve for y to get adjustion value for pos.y
|
|
||||||
let y = ry * ry * (1 - (x * x) / (rx * rx));
|
|
||||||
if (y != 0) y = Math.sqrt(y);
|
|
||||||
y = ry - y;
|
|
||||||
if (point.y - node.y > 0) y = -y;
|
|
||||||
|
|
||||||
pos.y += y;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
return shapeSvg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addToRender(render) {
|
|
||||||
render.shapes().question = question;
|
|
||||||
render.shapes().hexagon = hexagon;
|
|
||||||
render.shapes().stadium = stadium;
|
|
||||||
render.shapes().cylinder = cylinder;
|
|
||||||
|
|
||||||
// Add custom shape for box with inverted arrow on left side
|
|
||||||
render.shapes().rect_left_inv_arrow = rect_left_inv_arrow;
|
|
||||||
|
|
||||||
// Add custom shape for box with inverted arrow on left side
|
|
||||||
render.shapes().lean_right = lean_right;
|
|
||||||
|
|
||||||
// Add custom shape for box with inverted arrow on left side
|
|
||||||
render.shapes().lean_left = lean_left;
|
|
||||||
|
|
||||||
// Add custom shape for box with inverted arrow on left side
|
|
||||||
render.shapes().trapezoid = trapezoid;
|
|
||||||
|
|
||||||
// Add custom shape for box with inverted arrow on left side
|
|
||||||
render.shapes().inv_trapezoid = inv_trapezoid;
|
|
||||||
|
|
||||||
// Add custom shape for box with inverted arrow on right side
|
|
||||||
render.shapes().rect_right_inv_arrow = rect_right_inv_arrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertPolygonShape(parent, w, h, points) {
|
|
||||||
return parent
|
|
||||||
.insert('polygon', ':first-child')
|
|
||||||
.attr(
|
|
||||||
'points',
|
|
||||||
points
|
|
||||||
.map(function(d) {
|
|
||||||
return d.x + ',' + d.y;
|
|
||||||
})
|
|
||||||
.join(' ')
|
|
||||||
)
|
|
||||||
.attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
addToRender
|
|
||||||
};
|
|
@ -1,131 +0,0 @@
|
|||||||
import { addToRender } from './flowChartShapes';
|
|
||||||
|
|
||||||
describe('flowchart shapes', function() {
|
|
||||||
// rect-based shapes
|
|
||||||
[
|
|
||||||
['stadium', useWidth, useHeight]
|
|
||||||
].forEach(function([shapeType, getW, getH]) {
|
|
||||||
it(`should add a ${shapeType} shape that renders a properly positioned rect element`, function() {
|
|
||||||
const mockRender = MockRender();
|
|
||||||
const mockSvg = MockSvg();
|
|
||||||
addToRender(mockRender);
|
|
||||||
|
|
||||||
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
|
|
||||||
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
|
|
||||||
const w = width + height / 4;
|
|
||||||
const h = height;
|
|
||||||
const dx = -getW(w, h) / 2;
|
|
||||||
const dy = -getH(w, h) / 2;
|
|
||||||
expect(shape.__tag).toEqual('rect');
|
|
||||||
expect(shape.__attrs).toHaveProperty('x', dx);
|
|
||||||
expect(shape.__attrs).toHaveProperty('y', dy);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// path-based shapes
|
|
||||||
[
|
|
||||||
['cylinder', useWidth, useHeight]
|
|
||||||
].forEach(function([shapeType, getW, getH]) {
|
|
||||||
it(`should add a ${shapeType} shape that renders a properly positioned path element`, function() {
|
|
||||||
const mockRender = MockRender();
|
|
||||||
const mockSvg = MockSvg();
|
|
||||||
addToRender(mockRender);
|
|
||||||
|
|
||||||
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
|
|
||||||
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
|
|
||||||
expect(shape.__tag).toEqual('path');
|
|
||||||
expect(shape.__attrs).toHaveProperty('d');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// polygon-based shapes
|
|
||||||
[
|
|
||||||
[
|
|
||||||
'question',
|
|
||||||
4,
|
|
||||||
function(w, h) {
|
|
||||||
return (w + h) * 0.9;
|
|
||||||
},
|
|
||||||
function(w, h) {
|
|
||||||
return (w + h) * 0.9;
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'hexagon',
|
|
||||||
6,
|
|
||||||
function(w, h) {
|
|
||||||
return w + h / 2;
|
|
||||||
},
|
|
||||||
useHeight
|
|
||||||
],
|
|
||||||
['rect_left_inv_arrow', 5, useWidth, useHeight],
|
|
||||||
['rect_right_inv_arrow', 5, useWidth, useHeight],
|
|
||||||
['lean_right', 4, useWidth, useHeight],
|
|
||||||
['lean_left', 4, useWidth, useHeight],
|
|
||||||
['trapezoid', 4, useWidth, useHeight],
|
|
||||||
['inv_trapezoid', 4, useWidth, useHeight]
|
|
||||||
].forEach(function([shapeType, expectedPointCount, getW, getH]) {
|
|
||||||
it(`should add a ${shapeType} shape that renders a properly translated polygon element`, function() {
|
|
||||||
const mockRender = MockRender();
|
|
||||||
const mockSvg = MockSvg();
|
|
||||||
addToRender(mockRender);
|
|
||||||
|
|
||||||
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
|
|
||||||
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
|
|
||||||
const dx = -getW(width, height) / 2;
|
|
||||||
const dy = getH(width, height) / 2;
|
|
||||||
const points = shape.__attrs.points.split(' ');
|
|
||||||
expect(shape.__tag).toEqual('polygon');
|
|
||||||
expect(shape.__attrs).toHaveProperty('transform', `translate(${dx},${dy})`);
|
|
||||||
expect(points).toHaveLength(expectedPointCount);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function MockRender() {
|
|
||||||
const shapes = {};
|
|
||||||
return {
|
|
||||||
shapes() {
|
|
||||||
return shapes;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function MockSvg(tag, ...args) {
|
|
||||||
const children = [];
|
|
||||||
const attributes = {};
|
|
||||||
return {
|
|
||||||
get __args() {
|
|
||||||
return args;
|
|
||||||
},
|
|
||||||
get __tag() {
|
|
||||||
return tag;
|
|
||||||
},
|
|
||||||
get __children() {
|
|
||||||
return children;
|
|
||||||
},
|
|
||||||
get __attrs() {
|
|
||||||
return attributes;
|
|
||||||
},
|
|
||||||
insert: function(tag, ...args) {
|
|
||||||
const child = MockSvg(tag, ...args);
|
|
||||||
children.push(child);
|
|
||||||
return child;
|
|
||||||
},
|
|
||||||
attr(name, value) {
|
|
||||||
this.__attrs[name] = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function useWidth(w, h) {
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useHeight(w, h) {
|
|
||||||
return h;
|
|
||||||
}
|
|
@ -1,644 +0,0 @@
|
|||||||
import * as d3 from 'd3';
|
|
||||||
import { logger } from '../../logger';
|
|
||||||
import utils from '../../utils';
|
|
||||||
import { getConfig } from '../../config';
|
|
||||||
import common from '../common/common';
|
|
||||||
|
|
||||||
// const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-';
|
|
||||||
const MERMAID_DOM_ID_PREFIX = '';
|
|
||||||
|
|
||||||
const config = getConfig();
|
|
||||||
let vertices = {};
|
|
||||||
let edges = [];
|
|
||||||
let classes = [];
|
|
||||||
let subGraphs = [];
|
|
||||||
let subGraphLookup = {};
|
|
||||||
let tooltips = {};
|
|
||||||
let subCount = 0;
|
|
||||||
let firstGraphFlag = true;
|
|
||||||
let direction;
|
|
||||||
// Functions to be run after graph rendering
|
|
||||||
let funs = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function called by parser when a node definition has been found
|
|
||||||
* @param id
|
|
||||||
* @param text
|
|
||||||
* @param type
|
|
||||||
* @param style
|
|
||||||
* @param classes
|
|
||||||
*/
|
|
||||||
export const addVertex = function(_id, text, type, style, classes) {
|
|
||||||
let txt;
|
|
||||||
let id = _id;
|
|
||||||
if (typeof id === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (id.trim().length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
|
||||||
|
|
||||||
if (typeof vertices[id] === 'undefined') {
|
|
||||||
vertices[id] = { id: id, styles: [], classes: [] };
|
|
||||||
}
|
|
||||||
if (typeof text !== 'undefined') {
|
|
||||||
txt = common.sanitizeText(text.trim(), config);
|
|
||||||
|
|
||||||
// strip quotes if string starts and ends with a quote
|
|
||||||
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
|
|
||||||
txt = txt.substring(1, txt.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
vertices[id].text = txt;
|
|
||||||
} else {
|
|
||||||
if (typeof vertices[id].text === 'undefined') {
|
|
||||||
vertices[id].text = _id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof type !== 'undefined') {
|
|
||||||
vertices[id].type = type;
|
|
||||||
}
|
|
||||||
if (typeof style !== 'undefined') {
|
|
||||||
if (style !== null) {
|
|
||||||
style.forEach(function(s) {
|
|
||||||
vertices[id].styles.push(s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof classes !== 'undefined') {
|
|
||||||
if (classes !== null) {
|
|
||||||
classes.forEach(function(s) {
|
|
||||||
vertices[id].classes.push(s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function called by parser when a link/edge definition has been found
|
|
||||||
* @param start
|
|
||||||
* @param end
|
|
||||||
* @param type
|
|
||||||
* @param linktext
|
|
||||||
*/
|
|
||||||
export const addSingleLink = function(_start, _end, type, linktext) {
|
|
||||||
let start = _start;
|
|
||||||
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);
|
|
||||||
|
|
||||||
const edge = { start: start, end: end, type: undefined, text: '' };
|
|
||||||
linktext = type.text;
|
|
||||||
|
|
||||||
if (typeof linktext !== 'undefined') {
|
|
||||||
edge.text = common.sanitizeText(linktext.trim(), config);
|
|
||||||
|
|
||||||
// strip quotes if string starts and exnds with a quote
|
|
||||||
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
|
|
||||||
edge.text = edge.text.substring(1, edge.text.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof type !== 'undefined') {
|
|
||||||
edge.type = type.type;
|
|
||||||
edge.stroke = type.stroke;
|
|
||||||
}
|
|
||||||
edges.push(edge);
|
|
||||||
};
|
|
||||||
export const addLink = function(_start, _end, type, linktext) {
|
|
||||||
let i, j;
|
|
||||||
for (i = 0; i < _start.length; i++) {
|
|
||||||
for (j = 0; j < _end.length; j++) {
|
|
||||||
addSingleLink(_start[i], _end[j], type, linktext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a link's line interpolation algorithm
|
|
||||||
* @param pos
|
|
||||||
* @param interpolate
|
|
||||||
*/
|
|
||||||
export const updateLinkInterpolate = function(positions, interp) {
|
|
||||||
positions.forEach(function(pos) {
|
|
||||||
if (pos === 'default') {
|
|
||||||
edges.defaultInterpolate = interp;
|
|
||||||
} else {
|
|
||||||
edges[pos].interpolate = interp;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a link with a style
|
|
||||||
* @param pos
|
|
||||||
* @param style
|
|
||||||
*/
|
|
||||||
export const updateLink = function(positions, style) {
|
|
||||||
positions.forEach(function(pos) {
|
|
||||||
if (pos === 'default') {
|
|
||||||
edges.defaultStyle = style;
|
|
||||||
} else {
|
|
||||||
if (utils.isSubstringInArray('fill', style) === -1) {
|
|
||||||
style.push('fill:none');
|
|
||||||
}
|
|
||||||
edges[pos].style = style;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addClass = function(id, style) {
|
|
||||||
if (typeof classes[id] === 'undefined') {
|
|
||||||
classes[id] = { id: id, styles: [], textStyles: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof style !== 'undefined') {
|
|
||||||
if (style !== null) {
|
|
||||||
style.forEach(function(s) {
|
|
||||||
if (s.match('color')) {
|
|
||||||
const newStyle1 = s.replace('fill', 'bgFill');
|
|
||||||
const newStyle2 = newStyle1.replace('color', 'fill');
|
|
||||||
classes[id].textStyles.push(newStyle2);
|
|
||||||
}
|
|
||||||
classes[id].styles.push(s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by parser when a graph definition is found, stores the direction of the chart.
|
|
||||||
* @param dir
|
|
||||||
*/
|
|
||||||
export const setDirection = function(dir) {
|
|
||||||
direction = dir;
|
|
||||||
if (direction.match(/.*</)) {
|
|
||||||
direction = 'RL';
|
|
||||||
}
|
|
||||||
if (direction.match(/.*\^/)) {
|
|
||||||
direction = 'BT';
|
|
||||||
}
|
|
||||||
if (direction.match(/.*>/)) {
|
|
||||||
direction = 'LR';
|
|
||||||
}
|
|
||||||
if (direction.match(/.*v/)) {
|
|
||||||
direction = 'TB';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by parser when a special node is found, e.g. a clickable element.
|
|
||||||
* @param ids Comma separated list of ids
|
|
||||||
* @param className Class to add
|
|
||||||
*/
|
|
||||||
export const setClass = function(ids, className) {
|
|
||||||
ids.split(',').forEach(function(_id) {
|
|
||||||
let id = _id;
|
|
||||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
|
||||||
if (typeof vertices[id] !== 'undefined') {
|
|
||||||
vertices[id].classes.push(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof subGraphLookup[id] !== 'undefined') {
|
|
||||||
subGraphLookup[id].classes.push(className);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setTooltip = function(ids, tooltip) {
|
|
||||||
ids.split(',').forEach(function(id) {
|
|
||||||
if (typeof tooltip !== 'undefined') {
|
|
||||||
tooltips[id] = common.sanitizeText(tooltip, config);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setClickFun = function(_id, functionName) {
|
|
||||||
let id = _id;
|
|
||||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
|
||||||
if (config.securityLevel !== 'loose') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof functionName === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof vertices[id] !== 'undefined') {
|
|
||||||
funs.push(function() {
|
|
||||||
const elem = document.querySelector(`[id="${id}"]`);
|
|
||||||
if (elem !== null) {
|
|
||||||
elem.addEventListener(
|
|
||||||
'click',
|
|
||||||
function() {
|
|
||||||
window[functionName](id);
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
|
||||||
* @param ids Comma separated list of ids
|
|
||||||
* @param linkStr URL to create a link for
|
|
||||||
* @param tooltip Tooltip for the clickable element
|
|
||||||
*/
|
|
||||||
export const setLink = function(ids, linkStr, tooltip) {
|
|
||||||
ids.split(',').forEach(function(_id) {
|
|
||||||
let id = _id;
|
|
||||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
|
||||||
if (typeof vertices[id] !== 'undefined') {
|
|
||||||
vertices[id].link = utils.formatUrl(linkStr, config);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setTooltip(ids, tooltip);
|
|
||||||
setClass(ids, 'clickable');
|
|
||||||
};
|
|
||||||
export const getTooltip = function(id) {
|
|
||||||
return tooltips[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by parser when a click definition is found. Registers an event handler.
|
|
||||||
* @param ids Comma separated list of ids
|
|
||||||
* @param functionName Function to be called on click
|
|
||||||
* @param tooltip Tooltip for the clickable element
|
|
||||||
*/
|
|
||||||
export const setClickEvent = function(ids, functionName, tooltip) {
|
|
||||||
ids.split(',').forEach(function(id) {
|
|
||||||
setClickFun(id, functionName);
|
|
||||||
});
|
|
||||||
setTooltip(ids, tooltip);
|
|
||||||
setClass(ids, 'clickable');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const bindFunctions = function(element) {
|
|
||||||
funs.forEach(function(fun) {
|
|
||||||
fun(element);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
export const getDirection = function() {
|
|
||||||
return direction.trim();
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Retrieval function for fetching the found nodes after parsing has completed.
|
|
||||||
* @returns {{}|*|vertices}
|
|
||||||
*/
|
|
||||||
export const getVertices = function() {
|
|
||||||
return vertices;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieval function for fetching the found links after parsing has completed.
|
|
||||||
* @returns {{}|*|edges}
|
|
||||||
*/
|
|
||||||
export const getEdges = function() {
|
|
||||||
return edges;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieval function for fetching the found class definitions after parsing has completed.
|
|
||||||
* @returns {{}|*|classes}
|
|
||||||
*/
|
|
||||||
export const getClasses = function() {
|
|
||||||
return classes;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setupToolTips = function(element) {
|
|
||||||
let tooltipElem = d3.select('.mermaidTooltip');
|
|
||||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
|
||||||
tooltipElem = d3
|
|
||||||
.select('body')
|
|
||||||
.append('div')
|
|
||||||
.attr('class', 'mermaidTooltip')
|
|
||||||
.style('opacity', 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const svg = d3.select(element).select('svg');
|
|
||||||
|
|
||||||
const nodes = svg.selectAll('g.node');
|
|
||||||
nodes
|
|
||||||
.on('mouseover', function() {
|
|
||||||
const el = d3.select(this);
|
|
||||||
const title = el.attr('title');
|
|
||||||
// Dont try to draw a tooltip if no data is provided
|
|
||||||
if (title === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = this.getBoundingClientRect();
|
|
||||||
|
|
||||||
tooltipElem
|
|
||||||
.transition()
|
|
||||||
.duration(200)
|
|
||||||
.style('opacity', '.9');
|
|
||||||
tooltipElem
|
|
||||||
.html(el.attr('title'))
|
|
||||||
.style('left', rect.left + (rect.right - rect.left) / 2 + 'px')
|
|
||||||
.style('top', rect.top - 14 + document.body.scrollTop + 'px');
|
|
||||||
el.classed('hover', true);
|
|
||||||
})
|
|
||||||
.on('mouseout', function() {
|
|
||||||
tooltipElem
|
|
||||||
.transition()
|
|
||||||
.duration(500)
|
|
||||||
.style('opacity', 0);
|
|
||||||
const el = d3.select(this);
|
|
||||||
el.classed('hover', false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
funs.push(setupToolTips);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the internal graph db so that a new graph can be parsed.
|
|
||||||
*/
|
|
||||||
export const clear = function() {
|
|
||||||
vertices = {};
|
|
||||||
classes = {};
|
|
||||||
edges = [];
|
|
||||||
funs = [];
|
|
||||||
funs.push(setupToolTips);
|
|
||||||
subGraphs = [];
|
|
||||||
subGraphLookup = {};
|
|
||||||
subCount = 0;
|
|
||||||
tooltips = [];
|
|
||||||
firstGraphFlag = true;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const defaultStyle = function() {
|
|
||||||
return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the internal graph db so that a new graph can be parsed.
|
|
||||||
*/
|
|
||||||
export const addSubGraph = function(_id, list, _title) {
|
|
||||||
let id = _id.trim();
|
|
||||||
let title = _title;
|
|
||||||
if (_id === _title && _title.match(/\s/)) {
|
|
||||||
id = undefined;
|
|
||||||
}
|
|
||||||
function uniq(a) {
|
|
||||||
const prims = { boolean: {}, number: {}, string: {} };
|
|
||||||
const objs = [];
|
|
||||||
|
|
||||||
return a.filter(function(item) {
|
|
||||||
const type = typeof item;
|
|
||||||
if (item.trim() === '') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type in prims) {
|
|
||||||
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true); // eslint-disable-line
|
|
||||||
} else {
|
|
||||||
return objs.indexOf(item) >= 0 ? false : objs.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let nodeList = [];
|
|
||||||
|
|
||||||
nodeList = uniq(nodeList.concat.apply(nodeList, list));
|
|
||||||
for (let i = 0; i < nodeList.length; i++) {
|
|
||||||
if (nodeList[i][0].match(/\d/)) nodeList[i] = MERMAID_DOM_ID_PREFIX + nodeList[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
id = id || 'subGraph' + subCount;
|
|
||||||
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
|
||||||
title = title || '';
|
|
||||||
title = common.sanitizeText(title, config);
|
|
||||||
subCount = subCount + 1;
|
|
||||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
|
|
||||||
subGraphs.push(subGraph);
|
|
||||||
subGraphLookup[id] = subGraph;
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPosForId = function(id) {
|
|
||||||
for (let i = 0; i < subGraphs.length; i++) {
|
|
||||||
if (subGraphs[i].id === id) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
let secCount = -1;
|
|
||||||
const posCrossRef = [];
|
|
||||||
const indexNodes2 = function(id, pos) {
|
|
||||||
const nodes = subGraphs[pos].nodes;
|
|
||||||
secCount = secCount + 1;
|
|
||||||
if (secCount > 2000) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
posCrossRef[secCount] = pos;
|
|
||||||
// Check if match
|
|
||||||
if (subGraphs[pos].id === id) {
|
|
||||||
return {
|
|
||||||
result: true,
|
|
||||||
count: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
let posCount = 1;
|
|
||||||
while (count < nodes.length) {
|
|
||||||
const childPos = getPosForId(nodes[count]);
|
|
||||||
// Ignore regular nodes (pos will be -1)
|
|
||||||
if (childPos >= 0) {
|
|
||||||
const res = indexNodes2(id, childPos);
|
|
||||||
if (res.result) {
|
|
||||||
return {
|
|
||||||
result: true,
|
|
||||||
count: posCount + res.count
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
posCount = posCount + res.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
count = count + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
count: posCount
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDepthFirstPos = function(pos) {
|
|
||||||
return posCrossRef[pos];
|
|
||||||
};
|
|
||||||
export const indexNodes = function() {
|
|
||||||
secCount = -1;
|
|
||||||
if (subGraphs.length > 0) {
|
|
||||||
indexNodes2('none', subGraphs.length - 1, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSubGraphs = function() {
|
|
||||||
return subGraphs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const firstGraph = () => {
|
|
||||||
if (firstGraphFlag) {
|
|
||||||
firstGraphFlag = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const destructStartLink = _str => {
|
|
||||||
const str = _str.trim();
|
|
||||||
|
|
||||||
switch (str) {
|
|
||||||
case '<--':
|
|
||||||
return { type: 'arrow', stroke: 'normal' };
|
|
||||||
case 'x--':
|
|
||||||
return { type: 'arrow_cross', stroke: 'normal' };
|
|
||||||
case 'o--':
|
|
||||||
return { type: 'arrow_circle', stroke: 'normal' };
|
|
||||||
case '<-.':
|
|
||||||
return { type: 'arrow', stroke: 'dotted' };
|
|
||||||
case 'x-.':
|
|
||||||
return { type: 'arrow_cross', stroke: 'dotted' };
|
|
||||||
case 'o-.':
|
|
||||||
return { type: 'arrow_circle', stroke: 'dotted' };
|
|
||||||
case '<==':
|
|
||||||
return { type: 'arrow', stroke: 'thick' };
|
|
||||||
case 'x==':
|
|
||||||
return { type: 'arrow_cross', stroke: 'thick' };
|
|
||||||
case 'o==':
|
|
||||||
return { type: 'arrow_circle', stroke: 'thick' };
|
|
||||||
case '--':
|
|
||||||
return { type: 'arrow_open', stroke: 'normal' };
|
|
||||||
case '==':
|
|
||||||
return { type: 'arrow_open', stroke: 'thick' };
|
|
||||||
case '-.':
|
|
||||||
return { type: 'arrow_open', stroke: 'dotted' };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const destructEndLink = _str => {
|
|
||||||
const str = _str.trim();
|
|
||||||
|
|
||||||
switch (str) {
|
|
||||||
case '--x':
|
|
||||||
return { type: 'arrow_cross', stroke: 'normal' };
|
|
||||||
case '-->':
|
|
||||||
return { type: 'arrow', stroke: 'normal' };
|
|
||||||
case '<-->':
|
|
||||||
return { type: 'double_arrow_point', stroke: 'normal' };
|
|
||||||
case 'x--x':
|
|
||||||
return { type: 'double_arrow_cross', stroke: 'normal' };
|
|
||||||
case 'o--o':
|
|
||||||
return { type: 'double_arrow_circle', stroke: 'normal' };
|
|
||||||
case 'o.-o':
|
|
||||||
return { type: 'double_arrow_circle', stroke: 'dotted' };
|
|
||||||
case '<==>':
|
|
||||||
return { type: 'double_arrow_point', stroke: 'thick' };
|
|
||||||
case 'o==o':
|
|
||||||
return { type: 'double_arrow_circle', stroke: 'thick' };
|
|
||||||
case 'x==x':
|
|
||||||
return { type: 'double_arrow_cross', stroke: 'thick' };
|
|
||||||
case 'x.-x':
|
|
||||||
return { type: 'double_arrow_cross', stroke: 'dotted' };
|
|
||||||
case 'x-.-x':
|
|
||||||
return { type: 'double_arrow_cross', stroke: 'dotted' };
|
|
||||||
case '<.->':
|
|
||||||
return { type: 'double_arrow_point', stroke: 'dotted' };
|
|
||||||
case '<-.->':
|
|
||||||
return { type: 'double_arrow_point', stroke: 'dotted' };
|
|
||||||
case 'o-.-o':
|
|
||||||
return { type: 'double_arrow_circle', stroke: 'dotted' };
|
|
||||||
case '--o':
|
|
||||||
return { type: 'arrow_circle', stroke: 'normal' };
|
|
||||||
case '---':
|
|
||||||
return { type: 'arrow_open', stroke: 'normal' };
|
|
||||||
case '-.-x':
|
|
||||||
return { type: 'arrow_cross', stroke: 'dotted' };
|
|
||||||
case '-.->':
|
|
||||||
return { type: 'arrow', stroke: 'dotted' };
|
|
||||||
case '-.-o':
|
|
||||||
return { type: 'arrow_circle', stroke: 'dotted' };
|
|
||||||
case '-.-':
|
|
||||||
return { type: 'arrow_open', stroke: 'dotted' };
|
|
||||||
case '.-x':
|
|
||||||
return { type: 'arrow_cross', stroke: 'dotted' };
|
|
||||||
case '.->':
|
|
||||||
return { type: 'arrow', stroke: 'dotted' };
|
|
||||||
case '.-o':
|
|
||||||
return { type: 'arrow_circle', stroke: 'dotted' };
|
|
||||||
case '.-':
|
|
||||||
return { type: 'arrow_open', stroke: 'dotted' };
|
|
||||||
case '==x':
|
|
||||||
return { type: 'arrow_cross', stroke: 'thick' };
|
|
||||||
case '==>':
|
|
||||||
return { type: 'arrow', stroke: 'thick' };
|
|
||||||
case '==o':
|
|
||||||
return { type: 'arrow_circle', stroke: 'thick' };
|
|
||||||
case '===':
|
|
||||||
return { type: 'arrow_open', stroke: 'thick' };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const destructLink = (_str, _startStr) => {
|
|
||||||
const info = destructEndLink(_str);
|
|
||||||
let startInfo;
|
|
||||||
if (_startStr) {
|
|
||||||
startInfo = destructStartLink(_startStr);
|
|
||||||
|
|
||||||
if (startInfo.stroke !== info.stroke) {
|
|
||||||
return { type: 'INVALID', stroke: 'INVALID' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startInfo.type === 'arrow_open') {
|
|
||||||
// -- xyz --> - take arrow type form ending
|
|
||||||
startInfo.type = info.type;
|
|
||||||
} else {
|
|
||||||
// x-- xyz --> - not supported
|
|
||||||
if (startInfo.type !== info.type) return { type: 'INVALID', stroke: 'INVALID' };
|
|
||||||
|
|
||||||
startInfo.type = 'double_' + startInfo.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startInfo.type === 'double_arrow') {
|
|
||||||
startInfo.type = 'double_arrow_point';
|
|
||||||
}
|
|
||||||
|
|
||||||
return startInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
addVertex,
|
|
||||||
addLink,
|
|
||||||
updateLinkInterpolate,
|
|
||||||
updateLink,
|
|
||||||
addClass,
|
|
||||||
setDirection,
|
|
||||||
setClass,
|
|
||||||
getTooltip,
|
|
||||||
setClickEvent,
|
|
||||||
setLink,
|
|
||||||
bindFunctions,
|
|
||||||
getDirection,
|
|
||||||
getVertices,
|
|
||||||
getEdges,
|
|
||||||
getClasses,
|
|
||||||
clear,
|
|
||||||
defaultStyle,
|
|
||||||
addSubGraph,
|
|
||||||
getDepthFirstPos,
|
|
||||||
indexNodes,
|
|
||||||
getSubGraphs,
|
|
||||||
destructLink,
|
|
||||||
lex: {
|
|
||||||
firstGraph
|
|
||||||
}
|
|
||||||
};
|
|
@ -242,6 +242,31 @@ export function addToRender(render) {
|
|||||||
render.shapes().rect_right_inv_arrow = rect_right_inv_arrow;
|
render.shapes().rect_right_inv_arrow = rect_right_inv_arrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addToRenderV2(addShape) {
|
||||||
|
addShape({ question });
|
||||||
|
addShape({ hexagon });
|
||||||
|
addShape({ stadium });
|
||||||
|
addShape({ cylinder });
|
||||||
|
|
||||||
|
// Add custom shape for box with inverted arrow on left side
|
||||||
|
addShape({ rect_left_inv_arrow });
|
||||||
|
|
||||||
|
// Add custom shape for box with inverted arrow on left side
|
||||||
|
addShape({ lean_right });
|
||||||
|
|
||||||
|
// Add custom shape for box with inverted arrow on left side
|
||||||
|
addShape({ lean_left });
|
||||||
|
|
||||||
|
// Add custom shape for box with inverted arrow on left side
|
||||||
|
addShape({ trapezoid });
|
||||||
|
|
||||||
|
// Add custom shape for box with inverted arrow on left side
|
||||||
|
addShape({ inv_trapezoid });
|
||||||
|
|
||||||
|
// Add custom shape for box with inverted arrow on right side
|
||||||
|
addShape({ rect_right_inv_arrow });
|
||||||
|
}
|
||||||
|
|
||||||
function insertPolygonShape(parent, w, h, points) {
|
function insertPolygonShape(parent, w, h, points) {
|
||||||
return parent
|
return parent
|
||||||
.insert('polygon', ':first-child')
|
.insert('polygon', ':first-child')
|
||||||
@ -257,5 +282,6 @@ function insertPolygonShape(parent, w, h, points) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
addToRender
|
addToRender,
|
||||||
|
addToRenderV2
|
||||||
};
|
};
|
||||||
|
@ -2,15 +2,15 @@ import graphlib from 'graphlib';
|
|||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import dagre from 'dagre';
|
import dagre from 'dagre';
|
||||||
|
|
||||||
import flowDb from '../flowchart/flowDb';
|
import flowDb from './flowDb';
|
||||||
import flow from '../flowchart/parser/flow';
|
import flow from './parser/flow';
|
||||||
import { getConfig } from '../../config';
|
import { getConfig } from '../../config';
|
||||||
|
|
||||||
import dagreD3 from 'dagre-d3';
|
import { render, addShape } from '../../dagre-wrapper/index.js';
|
||||||
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
|
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||||
import flowChartShapes from '../flowchart/flowChartShapes';
|
import flowChartShapes from './flowChartShapes';
|
||||||
|
|
||||||
const conf = {};
|
const conf = {};
|
||||||
export const setConf = function(cnf) {
|
export const setConf = function(cnf) {
|
||||||
@ -21,7 +21,7 @@ export const setConf = function(cnf) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that adds the vertices found in the graph definition to the graph to be rendered.
|
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||||
* @param vert Object containing the vertices.
|
* @param vert Object containing the vertices.
|
||||||
* @param g The graph that is to be drawn.
|
* @param g The graph that is to be drawn.
|
||||||
*/
|
*/
|
||||||
@ -135,11 +135,13 @@ export const addVertices = function(vert, g, svgId) {
|
|||||||
labelStyle: styles.labelStyle,
|
labelStyle: styles.labelStyle,
|
||||||
shape: _shape,
|
shape: _shape,
|
||||||
label: vertexNode,
|
label: vertexNode,
|
||||||
|
labelText: vertexText,
|
||||||
rx: radious,
|
rx: radious,
|
||||||
ry: radious,
|
ry: radious,
|
||||||
class: classStr,
|
class: classStr,
|
||||||
style: styles.style,
|
style: styles.style,
|
||||||
id: vertex.id
|
id: vertex.id,
|
||||||
|
padding: getConfig().flowchart.padding
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -164,7 +166,7 @@ export const addEdges = function(edges, g) {
|
|||||||
edges.forEach(function(edge) {
|
edges.forEach(function(edge) {
|
||||||
cnt++;
|
cnt++;
|
||||||
const edgeData = {};
|
const edgeData = {};
|
||||||
|
edgeData.id = 'id' + cnt;
|
||||||
// Set link type for rendering
|
// Set link type for rendering
|
||||||
if (edge.type === 'arrow_open') {
|
if (edge.type === 'arrow_open') {
|
||||||
edgeData.arrowhead = 'none';
|
edgeData.arrowhead = 'none';
|
||||||
@ -321,50 +323,46 @@ export const draw = function(text, id) {
|
|||||||
addVertices(vert, g, id);
|
addVertices(vert, g, id);
|
||||||
addEdges(edges, g);
|
addEdges(edges, g);
|
||||||
|
|
||||||
// Create the renderer
|
|
||||||
const Render = dagreD3.render;
|
|
||||||
const render = new Render();
|
|
||||||
|
|
||||||
// Add custom shapes
|
// Add custom shapes
|
||||||
flowChartShapes.addToRender(render);
|
// flowChartShapes.addToRenderV2(addShape);
|
||||||
|
|
||||||
// Add our custom arrow - an empty arrowhead
|
// Add our custom arrow - an empty arrowhead
|
||||||
render.arrows().none = function normal(parent, id, edge, type) {
|
// render.arrows().none = function normal(parent, id, edge, type) {
|
||||||
const marker = parent
|
// const marker = parent
|
||||||
.append('marker')
|
// .append('marker')
|
||||||
.attr('id', id)
|
// .attr('id', id)
|
||||||
.attr('viewBox', '0 0 10 10')
|
// .attr('viewBox', '0 0 10 10')
|
||||||
.attr('refX', 9)
|
// .attr('refX', 9)
|
||||||
.attr('refY', 5)
|
// .attr('refY', 5)
|
||||||
.attr('markerUnits', 'strokeWidth')
|
// .attr('markerUnits', 'strokeWidth')
|
||||||
.attr('markerWidth', 8)
|
// .attr('markerWidth', 8)
|
||||||
.attr('markerHeight', 6)
|
// .attr('markerHeight', 6)
|
||||||
.attr('orient', 'auto');
|
// .attr('orient', 'auto');
|
||||||
|
|
||||||
const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
|
// // const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
|
||||||
dagreD3.util.applyStyle(path, edge[type + 'Style']);
|
// dagreD3.util.applyStyle(path, edge[type + 'Style']);
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
|
// Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
|
||||||
render.arrows().normal = function normal(parent, id) {
|
// render.arrows().normal = function normal(parent, id) {
|
||||||
const marker = parent
|
// const marker = parent
|
||||||
.append('marker')
|
// .append('marker')
|
||||||
.attr('id', id)
|
// .attr('id', id)
|
||||||
.attr('viewBox', '0 0 10 10')
|
// .attr('viewBox', '0 0 10 10')
|
||||||
.attr('refX', 9)
|
// .attr('refX', 9)
|
||||||
.attr('refY', 5)
|
// .attr('refY', 5)
|
||||||
.attr('markerUnits', 'strokeWidth')
|
// .attr('markerUnits', 'strokeWidth')
|
||||||
.attr('markerWidth', 8)
|
// .attr('markerWidth', 8)
|
||||||
.attr('markerHeight', 6)
|
// .attr('markerHeight', 6)
|
||||||
.attr('orient', 'auto');
|
// .attr('orient', 'auto');
|
||||||
|
|
||||||
marker
|
// marker
|
||||||
.append('path')
|
// .append('path')
|
||||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
// .attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||||
.attr('class', 'arrowheadPath')
|
// .attr('class', 'arrowheadPath')
|
||||||
.style('stroke-width', 1)
|
// .style('stroke-width', 1)
|
||||||
.style('stroke-dasharray', '1,0');
|
// .style('stroke-dasharray', '1,0');
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Set up an SVG group so that we can translate the final graph.
|
// Set up an SVG group so that we can translate the final graph.
|
||||||
const svg = d3.select(`[id="${id}"]`);
|
const svg = d3.select(`[id="${id}"]`);
|
||||||
@ -372,6 +370,7 @@ export const draw = function(text, id) {
|
|||||||
// Run the renderer. This is what draws the final graph.
|
// Run the renderer. This is what draws the final graph.
|
||||||
const element = d3.select('#' + id + ' g');
|
const element = d3.select('#' + id + ' g');
|
||||||
render(element, g);
|
render(element, g);
|
||||||
|
dagre.layout(g);
|
||||||
|
|
||||||
element.selectAll('g.node').attr('title', function() {
|
element.selectAll('g.node').attr('title', function() {
|
||||||
return flowDb.getTooltip(this.id);
|
return flowDb.getTooltip(this.id);
|
@ -17,6 +17,7 @@ export const logger = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setLogLevel = function(level) {
|
export const setLogLevel = function(level) {
|
||||||
|
logger.trace = () => {};
|
||||||
logger.debug = () => {};
|
logger.debug = () => {};
|
||||||
logger.info = () => {};
|
logger.info = () => {};
|
||||||
logger.warn = () => {};
|
logger.warn = () => {};
|
||||||
|
@ -17,7 +17,7 @@ import { setConfig, getConfig } from './config';
|
|||||||
import { logger, setLogLevel } from './logger';
|
import { logger, setLogLevel } from './logger';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import flowRenderer from './diagrams/flowchart/flowRenderer';
|
import flowRenderer from './diagrams/flowchart/flowRenderer';
|
||||||
import flowRendererV2 from './diagrams/flowchart-v2/flowRenderer';
|
import flowRendererV2 from './diagrams/flowchart/flowRenderer-v2';
|
||||||
import flowParser from './diagrams/flowchart/parser/flow';
|
import flowParser from './diagrams/flowchart/parser/flow';
|
||||||
import flowDb from './diagrams/flowchart/flowDb';
|
import flowDb from './diagrams/flowchart/flowDb';
|
||||||
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
|
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
|
||||||
@ -165,7 +165,10 @@ const config = {
|
|||||||
* * linear **default**
|
* * linear **default**
|
||||||
* * cardinal
|
* * cardinal
|
||||||
*/
|
*/
|
||||||
curve: 'linear'
|
curve: 'linear',
|
||||||
|
// Only used in new experimental rendering
|
||||||
|
// repreesents the padding between the labels and the shape
|
||||||
|
padding: 15
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user