mermaid/src/diagrams/state/stateRenderer.js

281 lines
7.4 KiB
JavaScript
Raw Normal View History

import * as d3 from 'd3';
import dagre from 'dagre-layout';
import graphlib from 'graphlibrary';
import { logger } from '../../logger';
import stateDb from './stateDb';
import { parser } from './parser/stateDiagram';
import utils from '../../utils';
2019-10-05 09:02:20 +02:00
import idCache from './id-cache';
2019-10-06 10:52:37 +02:00
import { drawState, addIdAndBox, drawEdge, drawNote } from './shapes';
parser.yy = stateDb;
2019-09-25 21:01:21 +02:00
let total = 0;
2019-10-05 09:02:20 +02:00
// TODO Move conf object to main conf in mermaidAPI
const conf = {
dividerMargin: 10,
padding: 5,
textHeight: 10
};
const transformationLog = {};
export const setConf = function(cnf) {};
2019-09-25 21:01:21 +02:00
// Todo optimize
const getGraphId = function(label) {
2019-10-05 09:02:20 +02:00
const keys = idCache.keys();
2019-09-25 21:01:21 +02:00
for (let i = 0; i < keys.length; i++) {
2019-10-05 09:02:20 +02:00
if (idCache.get(keys[i]).label === label) {
2019-09-25 21:01:21 +02:00
return keys[i];
}
}
return undefined;
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*/
const insertMarkers = function(elem) {
elem
.append('defs')
.append('marker')
.attr('id', 'dependencyEnd')
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
2019-09-25 21:01:21 +02:00
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
* @param text
* @param id
*/
export const draw = function(text, id) {
parser.yy.clear();
parser.parse(text);
2019-10-06 14:11:17 +02:00
logger.warn('Rendering diagram ' + text);
// /// / Fetch the default direction, use TD if none was found
2019-09-25 21:01:21 +02:00
const diagram = d3.select(`[id='${id}']`);
insertMarkers(diagram);
// // Layout graph, Create a new directed graph
2019-09-25 21:01:21 +02:00
const graph = new graphlib.Graph({
multigraph: false,
2019-10-06 10:52:37 +02:00
compound: true,
2019-10-03 19:54:07 +02:00
// acyclicer: 'greedy',
rankdir: 'RL'
2019-09-25 21:01:21 +02:00
});
2019-10-03 19:54:07 +02:00
// // Set an object for the graph label
// graph.setGraph({
// isMultiGraph: false,
// rankdir: 'RL'
// });
// // Default to assigning a new object as a label for each new edge.
2019-09-25 21:01:21 +02:00
graph.setDefaultEdgeLabel(function() {
return {};
});
const rootDoc = stateDb.getRootDoc();
2019-10-05 09:02:20 +02:00
const n = renderDoc(rootDoc, diagram);
2019-10-03 19:54:07 +02:00
const bounds = diagram.node().getBBox();
diagram.attr('height', '100%');
diagram.attr('width', '100%');
2019-10-06 10:52:37 +02:00
diagram.attr('viewBox', '0 0 ' + bounds.width * 2 + ' ' + (bounds.height + 50));
};
const getLabelWidth = text => {
return text ? text.length * 5.02 : 1;
};
2019-10-05 09:02:20 +02:00
const renderDoc = (doc, diagram, parentId) => {
// // Layout graph, Create a new directed graph
2019-10-06 10:52:37 +02:00
const graph = new graphlib.Graph({
compound: true
});
// Set an object for the graph label
2019-10-03 19:54:07 +02:00
if (parentId)
graph.setGraph({
rankdir: 'LR',
// multigraph: false,
2019-10-06 10:52:37 +02:00
compound: true,
// acyclicer: 'greedy',
2019-10-03 19:54:07 +02:00
rankdir: 'LR',
2019-10-06 16:06:15 +02:00
ranker: 'tight-tree',
ranksep: '20'
2019-10-03 19:54:07 +02:00
// isMultiGraph: false
});
else {
graph.setGraph({
rankdir: 'TB',
2019-10-06 10:52:37 +02:00
compound: true,
// isCompound: true,
// acyclicer: 'greedy',
// ranker: 'longest-path'
ranker: 'tight-tree'
// ranker: 'network-simplex'
2019-10-03 19:54:07 +02:00
// isMultiGraph: false
});
}
// Default to assigning a new object as a label for each new edge.
graph.setDefaultEdgeLabel(function() {
return {};
});
stateDb.extract(doc);
2019-09-25 21:29:32 +02:00
const states = stateDb.getStates();
const relations = stateDb.getRelations();
2019-09-25 21:01:21 +02:00
const keys = Object.keys(states);
2019-09-25 21:01:21 +02:00
total = keys.length;
let first = true;
2019-09-25 21:01:21 +02:00
for (let i = 0; i < keys.length; i++) {
const stateDef = states[keys[i]];
2019-10-06 10:52:37 +02:00
let node;
if (stateDef.doc) {
let sub = diagram
.append('g')
.attr('id', stateDef.id)
.attr('class', 'classGroup');
node = renderDoc(stateDef.doc, sub, stateDef.id);
if (first) {
first = false;
sub = addIdAndBox(sub, stateDef);
let boxBounds = sub.node().getBBox();
node.width = boxBounds.width;
node.height = boxBounds.height + 10;
transformationLog[stateDef.id] = { y: 35 };
} else {
// sub = addIdAndBox(sub, stateDef);
let boxBounds = sub.node().getBBox();
node.width = boxBounds.width;
node.height = boxBounds.height;
// transformationLog[stateDef.id] = { y: 35 };
}
} else {
node = drawState(diagram, stateDef, graph);
}
2019-10-06 10:52:37 +02:00
if (stateDef.note) {
// Draw note note
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
// metadata about the node. In this case we're going to add labels to each of
// our nodes.
graph.setNode(node.id, node);
}
2019-09-25 21:01:21 +02:00
}
2019-10-06 16:06:15 +02:00
logger.info('Count=', graph.nodeCount());
2019-09-25 21:01:21 +02:00
relations.forEach(function(relation) {
graph.setEdge(relation.id1, relation.id2, {
relation: relation,
width: getLabelWidth(relation.title),
height: 16,
labelpos: 'c'
});
2019-09-25 21:01:21 +02:00
});
2019-09-25 21:01:21 +02:00
dagre.layout(graph);
2019-10-06 16:06:15 +02:00
logger.debug('Graph after layout', graph.nodes());
2019-10-06 10:52:37 +02:00
2019-09-25 21:01:21 +02:00
graph.nodes().forEach(function(v) {
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
2019-10-06 16:06:15 +02:00
logger.debug('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
2019-09-25 21:01:21 +02:00
d3.select('#' + v).attr(
'transform',
'translate(' +
(graph.node(v).x - graph.node(v).width / 2) +
',' +
(graph.node(v).y +
(transformationLog[v] ? transformationLog[v].y : 0) -
graph.node(v).height / 2) +
2019-09-25 21:01:21 +02:00
' )'
);
2019-10-06 15:44:31 +02:00
d3.select('#' + v).attr('data-x-shift', graph.node(v).x - graph.node(v).width / 2);
const dividers = document.querySelectorAll('#' + v + ' .divider');
dividers.forEach(divider => {
const parent = divider.parentElement;
let pWidth = 0;
let pShift = 0;
if (parent) {
if (parent.parentElement) pWidth = parent.parentElement.getBBox().width;
pShift = parseInt(parent.getAttribute('data-x-shift'), 10);
if (Number.isNaN(pShift)) {
pShift = 0;
}
}
divider.setAttribute('x1', 0 - pShift);
divider.setAttribute('x2', pWidth - pShift);
});
2019-10-06 10:52:37 +02:00
} else {
2019-10-06 16:06:15 +02:00
logger.debug('No Node ' + v + ': ' + JSON.stringify(graph.node(v)));
2019-09-25 21:01:21 +02:00
}
});
2019-10-06 15:44:31 +02:00
let stateBox = diagram.node().getBBox();
2019-09-25 21:01:21 +02:00
graph.edges().forEach(function(e) {
if (typeof e !== 'undefined' && typeof graph.edge(e) !== 'undefined') {
logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
drawEdge(diagram, graph.edge(e), graph.edge(e).relation);
}
});
stateBox = diagram.node().getBBox();
const stateInfo = {
id: parentId ? parentId : 'root',
label: parentId ? parentId : 'root',
width: 0,
height: 0
};
stateInfo.width = stateBox.width + 2 * conf.padding;
stateInfo.height = stateBox.height + 2 * conf.padding;
2019-10-06 16:06:15 +02:00
logger.info('Doc rendered', stateInfo, graph);
return stateInfo;
};
export default {
setConf,
draw
};