2020-03-22 21:45:14 +01:00
|
|
|
import graphlib from 'graphlib';
|
|
|
|
import * as d3 from 'd3';
|
|
|
|
import stateDb from './stateDb';
|
|
|
|
import state from './parser/stateDiagram';
|
|
|
|
import { getConfig } from '../../config';
|
|
|
|
|
|
|
|
import { render } from '../../dagre-wrapper/index.js';
|
|
|
|
import { logger } from '../../logger';
|
|
|
|
|
|
|
|
const conf = {};
|
|
|
|
export const setConf = function(cnf) {
|
|
|
|
const keys = Object.keys(cnf);
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
conf[keys[i]] = cnf[keys[i]];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const nodeDb = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the all the styles from classDef statements in the graph definition.
|
|
|
|
* @returns {object} classDef styles
|
|
|
|
*/
|
|
|
|
export const getClasses = function(text) {
|
|
|
|
logger.trace('Extracting classes');
|
|
|
|
stateDb.clear();
|
|
|
|
const parser = state.parser;
|
|
|
|
parser.yy = stateDb;
|
|
|
|
|
|
|
|
// Parse the graph definition
|
|
|
|
parser.parse(text);
|
|
|
|
return stateDb.getClasses();
|
|
|
|
};
|
|
|
|
|
2020-03-28 17:34:23 +01:00
|
|
|
const setupNode = (g, parent, node, altFlag) => {
|
2020-03-22 21:45:14 +01:00
|
|
|
// Add the node
|
|
|
|
if (node.id !== 'root') {
|
2020-03-25 20:16:27 +01:00
|
|
|
let shape = 'rect';
|
|
|
|
if (node.start === true) {
|
|
|
|
shape = 'start';
|
|
|
|
}
|
|
|
|
if (node.start === false) {
|
|
|
|
shape = 'end';
|
|
|
|
}
|
|
|
|
|
2020-03-22 21:45:14 +01:00
|
|
|
if (!nodeDb[node.id]) {
|
|
|
|
nodeDb[node.id] = {
|
2020-03-25 20:16:27 +01:00
|
|
|
id: node.id,
|
|
|
|
shape,
|
|
|
|
description: node.id
|
2020-03-22 21:45:14 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Description
|
2020-03-25 20:16:27 +01:00
|
|
|
if (node.description) {
|
2020-03-22 21:45:14 +01:00
|
|
|
nodeDb[node.id].description = node.description;
|
|
|
|
}
|
|
|
|
|
2020-03-25 20:16:27 +01:00
|
|
|
// Save data for description and group so that for instance a statement without description overwrites
|
|
|
|
// one with description
|
|
|
|
|
2020-03-22 21:45:14 +01:00
|
|
|
// group
|
|
|
|
if (!nodeDb[node.id].type && node.doc) {
|
|
|
|
logger.info('Setting cluser for ', node.id);
|
|
|
|
nodeDb[node.id].type = 'group';
|
2020-03-28 17:34:23 +01:00
|
|
|
nodeDb[node.id].shape = 'roundedWithTitle';
|
2020-03-22 21:45:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const nodeData = {
|
|
|
|
labelType: 'svg',
|
|
|
|
labelStyle: '',
|
2020-03-25 20:16:27 +01:00
|
|
|
shape: nodeDb[node.id].shape,
|
2020-03-22 21:45:14 +01:00
|
|
|
label: node.id,
|
2020-03-25 20:16:27 +01:00
|
|
|
labelText: nodeDb[node.id].description,
|
2020-03-28 17:34:23 +01:00
|
|
|
class: altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster', //classStr,
|
2020-03-22 21:45:14 +01:00
|
|
|
style: '', //styles.style,
|
|
|
|
id: node.id,
|
|
|
|
type: nodeDb[node.id].type,
|
|
|
|
padding: 15 //getConfig().flowchart.padding
|
|
|
|
};
|
|
|
|
|
|
|
|
g.setNode(node.id, nodeData);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parent) {
|
|
|
|
if (parent.id !== 'root') {
|
|
|
|
logger.trace('Setting node ', node.id, ' to be child of its parent ', parent.id);
|
|
|
|
g.setParent(node.id, parent.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (node.doc) {
|
|
|
|
logger.trace('Adding nodes children ');
|
2020-03-28 17:34:23 +01:00
|
|
|
setupDoc(g, node, node.doc, !altFlag);
|
2020-03-22 21:45:14 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let cnt = 0;
|
2020-03-28 17:34:23 +01:00
|
|
|
const setupDoc = (g, parent, doc, altFlag) => {
|
2020-03-22 21:45:14 +01:00
|
|
|
logger.trace('items', doc);
|
|
|
|
doc.forEach(item => {
|
|
|
|
if (item.stmt === 'state' || item.stmt === 'default') {
|
2020-03-28 17:34:23 +01:00
|
|
|
setupNode(g, parent, item, altFlag);
|
2020-03-22 21:45:14 +01:00
|
|
|
} else if (item.stmt === 'relation') {
|
2020-03-28 17:34:23 +01:00
|
|
|
setupNode(g, parent, item.state1, altFlag);
|
|
|
|
setupNode(g, parent, item.state2, altFlag);
|
2020-03-22 21:45:14 +01:00
|
|
|
const edgeData = {
|
|
|
|
arrowhead: 'normal',
|
2020-03-28 13:59:41 +01:00
|
|
|
arrowType: 'arrow_barb',
|
2020-03-22 21:45:14 +01:00
|
|
|
style: 'fill:none',
|
|
|
|
labelStyle: '',
|
|
|
|
arrowheadStyle: 'fill: #333',
|
|
|
|
labelpos: 'c',
|
|
|
|
labelType: 'text',
|
2020-03-28 13:59:41 +01:00
|
|
|
label: ''
|
2020-03-22 21:45:14 +01:00
|
|
|
};
|
2020-03-25 20:16:27 +01:00
|
|
|
let startId = item.state1.id;
|
|
|
|
let endId = item.state2.id;
|
|
|
|
|
|
|
|
g.setEdge(startId, endId, edgeData, cnt);
|
2020-03-22 21:45:14 +01:00
|
|
|
cnt++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2020-03-28 13:59:41 +01:00
|
|
|
logger.info('Drawing state diagram (v2)', id);
|
2020-03-22 21:45:14 +01:00
|
|
|
stateDb.clear();
|
|
|
|
const parser = state.parser;
|
|
|
|
parser.yy = stateDb;
|
|
|
|
|
|
|
|
// Parse the graph definition
|
|
|
|
try {
|
|
|
|
parser.parse(text);
|
|
|
|
} catch (err) {
|
|
|
|
logger.debug('Parsing failed');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch the default direction, use TD if none was found
|
|
|
|
let dir = stateDb.getDirection();
|
|
|
|
if (typeof dir === 'undefined') {
|
2020-03-28 17:34:23 +01:00
|
|
|
dir = 'LR';
|
2020-03-22 21:45:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const conf = getConfig().state;
|
|
|
|
const nodeSpacing = conf.nodeSpacing || 50;
|
|
|
|
const rankSpacing = conf.rankSpacing || 50;
|
|
|
|
|
|
|
|
// Create the input mermaid.graph
|
|
|
|
const g = new graphlib.Graph({
|
|
|
|
multigraph: true,
|
|
|
|
compound: true
|
|
|
|
})
|
|
|
|
.setGraph({
|
2020-03-28 17:34:23 +01:00
|
|
|
rankdir: 'LR',
|
2020-03-22 21:45:14 +01:00
|
|
|
nodesep: nodeSpacing,
|
|
|
|
ranksep: rankSpacing,
|
|
|
|
marginx: 8,
|
|
|
|
marginy: 8
|
|
|
|
})
|
|
|
|
.setDefaultEdgeLabel(function() {
|
|
|
|
return {};
|
|
|
|
});
|
|
|
|
|
2020-03-25 20:16:27 +01:00
|
|
|
// logger.info(stateDb.getRootDoc());
|
|
|
|
logger.info(stateDb.getRootDocV2());
|
|
|
|
setupNode(g, undefined, stateDb.getRootDocV2(), true);
|
2020-03-22 21:45:14 +01:00
|
|
|
|
|
|
|
// Set up an SVG group so that we can translate the final graph.
|
|
|
|
const svg = d3.select(`[id="${id}"]`);
|
|
|
|
|
|
|
|
// Run the renderer. This is what draws the final graph.
|
|
|
|
const element = d3.select('#' + id + ' g');
|
2020-03-28 13:59:41 +01:00
|
|
|
render(element, g, ['barb'], 'statediagram', id);
|
2020-03-22 21:45:14 +01:00
|
|
|
|
|
|
|
const padding = 8;
|
2020-03-28 13:59:41 +01:00
|
|
|
// const svgBounds = svg.node().getBBox();
|
|
|
|
// const width = svgBounds.width + padding * 2;
|
|
|
|
// const height = svgBounds.height + padding * 2;
|
|
|
|
// logger.debug(
|
|
|
|
// `new ViewBox 0 0 ${width} ${height}`,
|
|
|
|
// `translate(${padding + g._label.marginx}, ${padding + g._label.marginy})`
|
|
|
|
// );
|
|
|
|
|
|
|
|
// if (conf.useMaxWidth) {
|
|
|
|
// svg.attr('width', '100%');
|
|
|
|
// svg.attr('style', `max-width: ${width}px;`);
|
|
|
|
// } else {
|
|
|
|
// svg.attr('height', height);
|
|
|
|
// svg.attr('width', width);
|
|
|
|
// }
|
|
|
|
|
|
|
|
// svg.attr('viewBox', `0 0 ${width} ${height}`);
|
|
|
|
// svg
|
|
|
|
// .select('g')
|
|
|
|
// .attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`);
|
|
|
|
|
|
|
|
const bounds = svg.node().getBBox();
|
|
|
|
|
|
|
|
const width = bounds.width + padding * 2;
|
|
|
|
const height = bounds.height + padding * 2;
|
|
|
|
|
|
|
|
// diagram.attr('height', '100%');
|
|
|
|
// diagram.attr('style', `width: ${bounds.width * 3 + conf.padding * 2};`);
|
|
|
|
// diagram.attr('height', height);
|
|
|
|
|
|
|
|
// Zoom in a bit
|
|
|
|
svg.attr('width', width * 1.75);
|
|
|
|
svg.attr('class', 'statediagram');
|
|
|
|
// diagram.attr('height', bounds.height * 3 + conf.padding * 2);
|
|
|
|
svg.attr(
|
|
|
|
'viewBox',
|
|
|
|
`${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height
|
2020-03-22 21:45:14 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
// Add label rects for non html labels
|
|
|
|
if (!conf.htmlLabels) {
|
|
|
|
const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
|
|
|
|
for (let k = 0; k < labels.length; k++) {
|
|
|
|
const label = labels[k];
|
|
|
|
|
|
|
|
// Get dimensions of label
|
|
|
|
const dim = label.getBBox();
|
|
|
|
|
|
|
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
|
|
rect.setAttribute('rx', 0);
|
|
|
|
rect.setAttribute('ry', 0);
|
|
|
|
rect.setAttribute('width', dim.width);
|
|
|
|
rect.setAttribute('height', dim.height);
|
|
|
|
rect.setAttribute('style', 'fill:#e8e8e8;');
|
|
|
|
|
|
|
|
label.insertBefore(rect, label.firstChild);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export default {
|
|
|
|
setConf,
|
|
|
|
getClasses,
|
|
|
|
draw
|
|
|
|
};
|