diff --git a/.gitignore b/.gitignore
index 69f442484..58579d79b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,5 @@ dist/classTest.html
dist/sequenceTest.html
.vscode/
-cypress/platform/current.html
\ No newline at end of file
+cypress/platform/current.html
+cypress/platform/experimental.html
\ No newline at end of file
diff --git a/cypress/platform/current.html b/cypress/platform/current.html
index 3ed964921..2f5ec32b4 100644
--- a/cypress/platform/current.html
+++ b/cypress/platform/current.html
@@ -20,11 +20,8 @@
info below
- stateDiagram
- O --> A : ong line using
should work
should work
should work
- A --> B : ong line using
should work
- B --> C : Sing line
-
+ flowchart LR
+ A --> B
diff --git a/src/diagrams/flowchart-v2/flowChartShapes.js b/src/diagrams/flowchart-v2/flowChartShapes.js
new file mode 100644
index 000000000..23cb53049
--- /dev/null
+++ b/src/diagrams/flowchart-v2/flowChartShapes.js
@@ -0,0 +1,261 @@
+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
+};
diff --git a/src/diagrams/flowchart-v2/flowChartShapes.spec.js b/src/diagrams/flowchart-v2/flowChartShapes.spec.js
new file mode 100644
index 000000000..61e876d4b
--- /dev/null
+++ b/src/diagrams/flowchart-v2/flowChartShapes.spec.js
@@ -0,0 +1,131 @@
+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;
+}
diff --git a/src/diagrams/flowchart-v2/flowDb.js b/src/diagrams/flowchart-v2/flowDb.js
new file mode 100644
index 000000000..4917a54a7
--- /dev/null
+++ b/src/diagrams/flowchart-v2/flowDb.js
@@ -0,0 +1,644 @@
+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
+ }
+};
diff --git a/src/diagrams/flowchart-v2/flowRenderer.js b/src/diagrams/flowchart-v2/flowRenderer.js
new file mode 100644
index 000000000..10250a16c
--- /dev/null
+++ b/src/diagrams/flowchart-v2/flowRenderer.js
@@ -0,0 +1,488 @@
+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 { getConfig } from '../../config';
+
+import dagreD3 from 'dagre-d3';
+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';
+
+const conf = {};
+export const setConf = function(cnf) {
+ const keys = Object.keys(cnf);
+ for (let i = 0; i < keys.length; i++) {
+ conf[keys[i]] = cnf[keys[i]];
+ }
+};
+
+/**
+ * Function that adds the vertices found in the graph definition to the graph to be rendered.
+ * @param vert Object containing the vertices.
+ * @param g The graph that is to be drawn.
+ */
+export const addVertices = function(vert, g, svgId) {
+ const svg = d3.select(`[id="${svgId}"]`);
+ const keys = Object.keys(vert);
+
+ // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
+ keys.forEach(function(id) {
+ const vertex = vert[id];
+
+ /**
+ * Variable for storing the classes for the vertex
+ * @type {string}
+ */
+ let classStr = 'default';
+ if (vertex.classes.length > 0) {
+ classStr = vertex.classes.join(' ');
+ }
+
+ const styles = getStylesFromArray(vertex.styles);
+
+ // Use vertex id as text in the box if no text is provided by the graph definition
+ let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
+
+ // We create a SVG label, either by delegating to addHtmlLabel or manually
+ let vertexNode;
+ if (getConfig().flowchart.htmlLabels) {
+ // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
+ const node = {
+ label: vertexText.replace(
+ /fa[lrsb]?:fa-[\w-]+/g,
+ s => ``
+ )
+ };
+ vertexNode = addHtmlLabel(svg, node).node();
+ vertexNode.parentNode.removeChild(vertexNode);
+ } else {
+ const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+ svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
+
+ const rows = vertexText.split(/
/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);
+ }
+ vertexNode = svgLabel;
+ }
+
+ let radious = 0;
+ let _shape = '';
+ // Set the shape based parameters
+ switch (vertex.type) {
+ case 'round':
+ radious = 5;
+ _shape = 'rect';
+ break;
+ case 'square':
+ _shape = 'rect';
+ break;
+ case 'diamond':
+ _shape = 'question';
+ break;
+ case 'hexagon':
+ _shape = 'hexagon';
+ break;
+ case 'odd':
+ _shape = 'rect_left_inv_arrow';
+ break;
+ case 'lean_right':
+ _shape = 'lean_right';
+ break;
+ case 'lean_left':
+ _shape = 'lean_left';
+ break;
+ case 'trapezoid':
+ _shape = 'trapezoid';
+ break;
+ case 'inv_trapezoid':
+ _shape = 'inv_trapezoid';
+ break;
+ case 'odd_right':
+ _shape = 'rect_left_inv_arrow';
+ break;
+ case 'circle':
+ _shape = 'circle';
+ break;
+ case 'ellipse':
+ _shape = 'ellipse';
+ break;
+ case 'stadium':
+ _shape = 'stadium';
+ break;
+ case 'cylinder':
+ _shape = 'cylinder';
+ break;
+ case 'group':
+ _shape = 'rect';
+ break;
+ default:
+ _shape = 'rect';
+ }
+ // Add the node
+ g.setNode(vertex.id, {
+ labelType: 'svg',
+ labelStyle: styles.labelStyle,
+ shape: _shape,
+ label: vertexNode,
+ rx: radious,
+ ry: radious,
+ class: classStr,
+ style: styles.style,
+ id: vertex.id
+ });
+ });
+};
+
+/**
+ * Add edges to graph based on parsed graph defninition
+ * @param {Object} edges The edges to add to the graph
+ * @param {Object} g The graph object
+ */
+export const addEdges = function(edges, g) {
+ let cnt = 0;
+
+ let defaultStyle;
+ let defaultLabelStyle;
+
+ if (typeof edges.defaultStyle !== 'undefined') {
+ const defaultStyles = getStylesFromArray(edges.defaultStyle);
+ defaultStyle = defaultStyles.style;
+ defaultLabelStyle = defaultStyles.labelStyle;
+ }
+
+ edges.forEach(function(edge) {
+ cnt++;
+ const edgeData = {};
+
+ // Set link type for rendering
+ if (edge.type === 'arrow_open') {
+ edgeData.arrowhead = 'none';
+ } else {
+ edgeData.arrowhead = 'normal';
+ }
+
+ let style = '';
+ let labelStyle = '';
+
+ if (typeof edge.style !== 'undefined') {
+ const styles = getStylesFromArray(edge.style);
+ style = styles.style;
+ labelStyle = styles.labelStyle;
+ } else {
+ switch (edge.stroke) {
+ case 'normal':
+ style = 'fill:none';
+ if (typeof defaultStyle !== 'undefined') {
+ style = defaultStyle;
+ }
+ if (typeof defaultLabelStyle !== 'undefined') {
+ labelStyle = defaultLabelStyle;
+ }
+ break;
+ case 'dotted':
+ style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
+ break;
+ case 'thick':
+ style = ' stroke-width: 3.5px;fill:none';
+ break;
+ }
+ }
+
+ edgeData.style = style;
+ edgeData.labelStyle = labelStyle;
+
+ if (typeof edge.interpolate !== 'undefined') {
+ edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear);
+ } else if (typeof edges.defaultInterpolate !== 'undefined') {
+ edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear);
+ } else {
+ edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear);
+ }
+
+ if (typeof edge.text === 'undefined') {
+ if (typeof edge.style !== 'undefined') {
+ edgeData.arrowheadStyle = 'fill: #333';
+ }
+ } else {
+ edgeData.arrowheadStyle = 'fill: #333';
+ edgeData.labelpos = 'c';
+
+ if (getConfig().flowchart.htmlLabels) {
+ edgeData.labelType = 'html';
+ edgeData.label = '' + edge.text + '';
+ } else {
+ edgeData.labelType = 'text';
+ edgeData.label = edge.text.replace(/
/gi, '\n');
+
+ if (typeof edge.style === 'undefined') {
+ edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
+ }
+
+ edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
+ }
+ }
+ // Add the edge to the graph
+ g.setEdge(edge.start, edge.end, edgeData, cnt);
+ });
+};
+
+/**
+ * Returns the all the styles from classDef statements in the graph definition.
+ * @returns {object} classDef styles
+ */
+export const getClasses = function(text) {
+ logger.info('Extracting classes');
+ flowDb.clear();
+ const parser = flow.parser;
+ parser.yy = flowDb;
+
+ // Parse the graph definition
+ parser.parse(text);
+ return flowDb.getClasses();
+};
+
+/**
+ * Draws a flowchart in the tag with id: id based on the graph definition in text.
+ * @param text
+ * @param id
+ */
+export const draw = function(text, id) {
+ logger.info('Drawing flowchart');
+ flowDb.clear();
+ const parser = flow.parser;
+ parser.yy = flowDb;
+
+ // Parse the graph definition
+ try {
+ parser.parse(text);
+ } catch (err) {
+ logger.debug('Parsing failed');
+ }
+
+ // Fetch the default direction, use TD if none was found
+ let dir = flowDb.getDirection();
+ if (typeof dir === 'undefined') {
+ dir = 'TD';
+ }
+
+ const conf = getConfig().flowchart;
+ const nodeSpacing = conf.nodeSpacing || 50;
+ const rankSpacing = conf.rankSpacing || 50;
+
+ // Create the input mermaid.graph
+ const g = new graphlib.Graph({
+ multigraph: true,
+ compound: true
+ })
+ .setGraph({
+ rankdir: dir,
+ nodesep: nodeSpacing,
+ ranksep: rankSpacing,
+ marginx: 8,
+ marginy: 8
+ })
+ .setDefaultEdgeLabel(function() {
+ return {};
+ });
+
+ let subG;
+ const subGraphs = flowDb.getSubGraphs();
+ for (let i = subGraphs.length - 1; i >= 0; i--) {
+ subG = subGraphs[i];
+ flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
+ }
+
+ // Fetch the verices/nodes and edges/links from the parsed graph definition
+ const vert = flowDb.getVertices();
+
+ const edges = flowDb.getEdges();
+
+ let i = 0;
+ for (i = subGraphs.length - 1; i >= 0; i--) {
+ subG = subGraphs[i];
+
+ d3.selectAll('cluster').append('text');
+
+ for (let j = 0; j < subG.nodes.length; j++) {
+ g.setParent(subG.nodes[j], subG.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);
+
+ // 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');
+
+ 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');
+
+ 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}"]`);
+
+ // Run the renderer. This is what draws the final graph.
+ const element = d3.select('#' + id + ' g');
+ render(element, g);
+
+ element.selectAll('g.node').attr('title', function() {
+ return flowDb.getTooltip(this.id);
+ });
+
+ const padding = 8;
+ const svgBounds = svg.node().getBBox();
+ const width = svgBounds.width + padding * 2;
+ const height = svgBounds.height + padding * 2;
+ logger.debug(
+ `new ViewBox 0 0 ${width} ${height}`,
+ `translate(${padding - g._label.marginx}, ${padding - g._label.marginy})`
+ );
+
+ if (conf.useMaxWidth) {
+ svg.attr('width', '100%');
+ svg.attr('style', `max-width: ${width}px;`);
+ } else {
+ svg.attr('height', height);
+ svg.attr('width', width);
+ }
+
+ svg.attr('viewBox', `0 0 ${width} ${height}`);
+ svg
+ .select('g')
+ .attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`);
+
+ // Index nodes
+ flowDb.indexNodes('subGraph' + i);
+
+ // reposition labels
+ for (i = 0; i < subGraphs.length; i++) {
+ subG = subGraphs[i];
+
+ if (subG.title !== 'undefined') {
+ const clusterRects = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"] rect');
+ const clusterEl = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"]');
+
+ const xPos = clusterRects[0].x.baseVal.value;
+ const yPos = clusterRects[0].y.baseVal.value;
+ const width = clusterRects[0].width.baseVal.value;
+ const cluster = d3.select(clusterEl[0]);
+ const te = cluster.select('.label');
+ te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`);
+ te.attr('id', id + 'Text');
+
+ for (let j = 0; j < subG.classes.length; j++) {
+ clusterEl[0].classList.add(subG.classes[j]);
+ }
+ }
+ }
+
+ // Add label rects for non html labels
+ if (!conf.htmlLabels) {
+ const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
+ for (let k = 0; k < labels.length; k++) {
+ const label = labels[k];
+
+ // Get dimensions of label
+ const dim = label.getBBox();
+
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ rect.setAttribute('rx', 0);
+ rect.setAttribute('ry', 0);
+ rect.setAttribute('width', dim.width);
+ rect.setAttribute('height', dim.height);
+ rect.setAttribute('style', 'fill:#e8e8e8;');
+
+ label.insertBefore(rect, label.firstChild);
+ }
+ }
+
+ // If node has a link, wrap it in an anchor SVG object.
+ const keys = Object.keys(vert);
+ keys.forEach(function(key) {
+ const vertex = vert[key];
+
+ if (vertex.link) {
+ const node = d3.select('#' + id + ' [id="' + key + '"]');
+ if (node) {
+ const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
+
+ const linkNode = node.insert(function() {
+ return link;
+ }, ':first-child');
+
+ const shape = node.select('.label-container');
+ if (shape) {
+ linkNode.append(function() {
+ return shape.node();
+ });
+ }
+
+ const label = node.select('.label');
+ if (label) {
+ linkNode.append(function() {
+ return label.node();
+ });
+ }
+ }
+ }
+ });
+};
+
+export default {
+ setConf,
+ addVertices,
+ addEdges,
+ getClasses,
+ draw
+};
diff --git a/src/diagrams/flowchart/parser/flow.jison b/src/diagrams/flowchart/parser/flow.jison
index f867e5713..58e4664a6 100644
--- a/src/diagrams/flowchart/parser/flow.jison
+++ b/src/diagrams/flowchart/parser/flow.jison
@@ -21,7 +21,8 @@
"classDef" return 'CLASSDEF';
"class" return 'CLASS';
"click" return 'CLICK';
-"graph" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
+"graph" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
+"flowchart" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
"subgraph" return 'subgraph';
"end"\b\s* return 'end';
\s*"LR" { this.popState(); return 'DIR'; }
diff --git a/src/experimental.js b/src/experimental.js
new file mode 100644
index 000000000..d34de469d
--- /dev/null
+++ b/src/experimental.js
@@ -0,0 +1,42 @@
+import dagre from 'dagre';
+import graphlib from 'graphlib';
+
+// Create a new directed graph
+var g = new dagre.graphlib.Graph({ compound: true });
+
+// Set an object for the graph label
+g.setGraph({});
+
+// Default to assigning a new object as a label for each new edge.
+g.setDefaultEdgeLabel(function() {
+ return {};
+});
+
+// Add nodes to the graph. The first argument is the node id. The second is
+// metadata about the node. In this case we're going to add labels to each of
+// our nodes.
+g.setNode('root', { label: 'Cluster' });
+g.setNode('kspacey', { label: 'Kevin Spacey', width: 144, height: 100, x: 200 });
+// g.setParent('kspacey', 'root');
+g.setNode('swilliams', { label: 'Saul Williams', width: 160, height: 100 });
+// g.setNode('bpitt', { label: 'Brad Pitt', width: 108, height: 100 });
+// g.setNode('hford', { label: 'Harrison Ford', width: 168, height: 100 });
+// g.setNode('lwilson', { label: 'Luke Wilson', width: 144, height: 100 });
+// g.setNode('kbacon', { label: 'Kevin Bacon', width: 121, height: 100 });
+
+// Add edges to the graph.
+g.setEdge('kspacey', 'swilliams');
+g.setEdge('swilliams');
+// g.setEdge('swilliams', 'kbacon');
+// g.setEdge('bpitt', 'kbacon');
+// g.setEdge('hford', 'lwilson');
+// g.setEdge('lwilson', 'kbacon');
+
+dagre.layout(g);
+
+g.nodes().forEach(function(v) {
+ console.log('Node ' + v + ': ' + JSON.stringify(g.node(v)));
+});
+g.edges().forEach(function(e) {
+ console.log('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e)));
+});
diff --git a/src/mermaid.js b/src/mermaid.js
index 0cf24e81f..6f2311390 100644
--- a/src/mermaid.js
+++ b/src/mermaid.js
@@ -6,7 +6,6 @@ import he from 'he';
import mermaidAPI from './mermaidAPI';
import { logger } from './logger';
-
/**
* ## init
* Function that goes through the document to find the chart definitions in there and render them.
diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js
index 205254540..600938027 100644
--- a/src/mermaidAPI.js
+++ b/src/mermaidAPI.js
@@ -17,6 +17,7 @@ import { setConfig, getConfig } from './config';
import { logger, setLogLevel } from './logger';
import utils from './utils';
import flowRenderer from './diagrams/flowchart/flowRenderer';
+import flowRendererV2 from './diagrams/flowchart-v2/flowRenderer';
import flowParser from './diagrams/flowchart/parser/flow';
import flowDb from './diagrams/flowchart/flowDb';
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
@@ -363,6 +364,11 @@ function parse(text) {
parser = flowParser;
parser.parser.yy = flowDb;
break;
+ case 'flowchart-v2':
+ flowDb.clear();
+ parser = flowRendererV2;
+ parser.parser.yy = flowDb;
+ break;
case 'sequence':
parser = sequenceParser;
parser.parser.yy = sequenceDb;
@@ -568,6 +574,11 @@ const render = function(id, _txt, cb, container) {
flowRenderer.setConf(config.flowchart);
flowRenderer.draw(txt, id, false);
break;
+ case 'flowchart-v2':
+ config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
+ flowRendererV2.setConf(config.flowchart);
+ flowRendererV2.draw(txt, id, false);
+ break;
case 'sequence':
config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
if (config.sequenceDiagram) {
diff --git a/src/utils.js b/src/utils.js
index 1aec62d4f..c6e37bb9d 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -41,6 +41,9 @@ export const detectType = function(text) {
if (text.match(/^\s*gitGraph/)) {
return 'git';
}
+ if (text.match(/^\s*flowchart/)) {
+ return 'flowchart-v2';
+ }
if (text.match(/^\s*info/)) {
return 'info';