From 59f3d2a11e1e3705f723638303789e62c7bf418c Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Wed, 22 Apr 2020 20:03:41 +0200 Subject: [PATCH 01/11] #1295 Fix for edges in subgraphs and handling of concurrent states in statediagram-v2 --- cypress/platform/current.html | 60 +++++++++++++--------- src/dagre-wrapper/clusters.js | 59 ++++++++++++++++++++- src/dagre-wrapper/index.js | 2 +- src/dagre-wrapper/mermaid-graphlib.js | 25 ++++++--- src/dagre-wrapper/mermaid-graphlib.spec.js | 28 ++++++++++ src/dagre-wrapper/nodes.js | 1 + src/dagre-wrapper/patterns.js | 54 +++++++++++++++++++ src/diagrams/state/stateRenderer-v2.js | 2 +- src/themes/state.scss | 8 ++- 9 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 src/dagre-wrapper/patterns.js diff --git a/cypress/platform/current.html b/cypress/platform/current.html index 01ce04d88..c5c1d2486 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -18,6 +18,14 @@

info below

+
+ flowchart LR + a --> b + + subgraph b [Test] + c --> d -->e + end +
flowchart LR a --> b @@ -40,7 +48,7 @@ Eating }
-
+
stateDiagram-v2 state Active { [*] --> NumLockOff @@ -73,8 +81,8 @@
stateDiagram-v2 [*] --> First - First --> Second -% First --> Third + First --> Third + First --> sec state First { [*] --> fir @@ -83,9 +91,14 @@ stateDiagram-v2 state Second { [*] --> sec sec --> [*] - } + } + state Third { + [*] --> thi + thi --> [*] + } + thi --> sec
-
+
flowchart TD subgraph A a @@ -101,7 +114,7 @@ flowchart TD A -- oAo --o B A --> C
-
+
flowchart TD subgraph A a @@ -133,28 +146,27 @@ stateDiagram-v2
stateDiagram-v2 - [*]-->TV + [*]-->TV - state TV { - [*] --> Off: Off to start with - On --> Off : Turn off - Off --> On : Turn on - } + state TV { + [*] --> Off: Off to start with + On --> Off : Turn off + Off --> On : Turn on + } - TV--> Console + TV--> Console - state Console { - [*] --> Off2: Off to start with - On2--> Off2 : Turn off - Off2 --> On2 : Turn on - On2-->Playing - - state Playing { - Alive --> Dead - Dead-->Alive - } - } + state Console { + [*] --> Off2: Off to start with + On2--> Off2 : Turn off + Off2 --> On2 : Turn on + On2-->Playing + state Playing { + Alive --> Dead + Dead-->Alive + } + }
diff --git a/src/dagre-wrapper/clusters.js b/src/dagre-wrapper/clusters.js index ac1163be8..a1f76477e 100644 --- a/src/dagre-wrapper/clusters.js +++ b/src/dagre-wrapper/clusters.js @@ -149,7 +149,64 @@ const roundedWithTitle = (parent, node) => { return shapeSvg; }; -const shapes = { rect, roundedWithTitle, noteGroup }; +const divider = (parent, node) => { + // Add outer g element + const shapeSvg = parent + .insert('g') + .attr('class', node.classes) + .attr('id', node.id); + + // add the rect + const rect = shapeSvg.insert('rect', ':first-child'); + + // Create the label and insert it after the rect + const label = shapeSvg.insert('g').attr('class', 'cluster-label'); + const innerRect = shapeSvg.append('rect'); + + const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle)); + + // Get the size of the label + const bbox = text.getBBox(); + + const padding = 0 * node.padding; + const halfPadding = padding / 2; + + // center the rect around its coordinate + rect + .attr('class', 'divider') + .attr('x', node.x - node.width / 2 - halfPadding) + .attr('y', node.y - node.height / 2 - halfPadding) + .attr('width', node.width + padding) + .attr('height', node.height + padding); + // innerRect + // .attr('class', 'inner') + // .attr('x', node.x - node.width / 2 - halfPadding) + // .attr('y', node.y - node.height / 2 - halfPadding + bbox.height - 1) + // .attr('width', node.width + padding) + // .attr('height', node.height + padding - bbox.height); + + // Center the label + label.attr( + 'transform', + 'translate(' + + (node.x - bbox.width / 2) + + ', ' + + (node.y - node.height / 2 - node.padding / 3 + 3) + + ')' + ); + + const rectBox = rect.node().getBBox(); + node.width = rectBox.width; + node.height = rectBox.height; + + node.intersect = function(point) { + return intersectRect(node, point); + }; + + return shapeSvg; +}; + +const shapes = { rect, roundedWithTitle, noteGroup, divider }; let clusterElems = {}; diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js index dca32ddc8..5d33e9a2e 100644 --- a/src/dagre-wrapper/index.js +++ b/src/dagre-wrapper/index.js @@ -14,7 +14,7 @@ import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } f import { logger as log } from '../logger'; const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { - log.trace('Graph in recursive render:', graphlib.json.write(graph), parentCluster); + log.info('Graph in recursive render:', graphlib.json.write(graph), parentCluster); const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line if (!graph.nodes()) { log.trace('No nodes found for', graph); diff --git a/src/dagre-wrapper/mermaid-graphlib.js b/src/dagre-wrapper/mermaid-graphlib.js index 9b8e61fdd..786af7b46 100644 --- a/src/dagre-wrapper/mermaid-graphlib.js +++ b/src/dagre-wrapper/mermaid-graphlib.js @@ -31,13 +31,17 @@ const isDecendant = (id, ancenstorId) => { }; const edgeInCluster = (edge, clusterId) => { + log.info('Decendants of ', clusterId, ' is ', decendants[clusterId]); + log.info('Edge is ', edge); // Edges to/from the cluster is not in the cluster, they are in the parent - if (!(edge.v === clusterId || edge.w === clusterId)) return false; + if (edge.v === clusterId) return false; + if (edge.w === clusterId) return false; if (!decendants[clusterId]) { log.debug('Tilt, ', clusterId, ',not in decendants'); return false; } + log.info('Here '); if (decendants[clusterId].indexOf(edge.v) >= 0) return true; if (isDecendant(edge.v, clusterId)) return true; @@ -80,17 +84,26 @@ const copy = (clusterId, graph, newGraph, rootId) => { const edges = graph.edges(node); log.debug('Copying Edges', edges); edges.forEach(edge => { - log.trace('Edge', edge); + log.info('Edge', edge); const data = graph.edge(edge.v, edge.w, edge.name); - log.trace('Edge data', data, rootId); + log.info('Edge data', data, rootId); try { // Do not copy edges in and out of the root cluster, they belong to the parent graph if (edgeInCluster(edge, rootId)) { - log.trace('Copying as ', edge.v, edge.w, data, edge.name); + log.info('Copying as ', edge.v, edge.w, data, edge.name); newGraph.setEdge(edge.v, edge.w, data, edge.name); - log.trace('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0])); + log.info('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0])); } else { - log.trace('Skipping copy of edge as ', rootId, edge.v, edge.w, clusterId); + log.info( + 'Skipping copy of edge ', + edge.v, + '-->', + edge.w, + ' rootId: ', + rootId, + ' clusterId:', + clusterId + ); } } catch (e) { log.error(e); diff --git a/src/dagre-wrapper/mermaid-graphlib.spec.js b/src/dagre-wrapper/mermaid-graphlib.spec.js index 33fea60de..73bc9a5a7 100644 --- a/src/dagre-wrapper/mermaid-graphlib.spec.js +++ b/src/dagre-wrapper/mermaid-graphlib.spec.js @@ -314,6 +314,34 @@ describe('Graphlib decorations', () => { // expect(edgeData.data).toBe('link2'); // expect(validate(g)).toBe(true); }); + it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB20', function () { + /* + a --> b + subgraph b [Test] + c --> d -->e + end + */ + g.setNode('a', { data: 1 }); + g.setNode('b', { data: 2 }); + g.setNode('c', { data: 3 }); + g.setNode('d', { data: 3 }); + g.setNode('e', { data: 3 }); + g.setParent('c', 'b'); + g.setParent('d', 'b'); + g.setParent('e', 'b'); + g.setEdge('a', 'b', { data: 'link1' }, '1'); + g.setEdge('c', 'd', { data: 'link2' }, '2'); + g.setEdge('d', 'e', { data: 'link2' }, '2'); + + logger.info('Graph before', graphlib.json.write(g)) + adjustClustersAndEdges(g); + const bGraph = g.node('b').graph; + // logger.trace('Graph after', graphlib.json.write(g)) + logger.info('Graph after', graphlib.json.write(bGraph)); + expect(bGraph.nodes().length).toBe(3); + expect(bGraph.edges().length).toBe(2); + }); + }); }); describe('extractDecendants', function () { diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index 08a0e36db..809f09701 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -253,6 +253,7 @@ const rect = (parent, node) => { const rect = shapeSvg.insert('rect', ':first-child'); rect + .attr('class', 'basic') .attr('rx', node.rx) .attr('ry', node.ry) .attr('x', -bbox.width / 2 - halfPadding) diff --git a/src/dagre-wrapper/patterns.js b/src/dagre-wrapper/patterns.js new file mode 100644 index 000000000..f607433d7 --- /dev/null +++ b/src/dagre-wrapper/patterns.js @@ -0,0 +1,54 @@ +/** + * Setup arrow head and define the marker. The result is appended to the svg. + */ + +import { logger } from '../logger'; + +// Only add the number of markers that the diagram needs +const insertPatterns = (elem, patternArray, type, id) => { + patternArray.forEach(patternName => { + patterns[patternName](elem, type, id); + }); +}; + +{ + /* + {' '} + + {' '} + + {' '} + + {' '} + {' '} + {' '} + {' '} +; */ +} + +const dots = (elem, type) => { + elem + .append('defs') + .append('marker') + .attr('id', type + '-barbEnd') + .attr('refX', 19) + .attr('refY', 7) + .attr('markerWidth', 20) + .attr('markerHeight', 14) + .attr('markerUnits', 0) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z'); +}; + +// TODO rename the class diagram markers to something shape descriptive and semanitc free +const patterns = { + dots +}; +export default insertPatterns; diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index f69d94ed3..5952f96dc 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -64,7 +64,7 @@ const setupNode = (g, parent, node, altFlag) => { if (!nodeDb[node.id].type && node.doc) { logger.info('Setting cluser for ', node.id); nodeDb[node.id].type = 'group'; - nodeDb[node.id].shape = 'roundedWithTitle'; + nodeDb[node.id].shape = node.type === 'divider' ? 'divider' : 'roundedWithTitle'; nodeDb[node.id].classes = nodeDb[node.id].classes + ' ' + diff --git a/src/themes/state.scss b/src/themes/state.scss index aa8bd4df3..6055db2d9 100644 --- a/src/themes/state.scss +++ b/src/themes/state.scss @@ -85,6 +85,8 @@ g.stateGroup line { fill: $nodeBkg; stroke: $nodeBorder; stroke-width: 1px; +} +.statediagram-cluster rect.outer { rx: 5px; ry: 5px; } @@ -100,10 +102,14 @@ g.stateGroup line { ry:0; } -.statediagram-state rect { +.statediagram-state rect.basic { rx: 5px; ry: 5px; } +.statediagram-state rect.divider { + stroke-dasharray: 10,10; + fill: #efefef; +} .note-edge { stroke-dasharray: 5; From 81d9b5afd6859041edbfe4e29d3fc44a56b42764 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Wed, 22 Apr 2020 20:05:36 +0200 Subject: [PATCH 02/11] #1295 Linting --- src/dagre-wrapper/clusters.js | 1 - src/dagre-wrapper/patterns.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dagre-wrapper/clusters.js b/src/dagre-wrapper/clusters.js index a1f76477e..8048ff1f4 100644 --- a/src/dagre-wrapper/clusters.js +++ b/src/dagre-wrapper/clusters.js @@ -161,7 +161,6 @@ const divider = (parent, node) => { // Create the label and insert it after the rect const label = shapeSvg.insert('g').attr('class', 'cluster-label'); - const innerRect = shapeSvg.append('rect'); const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle)); diff --git a/src/dagre-wrapper/patterns.js b/src/dagre-wrapper/patterns.js index f607433d7..aefe2b694 100644 --- a/src/dagre-wrapper/patterns.js +++ b/src/dagre-wrapper/patterns.js @@ -2,7 +2,7 @@ * Setup arrow head and define the marker. The result is appended to the svg. */ -import { logger } from '../logger'; +// import { logger } from '../logger'; // Only add the number of markers that the diagram needs const insertPatterns = (elem, patternArray, type, id) => { From 25ea221a6afa74ddf6d49a50264b5b7f95b17436 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 25 Apr 2020 14:11:05 +0200 Subject: [PATCH 03/11] #1295 Rendering tests for stateDiagram v2 --- .../rendering/stateDiagram-v2.spec.js | 358 ++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 cypress/integration/rendering/stateDiagram-v2.spec.js diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js new file mode 100644 index 000000000..b7913d9c6 --- /dev/null +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -0,0 +1,358 @@ +/* eslint-env jest */ +import { imgSnapshotTest } from '../../helpers/util'; + +describe('State diagram', () => { + it('should render a simple info', () => { + imgSnapshotTest( + ` + info + `, + { logLevel: 1 } + ); + cy.get('svg'); + }); + it('should render a simple state diagrams', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + + [*] --> State1 + State1 --> [*] + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a long descriptions instead of id when available', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + + [*] --> S1 + state "Some long name" as S1 + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a long descriptions with additional descriptions', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + + [*] --> S1 + state "Some long name" as S1: The description + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a single state with short descr', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + state "A long long name" as long1 + state "A" as longlonglongid + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a transition descrions with new lines', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + + [*] --> S1 + S1 --> S2: long line using
should work + S1 --> S3: long line using
should work + S1 --> S4: long line using \\nshould work + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a state with a note', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + State1: The state with a note + note right of State1 + Important information! You can write + notes. + end note + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a state with on the left side when so specified', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + State1: The state with a note with minus - and plus + in it + note left of State1 + Important information! You can write + notes with . and in them. + end note + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a state with a note together with another state', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + State1: The state with a note +,- + note right of State1 + Important information! You can write +,- + notes. + end note + State1 --> State2 : With +,- + note left of State2 : This is the note +,-
+ `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a note with multiple lines in it', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + State1: The state with a note + note right of State1 + Important information! You\ncan write + notes with multiple lines... + Here is another line... + And another line... + end note + `, + {} + ); + }); + it('should handle multiline notes with different line breaks', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + State1 + note right of State1 + Line1
Line2
Line3
Line4
Line5 + end note + `, + {} + ); + }); + + it('should render a states with descriptions including multi-line descriptions', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + State1: This a a single line description + State2: This a a multi line description + State2: here comes the multi part + [*] --> State1 + State1 --> State2 + State2 --> [*] + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a simple state diagrams', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + [*] --> State1 + State1 --> State2 + State1 --> State3 + State1 --> [*] + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a simple state diagrams with labels', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + [*] --> State1 + State1 --> State2 : Transition 1 + State1 --> State3 : Transition 2 + State1 --> State4 : Transition 3 + State1 --> State5 : Transition 4 + State2 --> State3 : Transition 5 + State1 --> [*] + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render state descriptions', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + state "Long state description" as XState1 + state "Another Long state description" as XState2 + XState2 : New line + XState1 --> XState2 + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render composit states', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + [*] --> NotShooting: Pacifist + NotShooting --> A + NotShooting --> B + NotShooting --> C + + state NotShooting { + [*] --> Idle: Yet another long long öong öong öong label + Idle --> Configuring : EvConfig + Configuring --> Idle : EvConfig EvConfig EvConfig EvConfig EvConfig + } + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render multiple composit states', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + [*]-->TV + + state TV { + [*] --> Off: Off to start with + On --> Off : Turn off + Off --> On : Turn on + } + + TV--> Console + + state Console { + [*] --> Off2: Off to start with + On2--> Off2 : Turn off + Off2 --> On2 : Turn on + On2-->Playing + + state Playing { + Alive --> Dead + Dead-->Alive + } + } + `, + { logLevel: 0 } + ); + }); + it('should render forks in composit states', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + [*]-->TV + + state TV { + state fork_state <<fork>> + [*] --> fork_state + fork_state --> State2 + fork_state --> State3 + + state join_state <<join>> + State2 --> join_state + State3 --> join_state + join_state --> State4 + State4 --> [*] + } + `, + { logLevel: 0 } + ); + }); + it('should render forks and joins', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + state fork_state <<fork>> + [*] --> fork_state + fork_state --> State2 + fork_state --> State3 + + state join_state <<join>> + State2 --> join_state + State3 --> join_state + join_state --> State4 + State4 --> [*] + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render conurrency states', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + [*] --> Active + + state Active { + [*] --> NumLockOff + NumLockOff --> NumLockOn : EvNumLockPressed + NumLockOn --> NumLockOff : EvNumLockPressed + -- + [*] --> CapsLockOff + CapsLockOff --> CapsLockOn : EvCapsLockPressed + CapsLockOn --> CapsLockOff : EvCapsLockPressed + -- + [*] --> ScrollLockOff + ScrollLockOff --> ScrollLockOn : EvCapsLockPressed + ScrollLockOn --> ScrollLockOff : EvCapsLockPressed + } + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render a state with states in it', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + state PilotCockpit { + state Parent { + C + } + } + `, + { + logLevel: 0, + } + ); + }); + it('Simplest composit state', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + state Parent { + C + } + `, + { + logLevel: 0, + } + ); + }); + it('should handle multiple arrows from one node to another', () => { + imgSnapshotTest( + ` + stateDiagram-v2 + a --> b: Start + a --> b: Stop + `, + { + logLevel: 0, + } + ); + }); + +}); From a1a7d44fd7118eecc116432a8a0e5218b331d654 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 25 Apr 2020 14:12:04 +0200 Subject: [PATCH 04/11] #1295 for Multi line labels separated with \n --- src/dagre-wrapper/createLabel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dagre-wrapper/createLabel.js b/src/dagre-wrapper/createLabel.js index a029f59fd..6fb944efa 100644 --- a/src/dagre-wrapper/createLabel.js +++ b/src/dagre-wrapper/createLabel.js @@ -3,7 +3,7 @@ const createLabel = (vertexText, style) => { svgLabel.setAttribute('style', style.replace('color:', 'fill:')); let rows = []; if (vertexText) { - rows = vertexText.split(/\n|/gi); + rows = vertexText.split(/\\n|\n|/gi); } for (let j = 0; j < rows.length; j++) { From b646672d3e43eb047f1e93952988b634e0b9351d Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 25 Apr 2020 14:12:35 +0200 Subject: [PATCH 05/11] #1295 Fix for statediagram labels --- src/dagre-wrapper/GraphObjects.md | 14 ++++++++++++++ src/dagre-wrapper/edges.js | 1 + src/dagre-wrapper/index.js | 14 +++++++------- src/diagrams/state/stateRenderer-v2.js | 2 ++ src/themes/flowchart.scss | 3 +++ 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/dagre-wrapper/GraphObjects.md b/src/dagre-wrapper/GraphObjects.md index cde45591d..5586b74bb 100644 --- a/src/dagre-wrapper/GraphObjects.md +++ b/src/dagre-wrapper/GraphObjects.md @@ -100,6 +100,20 @@ double_arrow_circle Lets try to make these types semantic free so that diagram type semantics does not find its way in to this more generic layer. +Required edgeData for proper rendering: + +| property | description | +| ---------- | ---------------------------------------- | +| id | Id of the edge | +| arrowHead | overlap between arrowHead and arrowType? | +| arrowType | overlap between arrowHead and arrowType? | +| style | | +| labelStyle | | +| label | overlap between label and labelText? | +| labelPos | | +| labelType | overlap between label and labelText? | + + # Markers Define what markers that should be included in the diagram with the insert markers function. The function takes two arguments, first the element in which the markers should be included and a list of the markers that should be added. diff --git a/src/dagre-wrapper/edges.js b/src/dagre-wrapper/edges.js index 0b5e474e2..b5e067c7f 100644 --- a/src/dagre-wrapper/edges.js +++ b/src/dagre-wrapper/edges.js @@ -33,6 +33,7 @@ export const insertEdgeLabel = (elem, edge) => { }; export const positionEdgeLabel = edge => { + logger.info('Moving label', edge.id, edge.label, edgeLabels[edge.id]); const el = edgeLabels[edge.id]; el.attr('transform', 'translate(' + edge.x + ', ' + edge.y + ')'); }; diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js index 5d33e9a2e..3e29d8640 100644 --- a/src/dagre-wrapper/index.js +++ b/src/dagre-wrapper/index.js @@ -79,14 +79,14 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { }); graph.edges().forEach(function(e) { - log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); + log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); }); - log.trace('#############################################'); - log.trace('### Layout ###'); - log.trace('#############################################'); - log.trace(graph); + log.info('#############################################'); + log.info('### Layout ###'); + log.info('#############################################'); + log.info(graph); dagre.layout(graph); - log.warn('Graph after layout:', graphlib.json.write(graph)); + log.trace('Graph after layout:', graphlib.json.write(graph)); // Move the nodes to the correct place graph.nodes().forEach(function(v) { const node = graph.node(v); @@ -119,7 +119,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { // Move the edge labels to the correct place after layout graph.edges().forEach(function(e) { const edge = graph.edge(e); - log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); + log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); insertEdge(edgePaths, edge, clusterDb, diagramtype); positionEdgeLabel(edge); diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index 5952f96dc..e6d9cedf2 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -155,10 +155,12 @@ const setupDoc = (g, parent, doc, altFlag) => { setupNode(g, parent, item.state1, altFlag); setupNode(g, parent, item.state2, altFlag); const edgeData = { + id: 'edge' + cnt, arrowhead: 'normal', arrowType: 'arrow_barb', style: 'fill:none', labelStyle: '', + label: item.description, arrowheadStyle: 'fill: #333', labelpos: 'c', labelType: 'text' diff --git a/src/themes/flowchart.scss b/src/themes/flowchart.scss index 9bd8d9664..495826cfc 100644 --- a/src/themes/flowchart.scss +++ b/src/themes/flowchart.scss @@ -36,6 +36,9 @@ .edgeLabel { background-color: $edgeLabelBackground; + rect { + opacity: 0.5; + } text-align: center; } From e52d4ce033b48dee19e256db1c51e5f3aafcf5fa Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 25 Apr 2020 14:18:23 +0200 Subject: [PATCH 06/11] #1295 Removing invalid title for concurrency clusters --- src/dagre-wrapper/clusters.js | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/dagre-wrapper/clusters.js b/src/dagre-wrapper/clusters.js index 8048ff1f4..ac13f484d 100644 --- a/src/dagre-wrapper/clusters.js +++ b/src/dagre-wrapper/clusters.js @@ -159,14 +159,6 @@ const divider = (parent, node) => { // add the rect const rect = shapeSvg.insert('rect', ':first-child'); - // Create the label and insert it after the rect - const label = shapeSvg.insert('g').attr('class', 'cluster-label'); - - const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle)); - - // Get the size of the label - const bbox = text.getBBox(); - const padding = 0 * node.padding; const halfPadding = padding / 2; @@ -174,25 +166,9 @@ const divider = (parent, node) => { rect .attr('class', 'divider') .attr('x', node.x - node.width / 2 - halfPadding) - .attr('y', node.y - node.height / 2 - halfPadding) + .attr('y', node.y - node.height / 2) .attr('width', node.width + padding) .attr('height', node.height + padding); - // innerRect - // .attr('class', 'inner') - // .attr('x', node.x - node.width / 2 - halfPadding) - // .attr('y', node.y - node.height / 2 - halfPadding + bbox.height - 1) - // .attr('width', node.width + padding) - // .attr('height', node.height + padding - bbox.height); - - // Center the label - label.attr( - 'transform', - 'translate(' + - (node.x - bbox.width / 2) + - ', ' + - (node.y - node.height / 2 - node.padding / 3 + 3) + - ')' - ); const rectBox = rect.node().getBBox(); node.width = rectBox.width; From 0aede618eca12ee8a9454f3e762d0794d2d6134f Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 25 Apr 2020 17:01:20 +0200 Subject: [PATCH 07/11] #1295 Adding support for multiline descriptions for states --- cypress/platform/current.html | 18 ++----- src/dagre-wrapper/createLabel.js | 7 ++- src/dagre-wrapper/nodes.js | 73 ++++++++++++++++++++++++++ src/diagrams/state/stateRenderer-v2.js | 7 ++- src/themes/state.scss | 8 +++ 5 files changed, 97 insertions(+), 16 deletions(-) diff --git a/cypress/platform/current.html b/cypress/platform/current.html index c5c1d2486..982e12b06 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -49,20 +49,10 @@ }
- stateDiagram-v2 - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } + stateDiagram-v2 + + [*] --> S1 + state "Some long name" as S1: The description
continues
stateDiagram diff --git a/src/dagre-wrapper/createLabel.js b/src/dagre-wrapper/createLabel.js index 6fb944efa..5e06cbc1a 100644 --- a/src/dagre-wrapper/createLabel.js +++ b/src/dagre-wrapper/createLabel.js @@ -1,4 +1,4 @@ -const createLabel = (vertexText, style) => { +const createLabel = (vertexText, style, isTitle) => { const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); svgLabel.setAttribute('style', style.replace('color:', 'fill:')); let rows = []; @@ -11,6 +11,11 @@ const createLabel = (vertexText, style) => { tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); tspan.setAttribute('x', '0'); + if (isTitle) { + tspan.setAttribute('class', 'title-row'); + } else { + tspan.setAttribute('class', 'row'); + } tspan.textContent = rows[j].trim(); svgLabel.appendChild(tspan); } diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index 809f09701..ee05244ce 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -1,6 +1,8 @@ import intersect from './intersect/index.js'; +import { select } from 'd3'; import { logger } from '../logger'; // eslint-disable-line import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util'; +import createLabel from './createLabel'; import note from './shapes/note'; const question = (parent, node) => { @@ -269,6 +271,76 @@ const rect = (parent, node) => { return shapeSvg; }; +const rectWithTitle = (parent, node) => { + // const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes); + + let classes; + if (!node.classes) { + classes = 'node default'; + } else { + classes = 'node ' + node.classes; + } + // Add outer g element + const shapeSvg = parent + .insert('g') + .attr('class', classes) + .attr('id', node.id); + + // Create the title label and insert it after the rect + const rect = shapeSvg.insert('rect', ':first-child'); + // const innerRect = shapeSvg.insert('rect'); + const innerLine = shapeSvg.insert('line'); + + const label = shapeSvg.insert('g').attr('class', 'label'); + + const text = label.node().appendChild(createLabel(node.labelText[0], node.labelStyle, true)); + const textRows = node.labelText.slice(1, node.labelText.length); + let titleBox = text.getBBox(); + const descr = label + .node() + .appendChild(createLabel(textRows.join('
'), node.labelStyle, true)); + + logger.info(descr); + const halfPadding = node.padding / 2; + select(descr).attr('transform', 'translate( 0' + ', ' + (titleBox.height + halfPadding) + ')'); + // Get the size of the label + + // Bounding box for title and text + const bbox = label.node().getBBox(); + + // Center the label + label.attr( + 'transform', + 'translate(' + -bbox.width / 2 + ', ' + (-bbox.height / 2 - halfPadding + 3) + ')' + ); + + rect + .attr('class', 'outer title-state') + .attr('x', -bbox.width / 2 - halfPadding) + .attr('y', -bbox.height / 2 - halfPadding) + .attr('width', bbox.width + node.padding) + .attr('height', bbox.height + node.padding); + // innerRect + // .attr('class', 'inner') + // .attr('x', -bbox.width / 2 - halfPadding) + // .attr('y', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding) + // .attr('width', bbox.width + node.padding) + // .attr('height', bbox.height + node.padding - titleBox.height - halfPadding); + innerLine + .attr('class', 'divider') + .attr('x1', -bbox.width / 2 - halfPadding) + .attr('x2', bbox.width / 2 + halfPadding) + .attr('y1', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding) + .attr('y2', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding); + + updateNodeBounds(node, rect); + + node.intersect = function(point) { + return intersect.rect(node, point); + }; + + return shapeSvg; +}; const stadium = (parent, node) => { const { shapeSvg, bbox } = labelHelper(parent, node); @@ -368,6 +440,7 @@ const end = (parent, node) => { const shapes = { question, rect, + rectWithTitle, circle, stadium, hexagon, diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index e6d9cedf2..763190f2e 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -54,7 +54,12 @@ const setupNode = (g, parent, node, altFlag) => { // Description if (node.description) { - nodeDb[node.id].description = node.description; + if (Array.isArray(node.description)) { + nodeDb[node.id].shape = 'rectWithTitle'; + nodeDb[node.id].description = node.description; + } else { + nodeDb[node.id].description = node.description; + } } // Save data for description and group so that for instance a statement without description overwrites diff --git a/src/themes/state.scss b/src/themes/state.scss index 6055db2d9..0fb7c0f86 100644 --- a/src/themes/state.scss +++ b/src/themes/state.scss @@ -90,6 +90,14 @@ g.stateGroup line { rx: 5px; ry: 5px; } +.statediagram-state .divider { + stroke: $nodeBorder; +} + +.statediagram-state .title-state { + rx: 5px; + ry: 5px; +} .statediagram-cluster.statediagram-cluster .inner { fill: white; } From 5662c06a33415c09fd0a0696c79a8a7923d951f2 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sun, 26 Apr 2020 09:47:47 +0200 Subject: [PATCH 08/11] #1295 Adding support for multiline descriptions for states --- cypress/platform/current.html | 12 ++++++++---- src/diagrams/state/stateDb.js | 1 + src/diagrams/state/stateRenderer-v2.js | 21 +++++++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cypress/platform/current.html b/cypress/platform/current.html index 982e12b06..1e2567ea3 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -49,10 +49,14 @@ }
- stateDiagram-v2 - - [*] --> S1 - state "Some long name" as S1: The description
continues + stateDiagram-v2 + State1: This a a single line description + State2: This a a multi line description + State2: here comes the multi part + a + [*] --> State1 + State1 --> State2 + State2 --> [*]
stateDiagram diff --git a/src/diagrams/state/stateDb.js b/src/diagrams/state/stateDb.js index c56dcd1a3..b5609bd40 100644 --- a/src/diagrams/state/stateDb.js +++ b/src/diagrams/state/stateDb.js @@ -132,6 +132,7 @@ export const addState = function(id, type, doc, descr, note) { } } if (descr) { + logger.info('Adding state ', id, descr); if (typeof descr === 'string') addDescription(id, descr.trim()); if (typeof descr === 'object') { diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index 763190f2e..1399b44bd 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -52,13 +52,26 @@ const setupNode = (g, parent, node, altFlag) => { }; } - // Description + // Build of the array of description strings accordinging if (node.description) { - if (Array.isArray(node.description)) { + if (Array.isArray(nodeDb[node.id].description)) { + // There already is an array of strings,add to it nodeDb[node.id].shape = 'rectWithTitle'; - nodeDb[node.id].description = node.description; + nodeDb[node.id].description.push(node.description); } else { - nodeDb[node.id].description = node.description; + if (nodeDb[node.id].description.length > 0) { + // if there is a description already transformit to an array + nodeDb[node.id].shape = 'rectWithTitle'; + if (nodeDb[node.id].description === node.id) { + // If the previous description was the is, remove it + nodeDb[node.id].description = [node.description]; + } else { + nodeDb[node.id].description = [nodeDb[node.id].description, node.description]; + } + } else { + nodeDb[node.id].shape = 'rect'; + nodeDb[node.id].description = node.description; + } } } From fd37edc53fa457bbf469ffc237497a5f960ff917 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sun, 26 Apr 2020 10:07:25 +0200 Subject: [PATCH 09/11] #1295 Adding support for forks and joins --- cypress/platform/current.html | 23 ++++++++++------ src/dagre-wrapper/nodes.js | 37 +++++++++++++++++++++----- src/diagrams/state/stateRenderer-v2.js | 3 +++ 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/cypress/platform/current.html b/cypress/platform/current.html index 1e2567ea3..5cdd66332 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -49,14 +49,21 @@ }
- stateDiagram-v2 - State1: This a a single line description - State2: This a a multi line description - State2: here comes the multi part - a - [*] --> State1 - State1 --> State2 - State2 --> [*] + stateDiagram-v2 + [*]-->TV + + state TV { + state fork_state <<fork>> + [*] --> fork_state + fork_state --> State2 + fork_state --> State3 + + state join_state <<join>> + State2 --> join_state + State3 --> join_state + join_state --> State4 + State4 --> [*] + }
stateDiagram diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index ee05244ce..a67d078b5 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -320,12 +320,7 @@ const rectWithTitle = (parent, node) => { .attr('y', -bbox.height / 2 - halfPadding) .attr('width', bbox.width + node.padding) .attr('height', bbox.height + node.padding); - // innerRect - // .attr('class', 'inner') - // .attr('x', -bbox.width / 2 - halfPadding) - // .attr('y', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding) - // .attr('width', bbox.width + node.padding) - // .attr('height', bbox.height + node.padding - titleBox.height - halfPadding); + innerLine .attr('class', 'divider') .attr('x1', -bbox.width / 2 - halfPadding) @@ -408,6 +403,32 @@ const start = (parent, node) => { return shapeSvg; }; + +const forkJoin = (parent, node) => { + const shapeSvg = parent + .insert('g') + .attr('class', 'node default') + .attr('id', node.id); + + const shape = shapeSvg + .append('rect') + .style('stroke', 'black') + .style('fill', 'black') + .attr('x', -35) + .attr('y', -5) + .attr('width', 70) + .attr('height', 10) + .attr('class', 'fork-join'); + + updateNodeBounds(node, shape); + node.height = node.height + node.padding / 2; + node.intersect = function(point) { + return intersect.rect(node, point); + }; + + return shapeSvg; +}; + const end = (parent, node) => { const shapeSvg = parent .insert('g') @@ -453,7 +474,9 @@ const shapes = { cylinder, start, end, - note + note, + fork: forkJoin, + join: forkJoin }; let nodeElems = {}; diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index 1399b44bd..d14e46a09 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -42,6 +42,9 @@ const setupNode = (g, parent, node, altFlag) => { if (node.start === false) { shape = 'end'; } + if (node.type !== 'default') { + shape = node.type; + } if (!nodeDb[node.id]) { nodeDb[node.id] = { From 76b4b88e4b6f5ba0988e6a6839bd3700a5f0db02 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sun, 26 Apr 2020 12:57:45 +0200 Subject: [PATCH 10/11] #1295 Alternating graph directions for subgraphs --- src/dagre-wrapper/index.js | 5 +++-- src/dagre-wrapper/mermaid-graphlib.js | 4 +++- src/dagre-wrapper/nodes.js | 23 ++++++++++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js index 3e29d8640..1c684f0a8 100644 --- a/src/dagre-wrapper/index.js +++ b/src/dagre-wrapper/index.js @@ -15,6 +15,7 @@ import { logger as log } from '../logger'; const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { log.info('Graph in recursive render:', graphlib.json.write(graph), parentCluster); + const dir = graph.graph().rankdir; const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line if (!graph.nodes()) { log.trace('No nodes found for', graph); @@ -59,7 +60,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { // insertCluster(clusters, graph.node(v)); } else { log.trace('Node - the non recursive path', v, node.id, node); - insertNode(nodes, graph.node(v)); + insertNode(nodes, graph.node(v), dir); } } }); @@ -138,7 +139,7 @@ export const render = (elem, graph, markers, diagramtype, id) => { log.warn('Graph before:', graphlib.json.write(graph)); adjustClustersAndEdges(graph); log.warn('Graph after:', graphlib.json.write(graph)); - + log.warn('Graph ever after:', graph.graph()); recursiveRender(elem, graph, diagramtype); }; diff --git a/src/dagre-wrapper/mermaid-graphlib.js b/src/dagre-wrapper/mermaid-graphlib.js index 786af7b46..c9cbd1fd9 100644 --- a/src/dagre-wrapper/mermaid-graphlib.js +++ b/src/dagre-wrapper/mermaid-graphlib.js @@ -329,12 +329,14 @@ export const extractor = (graph, depth) => { depth ); + const graphSettings = graph.graph(); + const clusterGraph = new graphlib.Graph({ multigraph: true, compound: true }) .setGraph({ - rankdir: 'TB', + rankdir: graphSettings.rankdir === 'TB' ? 'LR' : 'TB', // Todo: set proper spacing nodesep: 50, ranksep: 50, diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index a67d078b5..5da5e6d76 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -404,24 +404,33 @@ const start = (parent, node) => { return shapeSvg; }; -const forkJoin = (parent, node) => { +const forkJoin = (parent, node, dir) => { const shapeSvg = parent .insert('g') .attr('class', 'node default') .attr('id', node.id); + let width = 70; + let height = 10; + + if (dir === 'LR') { + width = 10; + height = 70; + } + const shape = shapeSvg .append('rect') .style('stroke', 'black') .style('fill', 'black') - .attr('x', -35) - .attr('y', -5) - .attr('width', 70) - .attr('height', 10) + .attr('x', (-1 * width) / 2) + .attr('y', (-1 * height) / 2) + .attr('width', width) + .attr('height', height) .attr('class', 'fork-join'); updateNodeBounds(node, shape); node.height = node.height + node.padding / 2; + node.width = node.width + node.padding / 2; node.intersect = function(point) { return intersect.rect(node, point); }; @@ -481,8 +490,8 @@ const shapes = { let nodeElems = {}; -export const insertNode = (elem, node) => { - nodeElems[node.id] = shapes[node.shape](elem, node); +export const insertNode = (elem, node, dir) => { + nodeElems[node.id] = shapes[node.shape](elem, node, dir); }; export const setNodeElem = (elem, node) => { nodeElems[node.id] = elem; From 507582f40b478be693b857d9efea996874206d15 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sun, 26 Apr 2020 16:01:17 +0200 Subject: [PATCH 11/11] #1295 Bugfix for descriptions --- .../rendering/stateDiagram-v2.spec.js | 2 +- cypress/platform/current.html | 27 ++++++++++++------- src/dagre-wrapper/createLabel.js | 6 ++++- src/dagre-wrapper/index.js | 2 ++ src/dagre-wrapper/nodes.js | 7 +++-- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index b7913d9c6..93b6f4acc 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -290,7 +290,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('should render conurrency states', () => { + it('should render concurrency states', () => { imgSnapshotTest( ` stateDiagram-v2 diff --git a/cypress/platform/current.html b/cypress/platform/current.html index 5cdd66332..26b0d88cc 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -39,16 +39,25 @@ G-->H G-->c
-
- stateDiagram-v2 - [*] --> monkey - state monkey { - Sitting - -- - Eating - } -
+stateDiagram-v2 + [*] --> Active + + state Active { + [*] --> NumLockOff + NumLockOff --> NumLockOn : EvNumLockPressed + NumLockOn --> NumLockOff : EvNumLockPressed + -- + [*] --> CapsLockOff + CapsLockOff --> CapsLockOn : EvCapsLockPressed + CapsLockOn --> CapsLockOff : EvCapsLockPressed + -- + [*] --> ScrollLockOff + ScrollLockOff --> ScrollLockOn : EvCapsLockPressed + ScrollLockOn --> ScrollLockOff : EvCapsLockPressed + } +
+
stateDiagram-v2 [*]-->TV diff --git a/src/dagre-wrapper/createLabel.js b/src/dagre-wrapper/createLabel.js index 5e06cbc1a..4c430b9f9 100644 --- a/src/dagre-wrapper/createLabel.js +++ b/src/dagre-wrapper/createLabel.js @@ -2,8 +2,12 @@ const createLabel = (vertexText, style, isTitle) => { const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); svgLabel.setAttribute('style', style.replace('color:', 'fill:')); let rows = []; - if (vertexText) { + if (typeof vertexText === 'string') { rows = vertexText.split(/\\n|\n|/gi); + } else if (Array.isArray(vertexText)) { + rows = vertexText; + } else { + rows = []; } for (let j = 0; j < rows.length; j++) { diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js index 1c684f0a8..9e2f7b851 100644 --- a/src/dagre-wrapper/index.js +++ b/src/dagre-wrapper/index.js @@ -16,6 +16,8 @@ import { logger as log } from '../logger'; const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { log.info('Graph in recursive render:', graphlib.json.write(graph), parentCluster); const dir = graph.graph().rankdir; + log.warn('Dir in recursive render - dir:', dir); + const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line if (!graph.nodes()) { log.trace('No nodes found for', graph); diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index 5da5e6d76..3a1370f3f 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -293,8 +293,11 @@ const rectWithTitle = (parent, node) => { const label = shapeSvg.insert('g').attr('class', 'label'); - const text = label.node().appendChild(createLabel(node.labelText[0], node.labelStyle, true)); - const textRows = node.labelText.slice(1, node.labelText.length); + const text2 = node.labelText.flat(); + logger.info('Label text', text2[0]); + + const text = label.node().appendChild(createLabel(text2[0], node.labelStyle, true)); + const textRows = text2.slice(1, text2.length); let titleBox = text.getBBox(); const descr = label .node()