stateDiagram-v2
[*] --> First
- First --> Second
-% First --> Third
+ First --> Third
+ First --> sec
state First {
[*] --> fir
@@ -83,9 +101,14 @@ stateDiagram-v2
state Second {
[*] --> sec
sec --> [*]
- }
+ }
+ state Third {
+ [*] --> thi
+ thi --> [*]
+ }
+ thi --> sec
-
+
flowchart TD
subgraph A
a
@@ -101,7 +124,7 @@ flowchart TD
A -- oAo --o B
A --> C
-
+
flowchart TD
subgraph A
a
@@ -133,28 +156,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/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/clusters.js b/src/dagre-wrapper/clusters.js
index ac1163be8..ac13f484d 100644
--- a/src/dagre-wrapper/clusters.js
+++ b/src/dagre-wrapper/clusters.js
@@ -149,7 +149,39 @@ 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');
+
+ 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)
+ .attr('width', node.width + padding)
+ .attr('height', node.height + padding);
+
+ 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/createLabel.js b/src/dagre-wrapper/createLabel.js
index a029f59fd..4c430b9f9 100644
--- a/src/dagre-wrapper/createLabel.js
+++ b/src/dagre-wrapper/createLabel.js
@@ -1,9 +1,13 @@
-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 = [];
- if (vertexText) {
- rows = vertexText.split(/\n| /gi);
+ 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++) {
@@ -11,6 +15,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/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 dca32ddc8..9e2f7b851 100644
--- a/src/dagre-wrapper/index.js
+++ b/src/dagre-wrapper/index.js
@@ -14,7 +14,10 @@ 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 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);
@@ -59,7 +62,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);
}
}
});
@@ -79,14 +82,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 +122,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);
@@ -138,7 +141,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 9b8e61fdd..c9cbd1fd9 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);
@@ -316,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/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..3a1370f3f 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) => {
@@ -253,6 +255,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)
@@ -268,6 +271,74 @@ 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 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()
+ .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);
+
+ 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);
@@ -335,6 +406,41 @@ const start = (parent, node) => {
return shapeSvg;
};
+
+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', (-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);
+ };
+
+ return shapeSvg;
+};
+
const end = (parent, node) => {
const shapeSvg = parent
.insert('g')
@@ -367,6 +473,7 @@ const end = (parent, node) => {
const shapes = {
question,
rect,
+ rectWithTitle,
circle,
stadium,
hexagon,
@@ -379,13 +486,15 @@ const shapes = {
cylinder,
start,
end,
- note
+ note,
+ fork: forkJoin,
+ join: forkJoin
};
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;
diff --git a/src/dagre-wrapper/patterns.js b/src/dagre-wrapper/patterns.js
new file mode 100644
index 000000000..aefe2b694
--- /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/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 f69d94ed3..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] = {
@@ -52,9 +55,27 @@ const setupNode = (g, parent, node, altFlag) => {
};
}
- // Description
+ // Build of the array of description strings accordinging
if (node.description) {
- nodeDb[node.id].description = 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.push(node.description);
+ } else {
+ 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;
+ }
+ }
}
// Save data for description and group so that for instance a statement without description overwrites
@@ -64,7 +85,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 +
' ' +
@@ -155,10 +176,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;
}
diff --git a/src/themes/state.scss b/src/themes/state.scss
index aa8bd4df3..0fb7c0f86 100644
--- a/src/themes/state.scss
+++ b/src/themes/state.scss
@@ -85,6 +85,16 @@ g.stateGroup line {
fill: $nodeBkg;
stroke: $nodeBorder;
stroke-width: 1px;
+}
+.statediagram-cluster rect.outer {
+ rx: 5px;
+ ry: 5px;
+}
+.statediagram-state .divider {
+ stroke: $nodeBorder;
+}
+
+.statediagram-state .title-state {
rx: 5px;
ry: 5px;
}
@@ -100,10 +110,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;