diff --git a/dist/index.html b/dist/index.html
index d6a856f49..2da7d74d4 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -291,7 +291,7 @@ graph TB
graph TD
A[Christmas] -->|Get money| B(Go shopping)
-B --> C{Let me think}
+B --> C{{Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?}}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
diff --git a/docs/flowchart.md b/docs/flowchart.md
index b793a86ab..665e3b3f7 100644
--- a/docs/flowchart.md
+++ b/docs/flowchart.md
@@ -112,6 +112,17 @@ graph LR
id1{This is the text in the box}
```
+### A hexagon node
+
+```
+graph LR
+ id1{{This is the text in the box}}
+```
+```mermaid
+graph LR
+ id1{{This is the text in the box}}
+```
+
### Trapezoid
```mermaid
diff --git a/docs/mermaidAPI.md b/docs/mermaidAPI.md
index bfad444f4..59aafba4f 100644
--- a/docs/mermaidAPI.md
+++ b/docs/mermaidAPI.md
@@ -77,7 +77,7 @@ This option decides the amount of logging to be used.
Sets the level of trust to be used on the parsed diagrams.
-- **strict**: (**default**) tags in text are encoded, click functionality is disabled
+- **strict**: (**default**) tags in text are encoded, click functionality is disabeled
- **loose**: tags in text are allowed, click functionality is enabled
## startOnLoad
diff --git a/src/diagrams/flowchart/flowChartShapes.js b/src/diagrams/flowchart/flowChartShapes.js
new file mode 100644
index 000000000..e9b17ae89
--- /dev/null
+++ b/src/diagrams/flowchart/flowChartShapes.js
@@ -0,0 +1,177 @@
+import dagreD3 from 'dagre-d3-renderer';
+
+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;
+}
+
+export function addToRender(render) {
+ render.shapes().question = question;
+ render.shapes().hexagon = hexagon;
+
+ // 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/flowChartShapes.spec.js b/src/diagrams/flowchart/flowChartShapes.spec.js
new file mode 100644
index 000000000..de3f05a1d
--- /dev/null
+++ b/src/diagrams/flowchart/flowChartShapes.spec.js
@@ -0,0 +1,91 @@
+import { addToRender } from './flowChartShapes';
+
+describe('flowchart shapes', function() {
+ [
+ [
+ '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/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js
index ef0bbbf61..7bb136287 100644
--- a/src/diagrams/flowchart/flowRenderer.js
+++ b/src/diagrams/flowchart/flowRenderer.js
@@ -8,6 +8,7 @@ import dagreD3 from 'dagre-d3-renderer';
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js';
import { logger } from '../../logger';
import { interpolateToCurve } from '../../utils';
+import flowChartShapes from './flowChartShapes';
const conf = {};
export const setConf = function(cnf) {
@@ -121,6 +122,9 @@ export const addVertices = function(vert, g, svgId) {
case 'diamond':
_shape = 'question';
break;
+ case 'hexagon':
+ _shape = 'hexagon';
+ break;
case 'odd':
_shape = 'rect_left_inv_arrow';
break;
@@ -328,199 +332,8 @@ export const draw = function(text, id) {
const Render = dagreD3.render;
const render = new Render();
- // Add custom shape for rhombus type of boc (decision)
- render.shapes().question = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('rx', 5)
- .attr('ry', 5)
- .attr('transform', 'translate(' + -s / 2 + ',' + (s * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
-
- // Add custom shape for box with inverted arrow on left side
- render.shapes().rect_left_inv_arrow = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
-
- // Add custom shape for box with inverted arrow on left side
- render.shapes().lean_right = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
-
- // Add custom shape for box with inverted arrow on left side
- render.shapes().lean_left = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
-
- // Add custom shape for box with inverted arrow on left side
- render.shapes().trapezoid = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
-
- // Add custom shape for box with inverted arrow on left side
- render.shapes().inv_trapezoid = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
-
- // Add custom shape for box with inverted arrow on right side
- render.shapes().rect_right_inv_arrow = function(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 = parent
- .insert('polygon', ':first-child')
- .attr(
- 'points',
- points
- .map(function(d) {
- return d.x + ',' + d.y;
- })
- .join(' ')
- )
- .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
- node.intersect = function(point) {
- return dagreD3.intersect.polygon(node, points, point);
- };
- return shapeSvg;
- };
+ // Add custom shapes
+ flowChartShapes.addToRender(render);
// Add our custom arrow - an empty arrowhead
render.arrows().none = function normal(parent, id, edge, type) {
diff --git a/src/diagrams/flowchart/flowRenderer.spec.js b/src/diagrams/flowchart/flowRenderer.spec.js
new file mode 100644
index 000000000..3b8c72a44
--- /dev/null
+++ b/src/diagrams/flowchart/flowRenderer.spec.js
@@ -0,0 +1,57 @@
+import { addVertices } from './flowRenderer';
+import { setConfig } from '../../config';
+
+setConfig({
+ flowchart: {
+ htmlLabels: false
+ }
+});
+
+describe('the flowchart renderer', function() {
+ describe('when adding vertices to a graph', function() {
+ [
+ ['round', 'rect', 5],
+ ['square', 'rect'],
+ ['diamond', 'question'],
+ ['hexagon', 'hexagon'],
+ ['odd', 'rect_left_inv_arrow'],
+ ['lean_right', 'lean_right'],
+ ['lean_left', 'lean_left'],
+ ['trapezoid', 'trapezoid'],
+ ['inv_trapezoid', 'inv_trapezoid'],
+ ['odd_right', 'rect_left_inv_arrow'],
+ ['circle', 'circle'],
+ ['ellipse', 'ellipse'],
+ ['group', 'rect']
+ ].forEach(function([type, expectedShape, expectedRadios = 0]) {
+ it(`should add the correct shaped node to the graph for vertex type ${type}`, function() {
+ const addedNodes = [];
+ const mockG = {
+ setNode: function(id, object) {
+ addedNodes.push([id, object]);
+ }
+ };
+ addVertices(
+ {
+ v1: {
+ type,
+ id: 'my-node-id',
+ classes: [],
+ styles: [],
+ text: 'my vertex text'
+ }
+ },
+ mockG,
+ 'svg-id'
+ );
+ expect(addedNodes).toHaveLength(1);
+ expect(addedNodes[0][0]).toEqual('my-node-id');
+ expect(addedNodes[0][1]).toHaveProperty('id', 'my-node-id');
+ expect(addedNodes[0][1]).toHaveProperty('labelType', 'svg');
+ expect(addedNodes[0][1]).toHaveProperty('shape', expectedShape);
+ expect(addedNodes[0][1]).toHaveProperty('rx', expectedRadios);
+ expect(addedNodes[0][1]).toHaveProperty('ry', expectedRadios);
+ });
+ });
+ });
+});
diff --git a/src/diagrams/flowchart/parser/flow.jison b/src/diagrams/flowchart/parser/flow.jison
index bdc8d2869..da86b50f8 100644
--- a/src/diagrams/flowchart/parser/flow.jison
+++ b/src/diagrams/flowchart/parser/flow.jison
@@ -313,6 +313,10 @@ vertex: idString SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| idString DIAMOND_START text DIAMOND_STOP spaceList
{$$ = $1;yy.addVertex($1,$3,'diamond');}
+ | idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP
+ {$$ = $1;yy.addVertex($1,$4,'hexagon');}
+ | idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP spaceList
+ {$$ = $1;yy.addVertex($1,$4,'hexagon');}
| idString TAGEND text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');}
| idString TAGEND text SQE spaceList
diff --git a/src/diagrams/flowchart/parser/flow.spec.js b/src/diagrams/flowchart/parser/flow.spec.js
index 778691e89..2d08f7d83 100644
--- a/src/diagrams/flowchart/parser/flow.spec.js
+++ b/src/diagrams/flowchart/parser/flow.spec.js
@@ -1391,6 +1391,16 @@ describe('when parsing ', function() {
expect(edges.length).toBe(0);
expect(vert['a'].type).toBe('diamond');
});
+ it('should handle a single diamond node with whitespace after it', function() {
+ // Silly but syntactically correct
+ const res = flow.parser.parse('graph TD;a{A} ;');
+
+ const vert = flow.parser.yy.getVertices();
+ const edges = flow.parser.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert['a'].type).toBe('diamond');
+ });
it('should handle a single diamond node with html in it', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a{A
end};');
@@ -1402,6 +1412,27 @@ describe('when parsing ', function() {
expect(vert['a'].type).toBe('diamond');
expect(vert['a'].text).toBe('A
end');
});
+ it('should handle a single hexagon node', function() {
+ // Silly but syntactically correct
+ const res = flow.parser.parse('graph TD;a{{A}};');
+
+ const vert = flow.parser.yy.getVertices();
+ const edges = flow.parser.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert['a'].type).toBe('hexagon');
+ });
+ it('should handle a single hexagon node with html in it', function() {
+ // Silly but syntactically correct
+ const res = flow.parser.parse('graph TD;a{{A
end}};');
+
+ const vert = flow.parser.yy.getVertices();
+ const edges = flow.parser.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert['a'].type).toBe('hexagon');
+ expect(vert['a'].text).toBe('A
end');
+ });
it('should handle a single round node with html in it', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;a(A
end);');