2019-09-21 08:19:55 -07:00
|
|
|
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';
|
2019-09-28 13:31:10 +02:00
|
|
|
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';
|
2019-09-21 08:19:55 -07:00
|
|
|
|
|
|
|
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
|
2019-09-21 08:19:55 -07:00
|
|
|
const conf = {
|
|
|
|
dividerMargin: 10,
|
|
|
|
padding: 5,
|
|
|
|
textHeight: 10
|
|
|
|
};
|
|
|
|
|
2019-10-03 19:08:15 +02:00
|
|
|
const transformationLog = {};
|
|
|
|
|
2019-09-21 08:19:55 -07:00
|
|
|
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')
|
2019-09-28 13:31:10 +02:00
|
|
|
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
|
2019-09-25 21:01:21 +02:00
|
|
|
};
|
|
|
|
|
2019-09-21 08:19:55 -07: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);
|
2019-09-21 08:19:55 -07:00
|
|
|
|
|
|
|
// /// / 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);
|
2019-09-21 08:19:55 -07:00
|
|
|
|
|
|
|
// // Layout graph, Create a new directed graph
|
2019-09-25 21:01:21 +02:00
|
|
|
const graph = new graphlib.Graph({
|
2019-10-02 19:32:13 +02:00
|
|
|
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-10-09 20:05:24 +02:00
|
|
|
// ranksep: '20'
|
2019-09-25 21:01:21 +02:00
|
|
|
});
|
2019-09-21 08:19:55 -07:00
|
|
|
|
2019-10-03 19:54:07 +02:00
|
|
|
// // Set an object for the graph label
|
|
|
|
// graph.setGraph({
|
|
|
|
// isMultiGraph: false,
|
|
|
|
// rankdir: 'RL'
|
|
|
|
// });
|
2019-09-21 08:19:55 -07:00
|
|
|
|
|
|
|
// // 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 {};
|
|
|
|
});
|
|
|
|
|
2019-10-03 19:08:15 +02:00
|
|
|
const rootDoc = stateDb.getRootDoc();
|
2019-10-05 09:02:20 +02:00
|
|
|
const n = renderDoc(rootDoc, diagram);
|
2019-10-03 19:08:15 +02:00
|
|
|
|
2019-10-03 19:54:07 +02:00
|
|
|
const bounds = diagram.node().getBBox();
|
2019-10-03 19:08:15 +02:00
|
|
|
|
2019-10-09 21:15:17 +02:00
|
|
|
diagram.attr('height', '100%');
|
2019-10-09 20:05:24 +02:00
|
|
|
// diagram.attr('width', 'fit-content');
|
2019-10-10 20:55:27 +02:00
|
|
|
diagram.attr('style', `width: ${bounds.width * 3 + conf.padding * 2};`);
|
2019-10-09 20:05:24 +02:00
|
|
|
diagram.attr(
|
|
|
|
'viewBox',
|
2019-10-09 21:15:17 +02:00
|
|
|
`${conf.padding * -1} ${conf.padding * -1} ` +
|
|
|
|
(bounds.width * 1.5 + conf.padding * 2) +
|
|
|
|
' ' +
|
|
|
|
(bounds.height * 1.5 + conf.padding * 2)
|
2019-10-09 20:05:24 +02:00
|
|
|
);
|
2019-10-03 19:08:15 +02:00
|
|
|
};
|
|
|
|
const getLabelWidth = text => {
|
|
|
|
return text ? text.length * 5.02 : 1;
|
|
|
|
};
|
|
|
|
|
2019-10-05 09:02:20 +02:00
|
|
|
const renderDoc = (doc, diagram, parentId) => {
|
2019-10-03 19:08:15 +02:00
|
|
|
// // Layout graph, Create a new directed graph
|
2019-10-06 10:52:37 +02:00
|
|
|
const graph = new graphlib.Graph({
|
|
|
|
compound: true
|
|
|
|
});
|
2019-10-03 19:08:15 +02:00
|
|
|
|
|
|
|
// Set an object for the graph label
|
2019-10-03 19:54:07 +02:00
|
|
|
if (parentId)
|
|
|
|
graph.setGraph({
|
|
|
|
rankdir: 'LR',
|
2019-10-06 15:53:34 +02:00
|
|
|
// multigraph: false,
|
2019-10-06 10:52:37 +02:00
|
|
|
compound: true,
|
2019-10-06 15:53:34 +02:00
|
|
|
// 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'
|
2019-10-09 20:05:24 +02:00
|
|
|
ranksep: '20',
|
2019-10-06 10:52:37 +02:00
|
|
|
ranker: 'tight-tree'
|
|
|
|
// ranker: 'network-simplex'
|
2019-10-03 19:54:07 +02:00
|
|
|
// isMultiGraph: false
|
|
|
|
});
|
|
|
|
}
|
2019-10-03 19:08:15 +02:00
|
|
|
|
2019-10-05 10:02:58 +02:00
|
|
|
// Default to assigning a new object as a label for each new edge.
|
2019-10-03 19:08:15 +02:00
|
|
|
graph.setDefaultEdgeLabel(function() {
|
|
|
|
return {};
|
|
|
|
});
|
|
|
|
|
|
|
|
stateDb.extract(doc);
|
2019-09-25 21:29:32 +02:00
|
|
|
const states = stateDb.getStates();
|
2019-10-03 19:08:15 +02:00
|
|
|
const relations = stateDb.getRelations();
|
|
|
|
|
2019-09-25 21:01:21 +02:00
|
|
|
const keys = Object.keys(states);
|
2019-10-02 19:32:13 +02:00
|
|
|
|
2019-09-25 21:01:21 +02:00
|
|
|
total = keys.length;
|
2019-10-06 15:53:34 +02:00
|
|
|
let first = true;
|
2019-10-10 20:55:27 +02:00
|
|
|
|
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
|
|
|
|
2019-10-10 17:57:29 +02:00
|
|
|
if (parentId) {
|
|
|
|
stateDef.parentId = parentId;
|
|
|
|
}
|
|
|
|
|
2019-10-03 19:08:15 +02:00
|
|
|
let node;
|
|
|
|
if (stateDef.doc) {
|
|
|
|
let sub = diagram
|
|
|
|
.append('g')
|
|
|
|
.attr('id', stateDef.id)
|
|
|
|
.attr('class', 'classGroup');
|
2019-10-05 10:02:58 +02:00
|
|
|
node = renderDoc(stateDef.doc, sub, stateDef.id);
|
2019-10-03 19:08:15 +02:00
|
|
|
|
2019-10-06 15:53:34 +02:00
|
|
|
if (first) {
|
2019-10-09 20:05:24 +02:00
|
|
|
// first = false;
|
2019-10-06 15:53:34 +02:00
|
|
|
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 };
|
|
|
|
}
|
2019-10-03 19:08:15 +02:00
|
|
|
} else {
|
|
|
|
node = drawState(diagram, stateDef, graph);
|
|
|
|
}
|
2019-10-02 19:32:13 +02:00
|
|
|
|
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) {
|
2019-10-03 19:08:15 +02:00
|
|
|
graph.setEdge(relation.id1, relation.id2, {
|
2019-10-02 19:32:13 +02:00
|
|
|
relation: relation,
|
2019-10-03 19:08:15 +02:00
|
|
|
width: getLabelWidth(relation.title),
|
|
|
|
height: 16,
|
|
|
|
labelpos: 'c'
|
2019-10-02 19:32:13 +02:00
|
|
|
});
|
2019-09-25 21:01:21 +02:00
|
|
|
});
|
2019-10-03 19:08:15 +02:00
|
|
|
|
2019-09-25 21:01:21 +02:00
|
|
|
dagre.layout(graph);
|
2019-10-03 19:08:15 +02:00
|
|
|
|
2019-10-06 16:06:15 +02:00
|
|
|
logger.debug('Graph after layout', graph.nodes());
|
2019-10-10 20:55:27 +02:00
|
|
|
const svgElem = diagram.node();
|
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-10 20:55:27 +02:00
|
|
|
logger.warn('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
|
|
|
d3.select('#' + svgElem.id + ' #' + v).attr(
|
2019-09-25 21:01:21 +02:00
|
|
|
'transform',
|
|
|
|
'translate(' +
|
|
|
|
(graph.node(v).x - graph.node(v).width / 2) +
|
|
|
|
',' +
|
2019-10-03 19:08:15 +02:00
|
|
|
(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-10 20:55:27 +02:00
|
|
|
d3.select('#' + svgElem.id + ' #' + v).attr(
|
|
|
|
'data-x-shift',
|
|
|
|
graph.node(v).x - graph.node(v).width / 2
|
|
|
|
);
|
|
|
|
const dividers = document.querySelectorAll('#' + svgElem.id + ' #' + v + ' .divider');
|
2019-10-06 15:44:31 +02:00
|
|
|
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
|
|
|
|
2019-10-10 20:55:27 +02:00
|
|
|
let stateBox = svgElem.getBBox();
|
2019-10-03 19:08:15 +02:00
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-10-10 20:55:27 +02:00
|
|
|
stateBox = svgElem.getBBox();
|
|
|
|
console.warn('Diagram node', svgElem.id);
|
2019-10-03 19:08:15 +02:00
|
|
|
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);
|
2019-10-03 19:08:15 +02:00
|
|
|
return stateInfo;
|
|
|
|
};
|
2019-09-21 08:19:55 -07:00
|
|
|
|
|
|
|
export default {
|
|
|
|
setConf,
|
|
|
|
draw
|
|
|
|
};
|