mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
#1295 Adding note support to state diagrams
This commit is contained in:
parent
5fbb69e7c5
commit
240077ffe8
@ -31,7 +31,7 @@
|
|||||||
G-->H
|
G-->H
|
||||||
G-->c
|
G-->c
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid" style="width: 50%; height: 20%;">
|
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph id1 [Test]
|
subgraph id1 [Test]
|
||||||
b
|
b
|
||||||
@ -56,21 +56,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mermaid" style="width: 100%; height: 100%;">
|
<div class="mermaid" style="width: 100%; height: 100%;">
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
[*] --> First
|
State1: The state with a note
|
||||||
|
note right of State1
|
||||||
state First {
|
Important information! You can write
|
||||||
[*] --> Second
|
notes.
|
||||||
|
end note
|
||||||
state Second {
|
State1 --> State2
|
||||||
[*] --> second
|
note left of State2 : This is the note to the left.
|
||||||
second --> Third
|
|
||||||
|
|
||||||
state Third {
|
|
||||||
[*] --> third
|
|
||||||
third --> [*]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
|
@ -55,11 +55,47 @@ const rect = (parent, node) => {
|
|||||||
return shapeSvg;
|
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) => {
|
const roundedWithTitle = (parent, node) => {
|
||||||
// Add outer g element
|
// Add outer g element
|
||||||
const shapeSvg = parent
|
const shapeSvg = parent
|
||||||
.insert('g')
|
.insert('g')
|
||||||
.attr('class', node.class)
|
.attr('class', node.classes)
|
||||||
.attr('id', node.id);
|
.attr('id', node.id);
|
||||||
|
|
||||||
// add the rect
|
// add the rect
|
||||||
@ -114,7 +150,7 @@ const roundedWithTitle = (parent, node) => {
|
|||||||
return shapeSvg;
|
return shapeSvg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const shapes = { rect, roundedWithTitle };
|
const shapes = { rect, roundedWithTitle, noteGroup };
|
||||||
|
|
||||||
let clusterElems = {};
|
let clusterElems = {};
|
||||||
|
|
||||||
|
@ -2,14 +2,14 @@ const createLabel = (vertexText, style) => {
|
|||||||
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
|
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++) {
|
for (let j = 0; j < rows.length; j++) {
|
||||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||||
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||||
tspan.setAttribute('dy', '1em');
|
tspan.setAttribute('dy', '1em');
|
||||||
tspan.setAttribute('x', '0');
|
tspan.setAttribute('x', '0');
|
||||||
tspan.textContent = rows[j];
|
tspan.textContent = rows[j].trim();
|
||||||
svgLabel.appendChild(tspan);
|
svgLabel.appendChild(tspan);
|
||||||
}
|
}
|
||||||
return svgLabel;
|
return svgLabel;
|
||||||
|
@ -192,7 +192,7 @@ export const insertEdge = function(elem, edge, clusterDb, diagramType) {
|
|||||||
.append('path')
|
.append('path')
|
||||||
.attr('d', lineFunction(lineData))
|
.attr('d', lineFunction(lineData))
|
||||||
.attr('id', edge.id)
|
.attr('id', edge.id)
|
||||||
.attr('class', 'transition');
|
.attr('class', 'transition' + (edge.classes ? ' ' + edge.classes : ''));
|
||||||
|
|
||||||
// DEBUG code, adds a red circle at each edge coordinate
|
// DEBUG code, adds a red circle at each edge coordinate
|
||||||
// edge.points.forEach(point => {
|
// edge.points.forEach(point => {
|
||||||
|
@ -1,49 +1,8 @@
|
|||||||
import intersect from './intersect/index.js';
|
import intersect from './intersect/index.js';
|
||||||
import { logger } from '../logger'; // eslint-disable-line
|
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 question = (parent, node) => {
|
||||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||||
|
|
||||||
@ -287,8 +246,9 @@ const cylinder = (parent, node) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rect = (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
|
// add the rect
|
||||||
const rect = shapeSvg.insert('rect', ':first-child');
|
const rect = shapeSvg.insert('rect', ':first-child');
|
||||||
|
|
||||||
@ -418,7 +378,8 @@ const shapes = {
|
|||||||
rect_right_inv_arrow,
|
rect_right_inv_arrow,
|
||||||
cylinder,
|
cylinder,
|
||||||
start,
|
start,
|
||||||
end
|
end,
|
||||||
|
note
|
||||||
};
|
};
|
||||||
|
|
||||||
let nodeElems = {};
|
let nodeElems = {};
|
||||||
|
29
src/dagre-wrapper/shapes/note.js
Normal file
29
src/dagre-wrapper/shapes/note.js
Normal 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;
|
50
src/dagre-wrapper/shapes/util.js
Normal file
50
src/dagre-wrapper/shapes/util.js
Normal 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 + ')');
|
||||||
|
}
|
@ -114,7 +114,7 @@ line
|
|||||||
|
|
||||||
statement
|
statement
|
||||||
: idStatement { /*console.warn('got id and descr', $1);*/$$={ stmt: 'state', id: $1, type: 'default', description: ''};}
|
: 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
|
| idStatement '-->' idStatement
|
||||||
{
|
{
|
||||||
/*console.warn('got id', $1);yy.addRelation($1, $3);*/
|
/*console.warn('got id', $1);yy.addRelation($1, $3);*/
|
||||||
|
@ -180,6 +180,8 @@ export const relationType = {
|
|||||||
DEPENDENCY: 3
|
DEPENDENCY: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const trimColon = str => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
addState,
|
addState,
|
||||||
clear,
|
clear,
|
||||||
@ -198,5 +200,6 @@ export default {
|
|||||||
getRootDoc,
|
getRootDoc,
|
||||||
setRootDoc,
|
setRootDoc,
|
||||||
getRootDocV2,
|
getRootDocV2,
|
||||||
extract
|
extract,
|
||||||
|
trimColon
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,8 @@ const setupNode = (g, parent, node, altFlag) => {
|
|||||||
nodeDb[node.id] = {
|
nodeDb[node.id] = {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
shape,
|
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);
|
logger.info('Setting cluser for ', node.id);
|
||||||
nodeDb[node.id].type = 'group';
|
nodeDb[node.id].type = 'group';
|
||||||
nodeDb[node.id].shape = 'roundedWithTitle';
|
nodeDb[node.id].shape = 'roundedWithTitle';
|
||||||
|
nodeDb[node.id].classes =
|
||||||
|
nodeDb[node.id].classes +
|
||||||
|
' ' +
|
||||||
|
(altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster');
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeData = {
|
const nodeData = {
|
||||||
@ -72,14 +77,68 @@ const setupNode = (g, parent, node, altFlag) => {
|
|||||||
shape: nodeDb[node.id].shape,
|
shape: nodeDb[node.id].shape,
|
||||||
label: node.id,
|
label: node.id,
|
||||||
labelText: nodeDb[node.id].description,
|
labelText: nodeDb[node.id].description,
|
||||||
class: altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster', //classStr,
|
classes: nodeDb[node.id].classes, //classStr,
|
||||||
style: '', //styles.style,
|
style: '', //styles.style,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
type: nodeDb[node.id].type,
|
type: nodeDb[node.id].type,
|
||||||
padding: 15 //getConfig().flowchart.padding
|
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.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) {
|
if (parent) {
|
||||||
@ -166,6 +225,7 @@ export const draw = function(text, id) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// logger.info(stateDb.getRootDoc());
|
// logger.info(stateDb.getRootDoc());
|
||||||
|
stateDb.extract(stateDb.getRootDocV2().doc);
|
||||||
logger.info(stateDb.getRootDocV2());
|
logger.info(stateDb.getRootDocV2());
|
||||||
setupNode(g, undefined, stateDb.getRootDocV2(), true);
|
setupNode(g, undefined, stateDb.getRootDocV2(), true);
|
||||||
|
|
||||||
|
@ -99,3 +99,20 @@ g.stateGroup line {
|
|||||||
rx:0;
|
rx:0;
|
||||||
ry: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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user