mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-21 09:16:41 +02:00
#945 Support for notes
This commit is contained in:
@@ -13,6 +13,49 @@ describe('State diagram', () => {
|
|||||||
);
|
);
|
||||||
cy.get('svg');
|
cy.get('svg');
|
||||||
});
|
});
|
||||||
|
it('should render a state with a note', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
stateDiagram
|
||||||
|
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
|
||||||
|
State1: The state with a note
|
||||||
|
note left of State1
|
||||||
|
Important information! You can write
|
||||||
|
notes.
|
||||||
|
end note
|
||||||
|
`,
|
||||||
|
{ logLevel: 0 }
|
||||||
|
);
|
||||||
|
cy.get('svg');
|
||||||
|
});
|
||||||
|
it('should render a state with a note together with another state', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
stateDiagram
|
||||||
|
State1: The state with a note
|
||||||
|
note right of State1
|
||||||
|
Important information! You can write
|
||||||
|
notes.
|
||||||
|
end note
|
||||||
|
State1 --> State2
|
||||||
|
`,
|
||||||
|
{ logLevel: 0 }
|
||||||
|
);
|
||||||
|
cy.get('svg');
|
||||||
|
});
|
||||||
it('should render a states with descriptions including multi-line descriptions', () => {
|
it('should render a states with descriptions including multi-line descriptions', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
|
@@ -66,7 +66,7 @@
|
|||||||
<FLOATING_NOTE_ID>[^\n]* {this.popState();console.log('Floating note ID', yytext);return "ID";}
|
<FLOATING_NOTE_ID>[^\n]* {this.popState();console.log('Floating note ID', yytext);return "ID";}
|
||||||
<NOTE_ID>\s*[^:\n\s\-]+ { this.popState();this.pushState('NOTE_TEXT');console.log('Got ID for note', yytext);return 'ID';}
|
<NOTE_ID>\s*[^:\n\s\-]+ { this.popState();this.pushState('NOTE_TEXT');console.log('Got ID for note', yytext);return 'ID';}
|
||||||
<NOTE_TEXT>\s*":"[^\+\-:\n,;]+ { this.popState();console.log('Got NOTE_TEXT for note',yytext);return 'NOTE_TEXT';}
|
<NOTE_TEXT>\s*":"[^\+\-:\n,;]+ { this.popState();console.log('Got NOTE_TEXT for note',yytext);return 'NOTE_TEXT';}
|
||||||
<NOTE_TEXT>\s*[^\+\-:,;]+"end note" { this.popState();console.log('Got NOTE_TEXT for note',yytext);return 'NOTE_TEXT';}
|
<NOTE_TEXT>\s*[^\+\-:,;]+"end note" { this.popState();console.log('Got NOTE_TEXT for note',yytext);yytext = yytext.slice(0,-8).trim();return 'NOTE_TEXT';}
|
||||||
|
|
||||||
"stateDiagram"\s+ { console.log('Got state diagram', yytext,'#');return 'SD'; }
|
"stateDiagram"\s+ { console.log('Got state diagram', yytext,'#');return 'SD'; }
|
||||||
"hide empty description" { console.log('HIDE_EMPTY', yytext,'#');return 'HIDE_EMPTY'; }
|
"hide empty description" { console.log('HIDE_EMPTY', yytext,'#');return 'HIDE_EMPTY'; }
|
||||||
@@ -138,6 +138,10 @@ statement
|
|||||||
| JOIN
|
| JOIN
|
||||||
| CONCURRENT
|
| CONCURRENT
|
||||||
| note notePosition ID NOTE_TEXT
|
| note notePosition ID NOTE_TEXT
|
||||||
|
{
|
||||||
|
console.warn('got NOTE, position: ', $2.trim(), 'id = ', $3.trim(), 'note: ', $4);
|
||||||
|
$$={ stmt: 'state', id: $3.trim(), note:{position: $2.trim(), text: $4.trim()}};
|
||||||
|
}
|
||||||
| note NOTE_TEXT AS ID
|
| note NOTE_TEXT AS ID
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@@ -9,7 +9,8 @@ console.warn('ID cache', idCache);
|
|||||||
const conf = {
|
const conf = {
|
||||||
dividerMargin: 10,
|
dividerMargin: 10,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
textHeight: 10
|
textHeight: 10,
|
||||||
|
noteMargin: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,6 +169,77 @@ const drawEndState = g => {
|
|||||||
.attr('cy', conf.padding + 7);
|
.attr('cy', conf.padding + 7);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const drawText = function(elem, textData, width) {
|
||||||
|
// Remove and ignore br:s
|
||||||
|
const nText = textData.text.replace(/<br\/?>/gi, ' ');
|
||||||
|
|
||||||
|
const textElem = elem.append('text');
|
||||||
|
textElem.attr('x', textData.x);
|
||||||
|
textElem.attr('y', textData.y);
|
||||||
|
textElem.style('text-anchor', textData.anchor);
|
||||||
|
textElem.attr('fill', textData.fill);
|
||||||
|
if (typeof textData.class !== 'undefined') {
|
||||||
|
textElem.attr('class', textData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = textElem.append('tspan');
|
||||||
|
span.attr('x', textData.x + textData.textMargin * 2);
|
||||||
|
span.attr('fill', textData.fill);
|
||||||
|
span.text(nText);
|
||||||
|
|
||||||
|
return textElem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _drawLongText = (_text, x, y, g) => {
|
||||||
|
let textHeight = 0;
|
||||||
|
let textWidth = 0;
|
||||||
|
const textElem = g.append('text');
|
||||||
|
textElem.style('text-anchor', 'start');
|
||||||
|
textElem.attr('class', 'noteText');
|
||||||
|
|
||||||
|
let text = _text.replace(/\r\n/g, '<br/>');
|
||||||
|
text = text.replace(/\n/g, '<br/>');
|
||||||
|
const lines = text.split(/<br\/?>/gi);
|
||||||
|
for (const line of lines) {
|
||||||
|
const txt = line.trim();
|
||||||
|
|
||||||
|
if (txt.length > 0) {
|
||||||
|
const span = textElem.append('tspan');
|
||||||
|
const textBounds = span.node().getBBox();
|
||||||
|
textHeight += textBounds.height;
|
||||||
|
span.attr('x', x + conf.noteMargin);
|
||||||
|
span.attr('y', y + textHeight + 1.75 * conf.noteMargin);
|
||||||
|
span.text(txt);
|
||||||
|
|
||||||
|
textWidth = Math.max(textBounds.width, textWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { textWidth, textHeight };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws an actor in the diagram with the attaced line
|
||||||
|
* @param center - The center of the the actor
|
||||||
|
* @param pos The position if the actor in the liost of actors
|
||||||
|
* @param description The text in the box
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const drawNote = (text, g) => {
|
||||||
|
g.attr('class', 'note');
|
||||||
|
const note = g
|
||||||
|
.append('rect')
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr('y', conf.padding);
|
||||||
|
const rectElem = g.append('g');
|
||||||
|
|
||||||
|
const { textWidth, textHeight } = _drawLongText(text, 0, 0, rectElem);
|
||||||
|
|
||||||
|
note.attr('height', textHeight + 2 * conf.noteMargin);
|
||||||
|
note.attr('width', textWidth + conf.noteMargin * 2);
|
||||||
|
|
||||||
|
return note;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starting point for drawing a state. The function finds out the specifics
|
* Starting point for drawing a state. The function finds out the specifics
|
||||||
* about the state and renders with approprtiate function.
|
* about the state and renders with approprtiate function.
|
||||||
@@ -192,6 +264,7 @@ export const drawState = function(elem, stateDef, graph, doc) {
|
|||||||
|
|
||||||
if (stateDef.type === 'start') drawStartState(g);
|
if (stateDef.type === 'start') drawStartState(g);
|
||||||
if (stateDef.type === 'end') drawEndState(g);
|
if (stateDef.type === 'end') drawEndState(g);
|
||||||
|
if (stateDef.type === 'note') drawNote(stateDef.note.text, g);
|
||||||
if (stateDef.type === 'default' && stateDef.descriptions.length === 0)
|
if (stateDef.type === 'default' && stateDef.descriptions.length === 0)
|
||||||
drawSimpleState(g, stateDef);
|
drawSimpleState(g, stateDef);
|
||||||
if (stateDef.type === 'default' && stateDef.descriptions.length > 0) drawDescrState(g, stateDef);
|
if (stateDef.type === 'default' && stateDef.descriptions.length > 0) drawDescrState(g, stateDef);
|
||||||
|
@@ -14,7 +14,7 @@ const extract = doc => {
|
|||||||
|
|
||||||
doc.forEach(item => {
|
doc.forEach(item => {
|
||||||
if (item.stmt === 'state') {
|
if (item.stmt === 'state') {
|
||||||
addState(item.id, item.type, item.doc, item.description);
|
addState(item.id, item.type, item.doc, item.description, item.note);
|
||||||
}
|
}
|
||||||
if (item.stmt === 'relation') {
|
if (item.stmt === 'relation') {
|
||||||
addRelation(item.state1.id, item.state2.id, item.description);
|
addRelation(item.state1.id, item.state2.id, item.description);
|
||||||
@@ -46,14 +46,15 @@ let endCnt = 0;
|
|||||||
* @param type
|
* @param type
|
||||||
* @param style
|
* @param style
|
||||||
*/
|
*/
|
||||||
export const addState = function(id, type, doc, descr) {
|
export const addState = function(id, type, doc, descr, note) {
|
||||||
console.warn('Add state', id);
|
console.warn('Add state', id);
|
||||||
if (typeof currentDocument.states[id] === 'undefined') {
|
if (typeof currentDocument.states[id] === 'undefined') {
|
||||||
currentDocument.states[id] = {
|
currentDocument.states[id] = {
|
||||||
id: id,
|
id: id,
|
||||||
descriptions: [],
|
descriptions: [],
|
||||||
type,
|
type,
|
||||||
doc
|
doc,
|
||||||
|
note
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (!currentDocument.states[id].doc) {
|
if (!currentDocument.states[id].doc) {
|
||||||
@@ -64,6 +65,7 @@ export const addState = function(id, type, doc, descr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (descr) addDescription(id, descr.trim());
|
if (descr) addDescription(id, descr.trim());
|
||||||
|
if (note) currentDocument.states[id].note = note;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clear = function() {
|
export const clear = function() {
|
||||||
|
@@ -6,7 +6,7 @@ import stateDb from './stateDb';
|
|||||||
import { parser } from './parser/stateDiagram';
|
import { parser } from './parser/stateDiagram';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
import idCache from './id-cache';
|
import idCache from './id-cache';
|
||||||
import { drawState, addIdAndBox, drawEdge } from './shapes';
|
import { drawState, addIdAndBox, drawEdge, drawNote } from './shapes';
|
||||||
|
|
||||||
parser.yy = stateDb;
|
parser.yy = stateDb;
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ export const draw = function(text, id) {
|
|||||||
// // Layout graph, Create a new directed graph
|
// // Layout graph, Create a new directed graph
|
||||||
const graph = new graphlib.Graph({
|
const graph = new graphlib.Graph({
|
||||||
multigraph: false,
|
multigraph: false,
|
||||||
// compound: true,
|
compound: true,
|
||||||
// acyclicer: 'greedy',
|
// acyclicer: 'greedy',
|
||||||
rankdir: 'RL'
|
rankdir: 'RL'
|
||||||
});
|
});
|
||||||
@@ -93,7 +93,7 @@ export const draw = function(text, id) {
|
|||||||
|
|
||||||
diagram.attr('height', '100%');
|
diagram.attr('height', '100%');
|
||||||
diagram.attr('width', '100%');
|
diagram.attr('width', '100%');
|
||||||
diagram.attr('viewBox', '0 0 ' + bounds.width + ' ' + (bounds.height + 50));
|
diagram.attr('viewBox', '0 0 ' + bounds.width * 2 + ' ' + (bounds.height + 50));
|
||||||
};
|
};
|
||||||
const getLabelWidth = text => {
|
const getLabelWidth = text => {
|
||||||
return text ? text.length * 5.02 : 1;
|
return text ? text.length * 5.02 : 1;
|
||||||
@@ -101,15 +101,17 @@ const getLabelWidth = text => {
|
|||||||
|
|
||||||
const renderDoc = (doc, diagram, parentId) => {
|
const renderDoc = (doc, diagram, parentId) => {
|
||||||
// // Layout graph, Create a new directed graph
|
// // Layout graph, Create a new directed graph
|
||||||
const graph = new graphlib.Graph({});
|
const graph = new graphlib.Graph({
|
||||||
|
compound: true
|
||||||
|
});
|
||||||
|
|
||||||
// Set an object for the graph label
|
// Set an object for the graph label
|
||||||
if (parentId)
|
if (parentId)
|
||||||
graph.setGraph({
|
graph.setGraph({
|
||||||
rankdir: 'LR',
|
rankdir: 'LR',
|
||||||
multigraph: false,
|
multigraph: false,
|
||||||
compound: false,
|
compound: true,
|
||||||
// acyclicer: 'greedy',
|
acyclicer: 'greedy',
|
||||||
rankdir: 'LR',
|
rankdir: 'LR',
|
||||||
ranker: 'tight-tree'
|
ranker: 'tight-tree'
|
||||||
// isMultiGraph: false
|
// isMultiGraph: false
|
||||||
@@ -117,8 +119,12 @@ const renderDoc = (doc, diagram, parentId) => {
|
|||||||
else {
|
else {
|
||||||
graph.setGraph({
|
graph.setGraph({
|
||||||
rankdir: 'TB',
|
rankdir: 'TB',
|
||||||
// acyclicer: 'greedy'
|
compound: true,
|
||||||
ranker: 'longest-path'
|
// isCompound: true,
|
||||||
|
// acyclicer: 'greedy',
|
||||||
|
// ranker: 'longest-path'
|
||||||
|
ranker: 'tight-tree'
|
||||||
|
// ranker: 'network-simplex'
|
||||||
// isMultiGraph: false
|
// isMultiGraph: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -139,6 +145,7 @@ const renderDoc = (doc, diagram, parentId) => {
|
|||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
const stateDef = states[keys[i]];
|
const stateDef = states[keys[i]];
|
||||||
console.warn('keys[i]', keys[i]);
|
console.warn('keys[i]', keys[i]);
|
||||||
|
|
||||||
let node;
|
let node;
|
||||||
if (stateDef.doc) {
|
if (stateDef.doc) {
|
||||||
let sub = diagram
|
let sub = diagram
|
||||||
@@ -156,11 +163,35 @@ const renderDoc = (doc, diagram, parentId) => {
|
|||||||
node = drawState(diagram, stateDef, graph);
|
node = drawState(diagram, stateDef, graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stateDef.note) {
|
||||||
|
// Draw note note
|
||||||
|
console.warn('Def=', stateDef);
|
||||||
|
const noteDef = {
|
||||||
|
descriptions: [],
|
||||||
|
id: stateDef.id + '-note',
|
||||||
|
note: stateDef.note,
|
||||||
|
type: 'note'
|
||||||
|
};
|
||||||
|
const note = drawState(diagram, noteDef, graph);
|
||||||
|
|
||||||
|
// graph.setNode(node.id, node);
|
||||||
|
if (stateDef.note.position === 'left of') {
|
||||||
|
graph.setNode(node.id + '-note', note);
|
||||||
|
graph.setNode(node.id, node);
|
||||||
|
} else {
|
||||||
|
graph.setNode(node.id, node);
|
||||||
|
graph.setNode(node.id + '-note', note);
|
||||||
|
}
|
||||||
|
// graph.setNode(node.id);
|
||||||
|
graph.setParent(node.id, node.id + '-group');
|
||||||
|
graph.setParent(node.id + '-note', node.id + '-group');
|
||||||
|
} else {
|
||||||
// Add nodes to the graph. The first argument is the node id. The second is
|
// Add nodes to the graph. The first argument is the node id. The second is
|
||||||
// metadata about the node. In this case we're going to add labels to each of
|
// metadata about the node. In this case we're going to add labels to each of
|
||||||
// our nodes.
|
// our nodes.
|
||||||
graph.setNode(node.id, node);
|
graph.setNode(node.id, node);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.info('Count=', graph.nodeCount());
|
console.info('Count=', graph.nodeCount());
|
||||||
relations.forEach(function(relation) {
|
relations.forEach(function(relation) {
|
||||||
@@ -178,6 +209,8 @@ const renderDoc = (doc, diagram, parentId) => {
|
|||||||
|
|
||||||
dagre.layout(graph);
|
dagre.layout(graph);
|
||||||
|
|
||||||
|
console.warn('Graph after layout', graph.nodes());
|
||||||
|
|
||||||
graph.nodes().forEach(function(v) {
|
graph.nodes().forEach(function(v) {
|
||||||
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
|
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
|
||||||
console.warn('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
console.warn('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
@@ -191,6 +224,8 @@ const renderDoc = (doc, diagram, parentId) => {
|
|||||||
graph.node(v).height / 2) +
|
graph.node(v).height / 2) +
|
||||||
' )'
|
' )'
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
console.warn('No Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let stateBox = diagram.node().getBBox();
|
let stateBox = diagram.node().getBBox();
|
||||||
|
Reference in New Issue
Block a user