diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js
index 4729537cc..21176feda 100644
--- a/cypress/integration/rendering/flowchart.spec.js
+++ b/cypress/integration/rendering/flowchart.spec.js
@@ -1,7 +1,7 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../../helpers/util';
-describe('Flowcart', () => {
+describe('Flowchart', () => {
it('1: should render a simple flowchart no htmlLabels', () => {
imgSnapshotTest(
`graph TD
@@ -57,7 +57,7 @@ describe('Flowcart', () => {
);
});
- it('4: should style nodes via a class.', () => {
+ it('5: should style nodes via a class.', () => {
imgSnapshotTest(
`
graph TD
@@ -73,7 +73,7 @@ describe('Flowcart', () => {
);
});
- it('5: should render a flowchart full of circles', () => {
+ it('6: should render a flowchart full of circles', () => {
imgSnapshotTest(
`
graph LR
@@ -102,7 +102,7 @@ describe('Flowcart', () => {
);
});
- it('6: should render a flowchart full of icons', () => {
+ it('7: should render a flowchart full of icons', () => {
imgSnapshotTest(
`
graph TD
@@ -173,7 +173,7 @@ describe('Flowcart', () => {
);
});
- it('7: should render labels with numbers at the start', () => {
+ it('8: should render labels with numbers at the start', () => {
imgSnapshotTest(
`
graph TB;subgraph "number as labels";1;end;
@@ -182,7 +182,7 @@ describe('Flowcart', () => {
);
});
- it('8: should render subgraphs', () => {
+ it('9: should render subgraphs', () => {
imgSnapshotTest(
`
graph TB
@@ -194,7 +194,7 @@ describe('Flowcart', () => {
);
});
- it('9: should render subgraphs with a title starting with a digit', () => {
+ it('10: should render subgraphs with a title starting with a digit', () => {
imgSnapshotTest(
`
graph TB
@@ -206,7 +206,7 @@ describe('Flowcart', () => {
);
});
- it('10: should render styled subgraphs', () => {
+ it('11: should render styled subgraphs', () => {
imgSnapshotTest(
`
graph TB
@@ -241,7 +241,7 @@ describe('Flowcart', () => {
);
});
- it('11: should render a flowchart with long names and class definitions', () => {
+ it('12: should render a flowchart with long names and class definitions', () => {
imgSnapshotTest(
`graph LR
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
@@ -343,7 +343,7 @@ describe('Flowcart', () => {
);
});
- it('12: should render color of styled nodes', () => {
+ it('13: should render color of styled nodes', () => {
imgSnapshotTest(
`
graph LR
@@ -361,7 +361,7 @@ describe('Flowcart', () => {
);
});
- it('13: should render hexagons', () => {
+ it('14: should render hexagons', () => {
imgSnapshotTest(
`
graph TD
@@ -383,7 +383,7 @@ describe('Flowcart', () => {
);
});
- it('14: should render a simple flowchart with comments', () => {
+ it('15: should render a simple flowchart with comments', () => {
imgSnapshotTest(
`graph TD
A[Christmas] -->|Get money| B(Go shopping)
@@ -396,7 +396,7 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
- it('15: Render Stadium shape', () => {
+ it('16: Render Stadium shape', () => {
imgSnapshotTest(
` graph TD
A([stadium shape test])
@@ -412,7 +412,7 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
- it('16: Render Stadium shape', () => {
+ it('17: Render multiline texts', () => {
imgSnapshotTest(
`graph LR
A1[Multi
Line] -->|Multi
Line| B1(Multi
Line)
@@ -428,7 +428,7 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
- it('17: Chaining of nodes', () => {
+ it('18: Chaining of nodes', () => {
imgSnapshotTest(
`graph LR
a --> b --> c
@@ -436,7 +436,7 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
- it('18: Multiple nodes and chaining in one statement', () => {
+ it('19: Multiple nodes and chaining in one statement', () => {
imgSnapshotTest(
`graph LR
a --> b c--> d
@@ -444,7 +444,7 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
- it('19: Multiple nodes and chaining in one statement', () => {
+ it('20: Multiple nodes and chaining in one statement', () => {
imgSnapshotTest(
`graph TD
A[ h ] -- hello --> B[" test "]:::exClass C --> D;
@@ -453,4 +453,25 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
+ it('21: Render cylindrical shape', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[(cylindrical
shape
test)]
+ A -->|Get money| B1[(Go shopping 1)]
+ A -->|Get money| B2[(Go shopping 2)]
+ A -->|Get money| B3[(Go shopping 3)]
+ C[(Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?)]
+ B1 --> C
+ B2 --> C
+ B3 --> C
+ C -->|One| D[(Laptop)]
+ C -->|Two| E[(iPhone)]
+ C -->|Three| F[(Car)]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;`,
+ { flowchart: { htmlLabels: false } }
+ );
+ });
});
diff --git a/dist/index.html b/dist/index.html
index 0b3db789f..e5d675bfc 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -313,6 +313,24 @@ class A someclass;
classDef someclass fill:#f96;
class A someclass;
+
+ graph LR
+ A[(cylindrical
shape
test)]
+ A -->|Get money| B1[(Go shopping 1)]
+ A -->|Get money| B2[(Go shopping 2)]
+ A -->|Get money| B3[(Go shopping 3)]
+ C[(Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?)]
+ B1 --> C
+ B2 --> C
+ B3 --> C
+ C -->|One| D[(Laptop)]
+ C -->|Two| E[(iPhone)]
+ C -->|Three| F[(Car)]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+
graph LR
A1[Multi
Line] -->|Multi
Line| B1(Multi
Line)
diff --git a/docs/flowchart.md b/docs/flowchart.md
index 7f0603008..d5b27874b 100644
--- a/docs/flowchart.md
+++ b/docs/flowchart.md
@@ -89,6 +89,17 @@ graph LR
id1([This is the text in the box])
```
+### A node in a cylindrical shape
+
+```
+graph LR
+ id1[(Database)]
+```
+```mermaid
+graph LR
+ id1[(Database)]
+```
+
### A node in the form of a circle
```
diff --git a/src/diagrams/flowchart/flowChartShapes.js b/src/diagrams/flowchart/flowChartShapes.js
index 1da63ebe3..23cb53049 100644
--- a/src/diagrams/flowchart/flowChartShapes.js
+++ b/src/diagrams/flowchart/flowChartShapes.js
@@ -154,10 +154,74 @@ function stadium(parent, bbox, node) {
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;
diff --git a/src/diagrams/flowchart/flowChartShapes.spec.js b/src/diagrams/flowchart/flowChartShapes.spec.js
index 415a4f026..61e876d4b 100644
--- a/src/diagrams/flowchart/flowChartShapes.spec.js
+++ b/src/diagrams/flowchart/flowChartShapes.spec.js
@@ -23,6 +23,23 @@ describe('flowchart shapes', function() {
});
});
+ // 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
[
[
diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js
index 524742b39..f55213786 100644
--- a/src/diagrams/flowchart/flowRenderer.js
+++ b/src/diagrams/flowchart/flowRenderer.js
@@ -157,6 +157,9 @@ export const addVertices = function(vert, g, svgId) {
case 'stadium':
_shape = 'stadium';
break;
+ case 'cylinder':
+ _shape = 'cylinder';
+ break;
case 'group':
_shape = 'rect';
break;
diff --git a/src/diagrams/flowchart/flowRenderer.spec.js b/src/diagrams/flowchart/flowRenderer.spec.js
index fe31292cc..de8a6a485 100644
--- a/src/diagrams/flowchart/flowRenderer.spec.js
+++ b/src/diagrams/flowchart/flowRenderer.spec.js
@@ -23,6 +23,7 @@ describe('the flowchart renderer', function() {
['circle', 'circle'],
['ellipse', 'ellipse'],
['stadium', 'stadium'],
+ ['cylinder', 'cylinder'],
['group', 'rect']
].forEach(function([type, expectedShape, expectedRadios = 0]) {
it(`should add the correct shaped node to the graph for vertex type ${type}`, function() {
diff --git a/src/diagrams/flowchart/parser/flow.jison b/src/diagrams/flowchart/parser/flow.jison
index eebea9628..f7c339acf 100644
--- a/src/diagrams/flowchart/parser/flow.jison
+++ b/src/diagrams/flowchart/parser/flow.jison
@@ -85,6 +85,8 @@
"-)" return '-)';
"([" return 'STADIUMSTART';
"])" return 'STADIUMEND';
+"[(" return 'CYLINDERSTART';
+")]" return 'CYLINDEREND';
\- return 'MINUS';
"." return 'DOT';
[\_] return 'UNDERSCORE';
@@ -312,6 +314,8 @@ vertex: idString SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
| idString STADIUMSTART text STADIUMEND
{$$ = $1;yy.addVertex($1,$3,'stadium');}
+ | idString CYLINDERSTART text CYLINDEREND
+ {$$ = $1;yy.addVertex($1,$3,'cylinder');}
| idString PS text PE
{$$ = $1;yy.addVertex($1,$3,'round');}
| idString DIAMOND_START text DIAMOND_STOP
@@ -468,5 +472,5 @@ alphaNumToken : PUNCTUATION | UNICODE_TEXT | NUM| ALPHA | COLON | COMMA | PLUS
idStringToken : ALPHA|UNDERSCORE |UNICODE_TEXT | NUM| COLON | COMMA | PLUS | MINUS | DOWN |EQUALS | MULT | BRKT | DOT | PUNCTUATION;
-graphCodeTokens: STADIUMSTART | STADIUMEND | TRAPSTART | TRAPEND | INVTRAPSTART | INVTRAPEND | PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAGSTART | TAGEND | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI;
+graphCodeTokens: STADIUMSTART | STADIUMEND | CYLINDERSTART | CYLINDEREND | TRAPSTART | TRAPEND | INVTRAPSTART | INVTRAPEND | PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAGSTART | TAGEND | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI;
%%
diff --git a/src/themes/flowchart.scss b/src/themes/flowchart.scss
index 4673371b8..9bd8d9664 100644
--- a/src/themes/flowchart.scss
+++ b/src/themes/flowchart.scss
@@ -11,7 +11,8 @@
.node rect,
.node circle,
.node ellipse,
-.node polygon {
+.node polygon,
+.node path {
fill: $mainBkg;
stroke: $nodeBorder;
stroke-width: 1px;