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 }