#1295 Adding note support to state diagrams

This commit is contained in:
Knut Sveidqvist 2020-03-29 14:20:49 +02:00
parent 5fbb69e7c5
commit 240077ffe8
11 changed files with 220 additions and 72 deletions

View File

@ -31,7 +31,7 @@
G-->H
G-->c
</div>
<div class="mermaid" style="width: 50%; height: 20%;">
<div class="mermaid2" style="width: 50%; height: 20%;">
flowchart LR
subgraph id1 [Test]
b
@ -56,21 +56,13 @@
</div>
<div class="mermaid" style="width: 100%; height: 100%;">
stateDiagram-v2
[*] --> First
state First {
[*] --> Second
state Second {
[*] --> second
second --> Third
state Third {
[*] --> third
third --> [*]
}
}
}
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
State1 --> State2
note left of State2 : This is the note to the left.
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
stateDiagram-v2

View File

@ -55,11 +55,47 @@ const rect = (parent, node) => {
return shapeSvg;
};
/**
* Non visiable cluster where the note is group with its
*/
const noteGroup = (parent, node) => {
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', 'note-cluster')
.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('rx', node.rx)
.attr('ry', node.ry)
.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)
.attr('fill', 'none');
const rectBox = rect.node().getBBox();
node.width = rectBox.width;
node.height = rectBox.height;
node.intersect = function(point) {
return intersectRect(node, point);
};
return shapeSvg;
};
const roundedWithTitle = (parent, node) => {
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', node.class)
.attr('class', node.classes)
.attr('id', node.id);
// add the rect
@ -114,7 +150,7 @@ const roundedWithTitle = (parent, node) => {
return shapeSvg;
};
const shapes = { rect, roundedWithTitle };
const shapes = { rect, roundedWithTitle, noteGroup };
let clusterElems = {};

View File

@ -2,14 +2,14 @@ const createLabel = (vertexText, style) => {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
const rows = vertexText.split(/<br\s*\/?>/gi);
const rows = vertexText.split(/\n|<br\s*\/?>/gi);
for (let j = 0; j < rows.length; j++) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '0');
tspan.textContent = rows[j];
tspan.textContent = rows[j].trim();
svgLabel.appendChild(tspan);
}
return svgLabel;

View File

@ -192,7 +192,7 @@ export const insertEdge = function(elem, edge, clusterDb, diagramType) {
.append('path')
.attr('d', lineFunction(lineData))
.attr('id', edge.id)
.attr('class', 'transition');
.attr('class', 'transition' + (edge.classes ? ' ' + edge.classes : ''));
// DEBUG code, adds a red circle at each edge coordinate
// edge.points.forEach(point => {

View File

@ -1,49 +1,8 @@
import intersect from './intersect/index.js';
import { logger } from '../logger'; // eslint-disable-line
import createLabel from './createLabel';
import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util';
import note from './shapes/note';
const labelHelper = (parent, node) => {
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', 'node default')
.attr('id', node.id);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'label');
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
// Get the size of the label
const bbox = text.getBBox();
const halfPadding = node.padding / 2;
// Center the label
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
return { shapeSvg, bbox, halfPadding, label };
};
const updateNodeBounds = (node, element) => {
const bbox = element.node().getBBox();
node.width = bbox.width;
node.height = bbox.height;
};
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 + ')');
}
const question = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
@ -287,8 +246,9 @@ const cylinder = (parent, node) => {
};
const rect = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node);
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
logger.info('Classes = ', node.classes);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
@ -418,7 +378,8 @@ const shapes = {
rect_right_inv_arrow,
cylinder,
start,
end
end,
note
};
let nodeElems = {};

View File

@ -0,0 +1,29 @@
import { updateNodeBounds, labelHelper } from './util';
import { logger } from '../../logger'; // eslint-disable-line
import intersect from '../intersect/index.js';
const note = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
logger.info('Classes = ', node.classes);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
rect
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', -bbox.width / 2 - halfPadding)
.attr('y', -bbox.height / 2 - halfPadding)
.attr('width', bbox.width + node.padding)
.attr('height', bbox.height + node.padding);
updateNodeBounds(node, rect);
node.intersect = function(point) {
return intersect.rect(node, point);
};
return shapeSvg;
};
export default note;

View File

