mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +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-->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
|
||||
|
@ -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 = {};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 => {
|
||||
|
@ -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 = {};
|
||||
|
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
|
||||
: 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);*/
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user