From bc59d015e6dde5bfe0f84e1fda25c52d12a070f3 Mon Sep 17 00:00:00 2001 From: Marc Faber Date: Tue, 31 Dec 2019 16:30:03 +0100 Subject: [PATCH] #1154 Flow diagram DB shape request --- .../integration/rendering/flowchart.spec.js | 55 +++++++++++----- dist/index.html | 18 ++++++ docs/flowchart.md | 11 ++++ src/diagrams/flowchart/flowChartShapes.js | 64 +++++++++++++++++++ .../flowchart/flowChartShapes.spec.js | 17 +++++ src/diagrams/flowchart/flowRenderer.js | 3 + src/diagrams/flowchart/flowRenderer.spec.js | 1 + src/diagrams/flowchart/parser/flow.jison | 6 +- src/themes/flowchart.scss | 3 +- 9 files changed, 159 insertions(+), 19 deletions(-) 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;