From 51e902cd36430be1fb6ea0d15bfe789bc690ed7e Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:10:58 -0400 Subject: [PATCH 01/13] Added the hex shape to the renderer, and support for it in the flow parser. Need to work on properly sizing it. --- src/diagrams/flowchart/flowRenderer.js | 34 ++++++++++++++++++++++++ src/diagrams/flowchart/parser/flow.jison | 4 +++ 2 files changed, 38 insertions(+) diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index ef696a8f7..189407909 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -121,6 +121,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; @@ -358,6 +361,37 @@ export const draw = function(text, id) { return shapeSvg; }; + render.shapes().hexagon = function(parent, bbox, node) { + const w = bbox.width; + const h = bbox.height; + const s = (w + h) * 0.9; + const points = [ + { x: s / 4, y: 0 }, + { x: (3 * s) / 4, y: 0 }, + { x: s, y: -s / 2 }, + { x: (3 * s) / 4, y: -s }, + { x: s / 4, 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; 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 From b300a4decbed65323698afc19f8ef2157ad695d2 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:23:30 -0400 Subject: [PATCH 02/13] #530 Fit hexagon to width and height The hexagon shape in flow chart now fits the width and height independently, so it can better fit large content without taking up too much space. --- src/diagrams/flowchart/flowRenderer.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index 189407909..353400efa 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -362,16 +362,16 @@ export const draw = function(text, id) { }; render.shapes().hexagon = function(parent, bbox, node) { - const w = bbox.width; + const q = 7; + const w = (q / (q - 2)) * bbox.width; const h = bbox.height; - const s = (w + h) * 0.9; const points = [ - { x: s / 4, y: 0 }, - { x: (3 * s) / 4, y: 0 }, - { x: s, y: -s / 2 }, - { x: (3 * s) / 4, y: -s }, - { x: s / 4, y: -s }, - { x: 0, y: -s / 2 } + { x: w / q, y: 0 }, + { x: (w * (q - 1)) / q, y: 0 }, + { x: w, y: -h / 2 }, + { x: (w * (q - 1)) / q, y: -h }, + { x: w / q, y: -h }, + { x: 0, y: -h / 2 } ]; const shapeSvg = parent .insert('polygon', ':first-child') @@ -385,7 +385,7 @@ export const draw = function(text, id) { ) .attr('rx', 5) .attr('ry', 5) - .attr('transform', 'translate(' + -s / 2 + ',' + (s * 2) / 4 + ')'); + .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')'); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; From 57921e3f524db668ab1f342353b45ef7b538747c Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:31:41 -0400 Subject: [PATCH 03/13] #530 Add Hex example to index.html --- dist/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.html b/dist/index.html index 027b17213..8c38bf514 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] @@ -443,7 +443,7 @@ Class08 <--> C2: Cool label const testLineEndings = (test, input) => { try { mermaid.render(test, input, () => {}); - } catch (err) { + } catch (err) { console.error("Error in %s:\n\n%s", test, err); } }; From 550f91aa6879c8de0da4e7c284c45581c52a3327 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:36:02 -0400 Subject: [PATCH 04/13] #530 Better shaping of hexagon The "corner" triangles are a fixed ratio to the height, so the triangles will always be mathemtically similar. --- src/diagrams/flowchart/flowRenderer.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index 353400efa..9b5423109 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -362,15 +362,16 @@ export const draw = function(text, id) { }; render.shapes().hexagon = function(parent, bbox, node) { - const q = 7; - const w = (q / (q - 2)) * bbox.width; + const f = 4; const h = bbox.height; + const m = h / 4; + const w = bbox.width + 2 * m; const points = [ - { x: w / q, y: 0 }, - { x: (w * (q - 1)) / q, y: 0 }, + { x: m, y: 0 }, + { x: w - m, y: 0 }, { x: w, y: -h / 2 }, - { x: (w * (q - 1)) / q, y: -h }, - { x: w / q, y: -h }, + { x: w - m, y: -h }, + { x: m, y: -h }, { x: 0, y: -h / 2 } ]; const shapeSvg = parent @@ -385,7 +386,7 @@ export const draw = function(text, id) { ) .attr('rx', 5) .attr('ry', 5) - .attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')'); + .attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; From 1920e9f758a4e317d684258ef40b5ef249a00341 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:38:41 -0400 Subject: [PATCH 05/13] #530 Add hexagon to flow docs --- docs/flowchart.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/flowchart.md b/docs/flowchart.md index ea778697c..5ea9e2c8e 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 @@ -350,7 +361,7 @@ Beginners tip, a full example using interactive links in a html context: click A callback "Tooltip" click B "http://www.github.com" "This is a link"
- + From e1446ce38ae80851d1cc0249d65dbfef668ea0eb Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:44:56 -0400 Subject: [PATCH 06/13] #530 Add unit test for hex nodes --- src/diagrams/flowchart/parser/flow.spec.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/diagrams/flowchart/parser/flow.spec.js b/src/diagrams/flowchart/parser/flow.spec.js index 778691e89..a1aec2949 100644 --- a/src/diagrams/flowchart/parser/flow.spec.js +++ b/src/diagrams/flowchart/parser/flow.spec.js @@ -1402,6 +1402,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);'); From 01fd54dd6f622fb0358989d8cdfb27696370899e Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Wed, 2 Oct 2019 22:51:12 -0400 Subject: [PATCH 07/13] #530 Encapsulate some redudant flow shape code --- src/diagrams/flowchart/flowRenderer.js | 44 +++++++++++--------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index 9b5423109..d64294946 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -342,19 +342,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, s, s, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -374,19 +362,7 @@ export const draw = function(text, id) { { x: m, 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('rx', 5) - .attr('ry', 5) - .attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -656,6 +632,22 @@ export const draw = function(text, id) { } }; +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('rx', 5) + .attr('ry', 5) + .attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); +} + export default { setConf, addVertices, From c9f84ccae5f95854724fa69995dde826827e8593 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Thu, 3 Oct 2019 06:41:36 -0400 Subject: [PATCH 08/13] #530 Testing Coverage --- src/diagrams/flowchart/parser/flow.spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/diagrams/flowchart/parser/flow.spec.js b/src/diagrams/flowchart/parser/flow.spec.js index a1aec2949..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};'); From 20b28666311b4700d63141bd6b29e98566a3c7b5 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Fri, 4 Oct 2019 21:21:00 -0400 Subject: [PATCH 09/13] #530 Further encapsulation of common code --- src/diagrams/flowchart/flowRenderer.js | 74 +++----------------------- 1 file changed, 6 insertions(+), 68 deletions(-) diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index d64294946..475c2b611 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -380,17 +380,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -407,17 +397,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -434,17 +414,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -461,17 +431,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -488,17 +448,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -516,17 +466,7 @@ export const draw = function(text, id) { { 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 + ')'); + const shapeSvg = insertPolygonShape(parent, w, h, points); node.intersect = function(point) { return dagreD3.intersect.polygon(node, points, point); }; @@ -643,8 +583,6 @@ function insertPolygonShape(parent, w, h, points) { }) .join(' ') ) - .attr('rx', 5) - .attr('ry', 5) .attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); } From 9a0a5ca804f4898d8c5d12679b55c8530acfb645 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Fri, 4 Oct 2019 22:03:20 -0400 Subject: [PATCH 10/13] #530 Add some unit tests for flowRenderer.addVertices --- docs/mermaidAPI.md | 2 +- src/diagrams/flowchart/flowRenderer.spec.js | 58 +++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/diagrams/flowchart/flowRenderer.spec.js 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/flowRenderer.spec.js b/src/diagrams/flowchart/flowRenderer.spec.js new file mode 100644 index 000000000..cdfd97a24 --- /dev/null +++ b/src/diagrams/flowchart/flowRenderer.spec.js @@ -0,0 +1,58 @@ +import { addVertices } from './flowRenderer'; +import { setConfig } from '../../config'; + +setConfig({ + securityLevel: 'strict', + 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); + }); + }); + }); +}); From dcbcbf40a0fe9a1160f79a914e4b94d4ffc6e741 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Fri, 4 Oct 2019 22:38:27 -0400 Subject: [PATCH 11/13] #530 Started adding some tests around how flowchart shapes are rendered in SVG --- src/diagrams/flowchart/flowChartShapes.js | 162 ++++++++++++++++++ .../flowchart/flowChartShapes.spec.js | 89 ++++++++++ src/diagrams/flowchart/flowRenderer.js | 158 +---------------- src/diagrams/flowchart/flowRenderer.spec.js | 1 - 4 files changed, 254 insertions(+), 156 deletions(-) create mode 100644 src/diagrams/flowchart/flowChartShapes.js create mode 100644 src/diagrams/flowchart/flowChartShapes.spec.js diff --git a/src/diagrams/flowchart/flowChartShapes.js b/src/diagrams/flowchart/flowChartShapes.js new file mode 100644 index 000000000..7bfb7aeaf --- /dev/null +++ b/src/diagrams/flowchart/flowChartShapes.js @@ -0,0 +1,162 @@ +import dagreD3 from 'dagre-d3-renderer'; + +export function addToRender(render) { + 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 = insertPolygonShape(parent, s, s, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; + }; + + render.shapes().hexagon = function(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; + }; + + // 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 = insertPolygonShape(parent, w, h, points); + 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 = insertPolygonShape(parent, w, h, points); + 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 = insertPolygonShape(parent, w, h, points); + 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 = insertPolygonShape(parent, w, h, points); + 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 = insertPolygonShape(parent, w, h, points); + 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 = insertPolygonShape(parent, w, h, points); + node.intersect = function(point) { + return dagreD3.intersect.polygon(node, points, point); + }; + return shapeSvg; + }; +} + +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..4d63314ae --- /dev/null +++ b/src/diagrams/flowchart/flowChartShapes.spec.js @@ -0,0 +1,89 @@ +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; + }, + function(w, h) { + return h; + } + ], + [ + 'rect_left_inv_arrow', + 5, + function(w) { + return w; + }, + function(w, h) { + return h; + } + ] + ].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; + } + }; +} diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index 475c2b611..f170f94bf 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) { @@ -331,147 +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 = insertPolygonShape(parent, s, s, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; - - render.shapes().hexagon = function(parent, bbox, node) { - const f = 4; - const h = bbox.height; - const m = h / 4; - 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; - }; - - // 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 = insertPolygonShape(parent, w, h, points); - 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 = insertPolygonShape(parent, w, h, points); - 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 = insertPolygonShape(parent, w, h, points); - 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 = insertPolygonShape(parent, w, h, points); - 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 = insertPolygonShape(parent, w, h, points); - 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 = insertPolygonShape(parent, w, h, points); - 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) { @@ -572,20 +434,6 @@ export const draw = function(text, id) { } }; -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 { setConf, addVertices, diff --git a/src/diagrams/flowchart/flowRenderer.spec.js b/src/diagrams/flowchart/flowRenderer.spec.js index cdfd97a24..3b8c72a44 100644 --- a/src/diagrams/flowchart/flowRenderer.spec.js +++ b/src/diagrams/flowchart/flowRenderer.spec.js @@ -2,7 +2,6 @@ import { addVertices } from './flowRenderer'; import { setConfig } from '../../config'; setConfig({ - securityLevel: 'strict', flowchart: { htmlLabels: false } From cc731fe3c4bce077f190d92d6aa7d4d8493ec63c Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Sat, 5 Oct 2019 09:38:03 -0400 Subject: [PATCH 12/13] #530 Pull shape functions out to shorten functions --- src/diagrams/flowchart/flowChartShapes.js | 271 ++++++++++++---------- 1 file changed, 143 insertions(+), 128 deletions(-) diff --git a/src/diagrams/flowchart/flowChartShapes.js b/src/diagrams/flowchart/flowChartShapes.js index 7bfb7aeaf..4ceecbf29 100644 --- a/src/diagrams/flowchart/flowChartShapes.js +++ b/src/diagrams/flowchart/flowChartShapes.js @@ -1,146 +1,161 @@ 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 = 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 = insertPolygonShape(parent, s, s, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; - - render.shapes().hexagon = function(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; - }; + 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 = 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 = insertPolygonShape(parent, w, h, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; + 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 = 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 = insertPolygonShape(parent, w, h, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; + render.shapes().lean_right = lean_right; // 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 = insertPolygonShape(parent, w, h, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; + render.shapes().lean_left = lean_left; // 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 = insertPolygonShape(parent, w, h, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; + render.shapes().trapezoid = trapezoid; // 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 = insertPolygonShape(parent, w, h, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; + render.shapes().inv_trapezoid = inv_trapezoid; // 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 = insertPolygonShape(parent, w, h, points); - node.intersect = function(point) { - return dagreD3.intersect.polygon(node, points, point); - }; - return shapeSvg; - }; + render.shapes().rect_right_inv_arrow = rect_right_inv_arrow; } function insertPolygonShape(parent, w, h, points) { From c9fe948b903fb3e74ef31ff115bb28c73bc311e7 Mon Sep 17 00:00:00 2001 From: Brian Mearns Date: Sat, 5 Oct 2019 13:48:20 -0400 Subject: [PATCH 13/13] #530 Finished basic unit tests for flow shapes --- src/diagrams/flowchart/flowChartShapes.js | 2 +- .../flowchart/flowChartShapes.spec.js | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/diagrams/flowchart/flowChartShapes.js b/src/diagrams/flowchart/flowChartShapes.js index 4ceecbf29..e9b17ae89 100644 --- a/src/diagrams/flowchart/flowChartShapes.js +++ b/src/diagrams/flowchart/flowChartShapes.js @@ -54,7 +54,7 @@ function rect_left_inv_arrow(parent, bbox, node) { return shapeSvg; } -function lean_right (parent, bbox, node) { +function lean_right(parent, bbox, node) { const w = bbox.width; const h = bbox.height; const points = [ diff --git a/src/diagrams/flowchart/flowChartShapes.spec.js b/src/diagrams/flowchart/flowChartShapes.spec.js index 4d63314ae..de3f05a1d 100644 --- a/src/diagrams/flowchart/flowChartShapes.spec.js +++ b/src/diagrams/flowchart/flowChartShapes.spec.js @@ -18,20 +18,14 @@ describe('flowchart shapes', function() { function(w, h) { return w + h / 2; }, - function(w, h) { - return h; - } + useHeight ], - [ - 'rect_left_inv_arrow', - 5, - function(w) { - return w; - }, - function(w, h) { - return h; - } - ] + ['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(); @@ -87,3 +81,11 @@ function MockSvg(tag, ...args) { } }; } + +function useWidth(w, h) { + return w; +} + +function useHeight(w, h) { + return h; +}