mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-23 09:20:03 +02:00
#1295 First draft of generic renderer applied to flowcharts.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return parent
|
||||
.insert('polygon', ':first-child')
|
||||
@@ -257,5 +282,6 @@ function insertPolygonShape(parent, w, h, points) {
|
||||
}
|
||||
|
||||
export default {
|
||||
addToRender
|
||||
addToRender,
|
||||
addToRenderV2
|
||||
};
|
||||
|
@@ -2,15 +2,15 @@ import graphlib from 'graphlib';
|
||||
import * as d3 from 'd3';
|
||||
import dagre from 'dagre';
|
||||
|
||||
import flowDb from '../flowchart/flowDb';
|
||||
import flow from '../flowchart/parser/flow';
|
||||
import flowDb from './flowDb';
|
||||
import flow from './parser/flow';
|
||||
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 { logger } from '../../logger';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
import flowChartShapes from '../flowchart/flowChartShapes';
|
||||
import flowChartShapes from './flowChartShapes';
|
||||
|
||||
const conf = {};
|
||||
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 g The graph that is to be drawn.
|
||||
*/
|
||||
@@ -135,11 +135,13 @@ export const addVertices = function(vert, g, svgId) {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
label: vertexNode,
|
||||
labelText: vertexText,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: classStr,
|
||||
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) {
|
||||
cnt++;
|
||||
const edgeData = {};
|
||||
|
||||
edgeData.id = 'id' + cnt;
|
||||
// Set link type for rendering
|
||||
if (edge.type === 'arrow_open') {
|
||||
edgeData.arrowhead = 'none';
|
||||
@@ -321,50 +323,46 @@ export const draw = function(text, id) {
|
||||
addVertices(vert, g, id);
|
||||
addEdges(edges, g);
|
||||
|
||||
// Create the renderer
|
||||
const Render = dagreD3.render;
|
||||
const render = new Render();
|
||||
|
||||
// Add custom shapes
|
||||
flowChartShapes.addToRender(render);
|
||||
// flowChartShapes.addToRenderV2(addShape);
|
||||
|
||||
// Add our custom arrow - an empty arrowhead
|
||||
render.arrows().none = function normal(parent, id, edge, type) {
|
||||
const marker = parent
|
||||
.append('marker')
|
||||
.attr('id', id)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto');
|
||||
// render.arrows().none = function normal(parent, id, edge, type) {
|
||||
// const marker = parent
|
||||
// .append('marker')
|
||||
// .attr('id', id)
|
||||
// .attr('viewBox', '0 0 10 10')
|
||||
// .attr('refX', 9)
|
||||
// .attr('refY', 5)
|
||||
// .attr('markerUnits', 'strokeWidth')
|
||||
// .attr('markerWidth', 8)
|
||||
// .attr('markerHeight', 6)
|
||||
// .attr('orient', 'auto');
|
||||
|
||||
const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
|
||||
dagreD3.util.applyStyle(path, edge[type + 'Style']);
|
||||
};
|
||||
// // const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
|
||||
// dagreD3.util.applyStyle(path, edge[type + 'Style']);
|
||||
// };
|
||||
|
||||
// Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
|
||||
render.arrows().normal = function normal(parent, id) {
|
||||
const marker = parent
|
||||
.append('marker')
|
||||
.attr('id', id)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto');
|
||||
// render.arrows().normal = function normal(parent, id) {
|
||||
// const marker = parent
|
||||
// .append('marker')
|
||||
// .attr('id', id)
|
||||
// .attr('viewBox', '0 0 10 10')
|
||||
// .attr('refX', 9)
|
||||
// .attr('refY', 5)
|
||||
// .attr('markerUnits', 'strokeWidth')
|
||||
// .attr('markerWidth', 8)
|
||||
// .attr('markerHeight', 6)
|
||||
// .attr('orient', 'auto');
|
||||
|
||||
marker
|
||||
.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');
|
||||
};
|
||||
// marker
|
||||
// .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');
|
||||
// };
|
||||
|
||||
// Set up an SVG group so that we can translate the final graph.
|
||||
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.
|
||||
const element = d3.select('#' + id + ' g');
|
||||
render(element, g);
|
||||
dagre.layout(g);
|
||||
|
||||
element.selectAll('g.node').attr('title', function() {
|
||||
return flowDb.getTooltip(this.id);
|
Reference in New Issue
Block a user