@ -0,0 +1,50 @@
import createLabel from '../createLabel';
export const labelHelper = (parent, node, _classes) => {
let classes;
if (!_classes) {
classes = 'node default';
} else {
classes = _classes;
}
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', classes)
.attr('id', node.id);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'label');
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
// Get the size of the label
const bbox = text.getBBox();
const halfPadding = node.padding / 2;
// Center the label
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
return { shapeSvg, bbox, halfPadding, label };
};
export const updateNodeBounds = (node, element) => {
const bbox = element.node().getBBox();
node.width = bbox.width;
node.height = bbox.height;
};
export 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 + ')');
}

View File

@ -114,7 +114,7 @@ line
statement
: idStatement { /*console.warn('got id and descr', $1);*/$$={ stmt: 'state', id: $1, type: 'default', description: ''};}
| idStatement DESCR { /*console.warn('got id and descr', $1, $2.trim());*/$$={ stmt: 'state', id: $1, type: 'default', description: $2.trim()};}
| idStatement DESCR { /*console.warn('got id and descr', $1, $2.trim());*/$$={ stmt: 'state', id: $1, type: 'default', description: yy.trimColon($2)};}
| idStatement '-->' idStatement
{
/*console.warn('got id', $1);yy.addRelation($1, $3);*/

View File

@ -180,6 +180,8 @@ export const relationType = {
DEPENDENCY: 3
};
const trimColon = str => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
export default {
addState,
clear,
@ -198,5 +200,6 @@ export default {
getRootDoc,
setRootDoc,
getRootDocV2,
extract
extract,
trimColon
};

View File

@ -47,7 +47,8 @@ const setupNode = (g, parent, node, altFlag) => {
nodeDb[node.id] = {
id: node.id,
shape,
description: node.id
description: node.id,
classes: 'statediagram-state'
};
}
@ -64,6 +65,10 @@ const setupNode = (g, parent, node, altFlag) => {
logger.info('Setting cluser for ', node.id);
nodeDb[node.id].type = 'group';
nodeDb[node.id].shape = 'roundedWithTitle';
nodeDb[node.id].classes =
nodeDb[node.id].classes +
' ' +
(altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster');
}
const nodeData = {
@ -72,14 +77,68 @@ const setupNode = (g, parent, node, altFlag) => {
shape: nodeDb[node.id].shape,
label: node.id,
labelText: nodeDb[node.id].description,
class: altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster', //classStr,
classes: nodeDb[node.id].classes, //classStr,
style: '', //styles.style,
id: node.id,
type: nodeDb[node.id].type,
padding: 15 //getConfig().flowchart.padding
};
if (node.note) {
// Todo: set random id
const noteData = {
labelType: 'svg',
labelStyle: '',
shape: 'note',
label: node.id,
labelText: node.note.text,
classes: 'statediagram-note', //classStr,
style: '', //styles.style,
id: node.id + '----note',
type: nodeDb[node.id].type,
padding: 15 //getConfig().flowchart.padding
};
const groupData = {
labelType: 'svg',
labelStyle: '',
shape: 'noteGroup',
label: node.id + '----parent',
labelText: node.note.text,
classes: nodeDb[node.id].classes, //classStr,
style: '', //styles.style,
id: node.id + '----parent',
type: 'group',
padding: 0 //getConfig().flowchart.padding
};
g.setNode(node.id + '----parent', groupData);
g.setNode(noteData.id, noteData);
g.setNode(node.id, nodeData);
g.setParent(node.id, node.id + '----parent');
g.setParent(noteData.id, node.id + '----parent');
let from = node.id;
let to = noteData.id;
if (node.note.position === 'left of') {
from = noteData.id;
to = node.id;
}
g.setEdge(from, to, {
arrowhead: 'none',
arrowType: '',
style: 'fill:none',
labelStyle: '',
classes: 'note-edge',
arrowheadStyle: 'fill: #333',
labelpos: 'c',
labelType: 'text',
label: ''
});
} else {
g.setNode(node.id, nodeData);
}
}
if (parent) {
@ -166,6 +225,7 @@ export const draw = function(text, id) {
});
// logger.info(stateDb.getRootDoc());
stateDb.extract(stateDb.getRootDocV2().doc);
logger.info(stateDb.getRootDocV2());
setupNode(g, undefined, stateDb.getRootDocV2(), true);

View File

@ -99,3 +99,20 @@ g.stateGroup line {
rx:0;
ry:0;
}
.statediagram-state rect {
rx: 5px;
ry: 5px;
}
.note-edge {
stroke-dasharray: 5;
}
.statediagram-note rect {
fill: $noteBkgColor;
stroke: $noteBorderColor;
stroke-width: 1px;
rx: 0;
ry: 0;
}