Merge pull request #1370 from mermaid-js/feature/1295_generic_rendering_engine

Feature/1295 generic rendering engine
This commit is contained in:
Knut Sveidqvist 2020-04-26 16:55:11 +02:00 committed by GitHub
commit e3b1944e31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 759 additions and 73 deletions

View File

@ -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<br/>should work
S1 --> S3: long line using <br>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 +,-<br/>
`,
{ 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<br>Line2<br/>Line3<br />Line4<br />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 &lt;&lt;fork&gt;&gt;
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state &lt;&lt;join&gt;&gt;
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
}
`,
{ logLevel: 0 }
);
});
it('should render forks and joins', () => {
imgSnapshotTest(
`
stateDiagram-v2
state fork_state &lt;&lt;fork&gt;&gt;
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state &lt;&lt;join&gt;&gt;
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
`,
{ logLevel: 0 }
);
cy.get('svg');
});
it('should render concurrency 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,
}
);
});
});

View File

@ -18,6 +18,14 @@
</head>
<body>
<h1>info below</h1>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart LR
a --> b
subgraph b [Test]
c --> d -->e
end
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart LR
a --> b
@ -31,30 +39,40 @@
G-->H
G-->c
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
stateDiagram-v2
[*] --> monkey
state monkey {
Sitting
--
Eating
}
<div class="mermaid" style="width: 50%; height: 20%;">
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
}
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
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
[*]-->TV
state TV {
state fork_state &lt;&lt;fork&gt;&gt;
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state &lt;&lt;join&gt;&gt;
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
}
</div>
<div class="mermaid2 mermaid-apa" style="width: 100%; height: 20%;">
stateDiagram
@ -73,8 +91,8 @@
<div class="mermaid2" style="width: 100%; height: 100%;">
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
</div>
<div class="mermaid" style="width: 100%; height: 100%;">
<div class="mermaid2" style="width: 100%; height: 100%;">
flowchart TD
subgraph A
a
@ -101,7 +124,7 @@ flowchart TD
A -- oAo --o B
A --> C
</div>
<div class="mermaid" style="width: 100%; height: 100%;">
<div class="mermaid2" style="width: 100%; height: 100%;">
flowchart TD
subgraph A
a
@ -133,28 +156,27 @@ stateDiagram-v2
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
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
}
}
</div>
<div style="display: flex;flex-direction:column;width: 100%; height: 100%">

View File

@ -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.

View File

@ -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 = {};

View File

@ -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|<br\s*\/?>/gi);
if (typeof vertexText === 'string') {
rows = vertexText.split(/\\n|\n|<br\s*\/?>/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);
}

View File

@ -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 + ')');
};

View File

@ -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);
};

View File

@ -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,

View File

@ -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 () {

View File

@ -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('<br/>'), 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;

View File

@ -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);
});
};
{
/* <svg height="10" width="10" xmlns="http://www.w3.org/2000/svg" version="1.1">
{' '}
<defs>
{' '}
<pattern id="circles-1" patternUnits="userSpaceOnUse" width="10" height="10">
{' '}
<image
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+CiAgPHJlY3Qgd2lkdGg9JzEwJyBoZWlnaHQ9JzEwJyBmaWxsPSJ3aGl0ZSIgLz4KICA8Y2lyY2xlIGN4PSIxIiBjeT0iMSIgcj0iMSIgZmlsbD0iYmxhY2siLz4KPC9zdmc+"
x="0"
y="0"
width="10"
height="10"
>
{' '}
</image>{' '}
</pattern>{' '}
</defs>{' '}
</svg>; */
}
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;

View File

@ -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') {

View File

@ -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'

View File

@ -36,6 +36,9 @@
.edgeLabel {
background-color: $edgeLabelBackground;
rect {
opacity: 0.5;
}
text-align: center;
}

View File

@ -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;