#931 Aligning with code standard

This commit is contained in:
knsv 2019-09-12 12:58:04 -07:00
parent cf05a8d8fa
commit 34de31195f
5 changed files with 2415 additions and 2327 deletions

View File

@ -1,33 +1,33 @@
import * as d3 from 'd3' import * as d3 from 'd3';
import { sanitizeUrl } from '@braintree/sanitize-url' import { sanitizeUrl } from '@braintree/sanitize-url';
import { logger } from '../../logger' import { logger } from '../../logger';
import utils from '../../utils' import utils from '../../utils';
import { getConfig } from '../../config' import { getConfig } from '../../config';
const config = getConfig() const config = getConfig();
let vertices = {} let vertices = {};
let edges = [] let edges = [];
let classes = [] let classes = [];
let subGraphs = [] let subGraphs = [];
let subGraphLookup = {} let subGraphLookup = {};
let tooltips = {} let tooltips = {};
let subCount = 0 let subCount = 0;
let direction let direction;
// Functions to be run after graph rendering // Functions to be run after graph rendering
let funs = [] let funs = [];
const sanitize = text => { const sanitize = text => {
let txt = text let txt = text;
if (config.securityLevel !== 'loose') { if (config.securityLevel !== 'loose') {
txt = txt.replace(/<br>/g, '#br#') txt = txt.replace(/<br>/g, '#br#');
txt = txt.replace(/<br\S*?\/>/g, '#br#') txt = txt.replace(/<br\S*?\/>/g, '#br#');
txt = txt.replace(/</g, '&lt;').replace(/>/g, '&gt;') txt = txt.replace(/</g, '&lt;').replace(/>/g, '&gt;');
txt = txt.replace(/=/g, '&equals;') txt = txt.replace(/=/g, '&equals;');
txt = txt.replace(/#br#/g, '<br/>') txt = txt.replace(/#br#/g, '<br/>');
} }
return txt return txt;
} };
/** /**
* Function called by parser when a node definition has been found * Function called by parser when a node definition has been found
@ -37,53 +37,53 @@ const sanitize = text => {
* @param style * @param style
* @param classes * @param classes
*/ */
export const addVertex = function (_id, text, type, style, classes) { export const addVertex = function(_id, text, type, style, classes) {
let txt let txt;
let id = _id let id = _id;
if (typeof id === 'undefined') { if (typeof id === 'undefined') {
return return;
} }
if (id.trim().length === 0) { if (id.trim().length === 0) {
return return;
} }
if (id[0].match(/\d/)) id = 's' + id if (id[0].match(/\d/)) id = 's' + id;
if (typeof vertices[id] === 'undefined') { if (typeof vertices[id] === 'undefined') {
vertices[id] = { id: id, styles: [], classes: [] } vertices[id] = { id: id, styles: [], classes: [] };
} }
if (typeof text !== 'undefined') { if (typeof text !== 'undefined') {
txt = sanitize(text.trim()) txt = sanitize(text.trim());
// strip quotes if string starts and exnds with a quote // strip quotes if string starts and exnds with a quote
if (txt[0] === '"' && txt[txt.length - 1] === '"') { if (txt[0] === '"' && txt[txt.length - 1] === '"') {
txt = txt.substring(1, txt.length - 1) txt = txt.substring(1, txt.length - 1);
} }
vertices[id].text = txt vertices[id].text = txt;
} else { } else {
if (!vertices[id].text) { if (!vertices[id].text) {
vertices[id].text = _id vertices[id].text = _id;
} }
} }
if (typeof type !== 'undefined') { if (typeof type !== 'undefined') {
vertices[id].type = type vertices[id].type = type;
} }
if (typeof style !== 'undefined') { if (typeof style !== 'undefined') {
if (style !== null) { if (style !== null) {
style.forEach(function (s) { style.forEach(function(s) {
vertices[id].styles.push(s) vertices[id].styles.push(s);
}) });
} }
} }
if (typeof classes !== 'undefined') { if (typeof classes !== 'undefined') {
if (classes !== null) { if (classes !== null) {
classes.forEach(function (s) { classes.forEach(function(s) {
vertices[id].classes.push(s) vertices[id].classes.push(s);
}) });
} }
} }
} };
/** /**
* Function called by parser when a link/edge definition has been found * Function called by parser when a link/edge definition has been found
@ -92,134 +92,138 @@ export const addVertex = function (_id, text, type, style, classes) {
* @param type * @param type
* @param linktext * @param linktext
*/ */
export const addLink = function (_start, _end, type, linktext) { export const addLink = function(_start, _end, type, linktext) {
let start = _start let start = _start;
let end = _end let end = _end;
if (start[0].match(/\d/)) start = 's' + start if (start[0].match(/\d/)) start = 's' + start;
if (end[0].match(/\d/)) end = 's' + end if (end[0].match(/\d/)) end = 's' + end;
logger.info('Got edge...', start, end) logger.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '' } const edge = { start: start, end: end, type: undefined, text: '' };
linktext = type.text linktext = type.text;
if (typeof linktext !== 'undefined') { if (typeof linktext !== 'undefined') {
edge.text = sanitize(linktext.trim()) edge.text = sanitize(linktext.trim());
// strip quotes if string starts and exnds with a quote // strip quotes if string starts and exnds with a quote
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') { if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
edge.text = edge.text.substring(1, edge.text.length - 1) edge.text = edge.text.substring(1, edge.text.length - 1);
} }
} }
if (typeof type !== 'undefined') { if (typeof type !== 'undefined') {
edge.type = type.type edge.type = type.type;
edge.stroke = type.stroke edge.stroke = type.stroke;
} }
edges.push(edge) edges.push(edge);
} };
/** /**
* Updates a link's line interpolation algorithm * Updates a link's line interpolation algorithm
* @param pos * @param pos
* @param interpolate * @param interpolate
*/ */
export const updateLinkInterpolate = function (positions, interp) { export const updateLinkInterpolate = function(positions, interp) {
positions.forEach(function (pos) { positions.forEach(function(pos) {
if (pos === 'default') { if (pos === 'default') {
edges.defaultInterpolate = interp edges.defaultInterpolate = interp;
} else { } else {
edges[pos].interpolate = interp edges[pos].interpolate = interp;
} }
}) });
} };
/** /**
* Updates a link with a style * Updates a link with a style
* @param pos * @param pos
* @param style * @param style
*/ */
export const updateLink = function (positions, style) { export const updateLink = function(positions, style) {
positions.forEach(function (pos) { positions.forEach(function(pos) {
if (pos === 'default') { if (pos === 'default') {
edges.defaultStyle = style edges.defaultStyle = style;
} else { } else {
if (utils.isSubstringInArray('fill', style) === -1) { if (utils.isSubstringInArray('fill', style) === -1) {
style.push('fill:none') style.push('fill:none');
} }
edges[pos].style = style edges[pos].style = style;
} }
}) });
} };
export const addClass = function (id, style) { export const addClass = function(id, style) {
if (typeof classes[id] === 'undefined') { if (typeof classes[id] === 'undefined') {
classes[id] = { id: id, styles: [] } classes[id] = { id: id, styles: [] };
} }
if (typeof style !== 'undefined') { if (typeof style !== 'undefined') {
if (style !== null) { if (style !== null) {
style.forEach(function (s) { style.forEach(function(s) {
classes[id].styles.push(s) classes[id].styles.push(s);
}) });
} }
} }
} };
/** /**
* Called by parser when a graph definition is found, stores the direction of the chart. * Called by parser when a graph definition is found, stores the direction of the chart.
* @param dir * @param dir
*/ */
export const setDirection = function (dir) { export const setDirection = function(dir) {
direction = dir direction = dir;
} };
/** /**
* Called by parser when a special node is found, e.g. a clickable element. * Called by parser when a special node is found, e.g. a clickable element.
* @param ids Comma separated list of ids * @param ids Comma separated list of ids
* @param className Class to add * @param className Class to add
*/ */
export const setClass = function (ids, className) { export const setClass = function(ids, className) {
ids.split(',').forEach(function (_id) { ids.split(',').forEach(function(_id) {
let id = _id let id = _id;
if (_id[0].match(/\d/)) id = 's' + id if (_id[0].match(/\d/)) id = 's' + id;
if (typeof vertices[id] !== 'undefined') { if (typeof vertices[id] !== 'undefined') {
vertices[id].classes.push(className) vertices[id].classes.push(className);
} }
if (typeof subGraphLookup[id] !== 'undefined') { if (typeof subGraphLookup[id] !== 'undefined') {
subGraphLookup[id].classes.push(className) subGraphLookup[id].classes.push(className);
} }
}) });
} };
const setTooltip = function (ids, tooltip) { const setTooltip = function(ids, tooltip) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function(id) {
if (typeof tooltip !== 'undefined') { if (typeof tooltip !== 'undefined') {
tooltips[id] = sanitize(tooltip) tooltips[id] = sanitize(tooltip);
} }
}) });
} };
const setClickFun = function (_id, functionName) { const setClickFun = function(_id, functionName) {
let id = _id let id = _id;
if (_id[0].match(/\d/)) id = 's' + id if (_id[0].match(/\d/)) id = 's' + id;
if (config.securityLevel !== 'loose') { if (config.securityLevel !== 'loose') {
return return;
} }
if (typeof functionName === 'undefined') { if (typeof functionName === 'undefined') {
return return;
} }
if (typeof vertices[id] !== 'undefined') { if (typeof vertices[id] !== 'undefined') {
funs.push(function (element) { funs.push(function(element) {
const elem = document.querySelector(`[id="${id}"]`) const elem = document.querySelector(`[id="${id}"]`);
if (elem !== null) { if (elem !== null) {
elem.addEventListener('click', function () { elem.addEventListener(
window[functionName](id) 'click',
}, false) function() {
window[functionName](id);
},
false
);
} }
}) });
} }
} };
/** /**
* Called by parser when a link is found. Adds the URL to the vertex data. * Called by parser when a link is found. Adds the URL to the vertex data.
@ -227,24 +231,24 @@ const setClickFun = function (_id, functionName) {
* @param linkStr URL to create a link for * @param linkStr URL to create a link for
* @param tooltip Tooltip for the clickable element * @param tooltip Tooltip for the clickable element
*/ */
export const setLink = function (ids, linkStr, tooltip) { export const setLink = function(ids, linkStr, tooltip) {
ids.split(',').forEach(function (_id) { ids.split(',').forEach(function(_id) {
let id = _id let id = _id;
if (_id[0].match(/\d/)) id = 's' + id if (_id[0].match(/\d/)) id = 's' + id;
if (typeof vertices[id] !== 'undefined') { if (typeof vertices[id] !== 'undefined') {
if (config.securityLevel !== 'loose') { if (config.securityLevel !== 'loose') {
vertices[id].link = sanitizeUrl(linkStr) // .replace(/javascript:.*/g, '') vertices[id].link = sanitizeUrl(linkStr); // .replace(/javascript:.*/g, '')
} else { } else {
vertices[id].link = linkStr vertices[id].link = linkStr;
} }
} }
}) });
setTooltip(ids, tooltip) setTooltip(ids, tooltip);
setClass(ids, 'clickable') setClass(ids, 'clickable');
} };
export const getTooltip = function (id) { export const getTooltip = function(id) {
return tooltips[id] return tooltips[id];
} };
/** /**
* Called by parser when a click definition is found. Registers an event handler. * Called by parser when a click definition is found. Registers an event handler.
@ -252,209 +256,219 @@ export const getTooltip = function (id) {
* @param functionName Function to be called on click * @param functionName Function to be called on click
* @param tooltip Tooltip for the clickable element * @param tooltip Tooltip for the clickable element
*/ */
export const setClickEvent = function (ids, functionName, tooltip) { export const setClickEvent = function(ids, functionName, tooltip) {
ids.split(',').forEach(function (id) { setClickFun(id, functionName) }) ids.split(',').forEach(function(id) {
setTooltip(ids, tooltip) setClickFun(id, functionName);
setClass(ids, 'clickable') });
} setTooltip(ids, tooltip);
setClass(ids, 'clickable');
};
export const bindFunctions = function (element) { export const bindFunctions = function(element) {
funs.forEach(function (fun) { funs.forEach(function(fun) {
fun(element) fun(element);
}) });
} };
export const getDirection = function () { export const getDirection = function() {
return direction return direction;
} };
/** /**
* Retrieval function for fetching the found nodes after parsing has completed. * Retrieval function for fetching the found nodes after parsing has completed.
* @returns {{}|*|vertices} * @returns {{}|*|vertices}
*/ */
export const getVertices = function () { export const getVertices = function() {
return vertices return vertices;
} };
/** /**
* Retrieval function for fetching the found links after parsing has completed. * Retrieval function for fetching the found links after parsing has completed.
* @returns {{}|*|edges} * @returns {{}|*|edges}
*/ */
export const getEdges = function () { export const getEdges = function() {
return edges return edges;
} };
/** /**
* Retrieval function for fetching the found class definitions after parsing has completed. * Retrieval function for fetching the found class definitions after parsing has completed.
* @returns {{}|*|classes} * @returns {{}|*|classes}
*/ */
export const getClasses = function () { export const getClasses = function() {
return classes return classes;
} };
const setupToolTips = function (element) { const setupToolTips = function(element) {
let tooltipElem = d3.select('.mermaidTooltip') let tooltipElem = d3.select('.mermaidTooltip');
if ((tooltipElem._groups || tooltipElem)[0][0] === null) { if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
tooltipElem = d3.select('body') tooltipElem = d3
.select('body')
.append('div') .append('div')
.attr('class', 'mermaidTooltip') .attr('class', 'mermaidTooltip')
.style('opacity', 0) .style('opacity', 0);
} }
const svg = d3.select(element).select('svg') const svg = d3.select(element).select('svg');
const nodes = svg.selectAll('g.node') const nodes = svg.selectAll('g.node');
nodes nodes
.on('mouseover', function () { .on('mouseover', function() {
const el = d3.select(this) const el = d3.select(this);
const title = el.attr('title') const title = el.attr('title');
// Dont try to draw a tooltip if no data is provided // Dont try to draw a tooltip if no data is provided
if (title === null) { if (title === null) {
return return;
} }
const rect = this.getBoundingClientRect() const rect = this.getBoundingClientRect();
tooltipElem.transition() tooltipElem
.transition()
.duration(200) .duration(200)
.style('opacity', '.9') .style('opacity', '.9');
tooltipElem.html(el.attr('title')) tooltipElem
.style('left', (rect.left + (rect.right - rect.left) / 2) + 'px') .html(el.attr('title'))
.style('top', (rect.top - 14 + document.body.scrollTop) + 'px') .style('left', rect.left + (rect.right - rect.left) / 2 + 'px')
el.classed('hover', true) .style('top', rect.top - 14 + document.body.scrollTop + 'px');
el.classed('hover', true);
}) })
.on('mouseout', function () { .on('mouseout', function() {
tooltipElem.transition() tooltipElem
.transition()
.duration(500) .duration(500)
.style('opacity', 0) .style('opacity', 0);
const el = d3.select(this) const el = d3.select(this);
el.classed('hover', false) el.classed('hover', false);
}) });
} };
funs.push(setupToolTips) funs.push(setupToolTips);
/** /**
* Clears the internal graph db so that a new graph can be parsed. * Clears the internal graph db so that a new graph can be parsed.
*/ */
export const clear = function () { export const clear = function() {
vertices = {} vertices = {};
classes = {} classes = {};
edges = [] edges = [];
funs = [] funs = [];
funs.push(setupToolTips) funs.push(setupToolTips);
subGraphs = [] subGraphs = [];
subGraphLookup = {} subGraphLookup = {};
subCount = 0 subCount = 0;
tooltips = [] tooltips = [];
} };
/** /**
* *
* @returns {string} * @returns {string}
*/ */
export const defaultStyle = function () { export const defaultStyle = function() {
return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;' return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;';
} };
/** /**
* Clears the internal graph db so that a new graph can be parsed. * Clears the internal graph db so that a new graph can be parsed.
*/ */
export const addSubGraph = function (_id, list, _title) { export const addSubGraph = function(_id, list, _title) {
let id = _id let id = _id;
let title = _title let title = _title;
if (_id === _title && _title.match(/\s/)) { if (_id === _title && _title.match(/\s/)) {
id = undefined id = undefined;
} }
function uniq (a) { function uniq(a) {
const prims = { 'boolean': {}, 'number': {}, 'string': {} } const prims = { boolean: {}, number: {}, string: {} };
const objs = [] const objs = [];
return a.filter(function (item) { return a.filter(function(item) {
const type = typeof item const type = typeof item;
if (item.trim() === '') { if (item.trim() === '') {
return false return false;
} }
if (type in prims) { return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true) } else { return objs.indexOf(item) >= 0 ? false : objs.push(item) } if (type in prims) {
}) return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
} else {
return objs.indexOf(item) >= 0 ? false : objs.push(item);
}
});
} }
let nodeList = [] let nodeList = [];
nodeList = uniq(nodeList.concat.apply(nodeList, list)) nodeList = uniq(nodeList.concat.apply(nodeList, list));
for (let i = 0; i < nodeList.length; i++) { for (let i = 0; i < nodeList.length; i++) {
if (nodeList[i][0].match(/\d/)) nodeList[i] = 's' + nodeList[i] if (nodeList[i][0].match(/\d/)) nodeList[i] = 's' + nodeList[i];
} }
id = id || ('subGraph' + subCount) id = id || 'subGraph' + subCount;
if (id[0].match(/\d/)) id = 's' + id if (id[0].match(/\d/)) id = 's' + id;
title = title || '' title = title || '';
title = sanitize(title) title = sanitize(title);
subCount = subCount + 1 subCount = subCount + 1;
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] } const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
subGraphs.push(subGraph) subGraphs.push(subGraph);
subGraphLookup[id] = subGraph subGraphLookup[id] = subGraph;
return id return id;
} };
const getPosForId = function (id) { const getPosForId = function(id) {
for (let i = 0; i < subGraphs.length; i++) { for (let i = 0; i < subGraphs.length; i++) {
if (subGraphs[i].id === id) { if (subGraphs[i].id === id) {
return i return i;
} }
} }
return -1 return -1;
} };
let secCount = -1 let secCount = -1;
const posCrossRef = [] const posCrossRef = [];
const indexNodes2 = function (id, pos) { const indexNodes2 = function(id, pos) {
const nodes = subGraphs[pos].nodes const nodes = subGraphs[pos].nodes;
secCount = secCount + 1 secCount = secCount + 1;
if (secCount > 2000) { if (secCount > 2000) {
return return;
} }
posCrossRef[secCount] = pos posCrossRef[secCount] = pos;
// Check if match // Check if match
if (subGraphs[pos].id === id) { if (subGraphs[pos].id === id) {
return { return {
result: true, result: true,
count: 0 count: 0
} };
} }
let count = 0 let count = 0;
let posCount = 1 let posCount = 1;
while (count < nodes.length) { while (count < nodes.length) {
const childPos = getPosForId(nodes[count]) const childPos = getPosForId(nodes[count]);
// Ignore regular nodes (pos will be -1) // Ignore regular nodes (pos will be -1)
if (childPos >= 0) { if (childPos >= 0) {
const res = indexNodes2(id, childPos) const res = indexNodes2(id, childPos);
if (res.result) { if (res.result) {
return { return {
result: true, result: true,
count: posCount + res.count count: posCount + res.count
} };
} else { } else {
posCount = posCount + res.count posCount = posCount + res.count;
} }
} }
count = count + 1 count = count + 1;
} }
return { return {
result: false, result: false,
count: posCount count: posCount
} };
} };
export const getDepthFirstPos = function (pos) { export const getDepthFirstPos = function(pos) {
return posCrossRef[pos] return posCrossRef[pos];
} };
export const indexNodes = function () { export const indexNodes = function() {
secCount = -1 secCount = -1;
if (subGraphs.length > 0) { if (subGraphs.length > 0) {
indexNodes2('none', subGraphs.length - 1, 0) indexNodes2('none', subGraphs.length - 1, 0);
} }
} };
export const getSubGraphs = function () { export const getSubGraphs = function() {
return subGraphs return subGraphs;
} };
export default { export default {
addVertex, addVertex,
@ -478,4 +492,4 @@ export default {
getDepthFirstPos, getDepthFirstPos,
indexNodes, indexNodes,
getSubGraphs getSubGraphs
} };

View File

@ -1,274 +1,288 @@
import graphlib from 'graphlibrary' import graphlib from 'graphlibrary';
import * as d3 from 'd3' import * as d3 from 'd3';
import flowDb from './flowDb' import flowDb from './flowDb';
import flow from './parser/flow' import flow from './parser/flow';
import { getConfig } from '../../config' import { getConfig } from '../../config';
import dagreD3 from 'dagre-d3-renderer' import dagreD3 from 'dagre-d3-renderer';
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js' import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js';
import { logger } from '../../logger' import { logger } from '../../logger';
import { interpolateToCurve } from '../../utils' import { interpolateToCurve } from '../../utils';
const conf = { const conf = {};
} export const setConf = function(cnf) {
export const setConf = function (cnf) { const keys = Object.keys(cnf);
const keys = Object.keys(cnf)
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
conf[keys[i]] = cnf[keys[i]] conf[keys[i]] = cnf[keys[i]];
} }
} };
/** /**
* Function that adds the vertices found in the graph definition to the graph to be rendered. * Function that adds the vertices found in the graph definition to the graph to be rendered.
* @param vert Object containing the vertices. * @param vert Object containing the vertices.
* @param g The graph that is to be drawn. * @param g The graph that is to be drawn.
*/ */
export const addVertices = function (vert, g, svgId) { export const addVertices = function(vert, g, svgId) {
const svg = d3.select(`[id="${svgId}"]`) const svg = d3.select(`[id="${svgId}"]`);
const keys = Object.keys(vert) const keys = Object.keys(vert);
const styleFromStyleArr = function (styleStr, arr, { label }) { const styleFromStyleArr = function(styleStr, arr, { label }) {
if (!label) { if (!label) {
// Create a compound style definition from the style definitions found for the node in the graph definition // Create a compound style definition from the style definitions found for the node in the graph definition
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] !== 'undefined') { if (typeof arr[i] !== 'undefined') {
styleStr = styleStr + arr[i] + ';' styleStr = styleStr + arr[i] + ';';
} }
} }
} else { } else {
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] !== 'undefined') { if (typeof arr[i] !== 'undefined') {
if (arr[i].match('^color:')) styleStr = styleStr + arr[i] + ';' if (arr[i].match('^color:')) styleStr = styleStr + arr[i] + ';';
} }
} }
} }
return styleStr return styleStr;
} };
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) { keys.forEach(function(id) {
const vertex = vert[id] const vertex = vert[id];
/** /**
* Variable for storing the classes for the vertex * Variable for storing the classes for the vertex
* @type {string} * @type {string}
*/ */
let classStr = '' let classStr = '';
if (vertex.classes.length > 0) { if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ') classStr = vertex.classes.join(' ');
} }
/** /**
* Variable for storing the extracted style for the vertex * Variable for storing the extracted style for the vertex
* @type {string} * @type {string}
*/ */
let style = '' let style = '';
// Create a compound style definition from the style definitions found for the node in the graph definition // Create a compound style definition from the style definitions found for the node in the graph definition
style = styleFromStyleArr(style, vertex.styles, { label: false }) style = styleFromStyleArr(style, vertex.styles, { label: false });
let labelStyle = '' let labelStyle = '';
labelStyle = styleFromStyleArr(labelStyle, vertex.styles, { label: true }) labelStyle = styleFromStyleArr(labelStyle, vertex.styles, { label: true });
// Use vertex id as text in the box if no text is provided by the graph definition // Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
// We create a SVG label, either by delegating to addHtmlLabel or manually // We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode let vertexNode;
if (getConfig().flowchart.htmlLabels) { if (getConfig().flowchart.htmlLabels) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = { label: vertexText.replace(/fa[lrsb]?:fa-[\w-]+/g, s => `<i class='${s.replace(':', ' ')}'></i>`) } const node = {
vertexNode = addHtmlLabel(svg, node).node() label: vertexText.replace(
vertexNode.parentNode.removeChild(vertexNode) /fa[lrsb]?:fa-[\w-]+/g,
s => `<i class='${s.replace(':', ' ')}'></i>`
)
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
} else { } else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text') const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
const rows = vertexText.split(/<br[/]{0,1}>/) const rows = vertexText.split(/<br[/]{0,1}>/);
for (let j = 0; j < rows.length; j++) { for (let j = 0; j < rows.length; j++) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan') const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve') tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em') tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1') tspan.setAttribute('x', '1');
tspan.textContent = rows[j] tspan.textContent = rows[j];
svgLabel.appendChild(tspan) svgLabel.appendChild(tspan);
} }
vertexNode = svgLabel vertexNode = svgLabel;
} }
// If the node has a link, we wrap it in a SVG link // If the node has a link, we wrap it in a SVG link
if (vertex.link) { if (vertex.link) {
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a') const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link) link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener') link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
link.appendChild(vertexNode) link.appendChild(vertexNode);
vertexNode = link vertexNode = link;
} }
let radious = 0 let radious = 0;
let _shape = '' let _shape = '';
// Set the shape based parameters // Set the shape based parameters
switch (vertex.type) { switch (vertex.type) {
case 'round': case 'round':
radious = 5 radious = 5;
_shape = 'rect' _shape = 'rect';
break break;
case 'square': case 'square':
_shape = 'rect' _shape = 'rect';
break break;
case 'diamond': case 'diamond':
_shape = 'question' _shape = 'question';
break break;
case 'odd': case 'odd':
_shape = 'rect_left_inv_arrow' _shape = 'rect_left_inv_arrow';
break break;
case 'lean_right': case 'lean_right':
_shape = 'lean_right' _shape = 'lean_right';
break break;
case 'lean_left': case 'lean_left':
_shape = 'lean_left' _shape = 'lean_left';
break break;
case 'trapezoid': case 'trapezoid':
_shape = 'trapezoid' _shape = 'trapezoid';
break break;
case 'inv_trapezoid': case 'inv_trapezoid':
_shape = 'inv_trapezoid' _shape = 'inv_trapezoid';
break break;
case 'odd_right': case 'odd_right':
_shape = 'rect_left_inv_arrow' _shape = 'rect_left_inv_arrow';
break break;
case 'circle': case 'circle':
_shape = 'circle' _shape = 'circle';
break break;
case 'ellipse': case 'ellipse':
_shape = 'ellipse' _shape = 'ellipse';
break break;
case 'group': case 'group':
_shape = 'rect' _shape = 'rect';
break break;
default: default:
_shape = 'rect' _shape = 'rect';
} }
// Add the node // Add the node
g.setNode(vertex.id, { labelType: 'svg', labelStyle: labelStyle, shape: _shape, label: vertexNode, rx: radious, ry: radious, 'class': classStr, style: style, id: vertex.id }) g.setNode(vertex.id, {
}) labelType: 'svg',
} labelStyle: labelStyle,
shape: _shape,
label: vertexNode,
rx: radious,
ry: radious,
class: classStr,
style: style,
id: vertex.id
});
});
};
/** /**
* Add edges to graph based on parsed graph defninition * Add edges to graph based on parsed graph defninition
* @param {Object} edges The edges to add to the graph * @param {Object} edges The edges to add to the graph
* @param {Object} g The graph object * @param {Object} g The graph object
*/ */
export const addEdges = function (edges, g) { export const addEdges = function(edges, g) {
let cnt = 0 let cnt = 0;
let defaultStyle let defaultStyle;
if (typeof edges.defaultStyle !== 'undefined') { if (typeof edges.defaultStyle !== 'undefined') {
defaultStyle = edges.defaultStyle.toString().replace(/,/g, ';') defaultStyle = edges.defaultStyle.toString().replace(/,/g, ';');
} }
edges.forEach(function (edge) { edges.forEach(function(edge) {
cnt++ cnt++;
const edgeData = {} const edgeData = {};
// Set link type for rendering // Set link type for rendering
if (edge.type === 'arrow_open') { if (edge.type === 'arrow_open') {
edgeData.arrowhead = 'none' edgeData.arrowhead = 'none';
} else { } else {
edgeData.arrowhead = 'normal' edgeData.arrowhead = 'normal';
} }
let style = '' let style = '';
if (typeof edge.style !== 'undefined') { if (typeof edge.style !== 'undefined') {
edge.style.forEach(function (s) { edge.style.forEach(function(s) {
style = style + s + ';' style = style + s + ';';
}) });
} else { } else {
switch (edge.stroke) { switch (edge.stroke) {
case 'normal': case 'normal':
style = 'fill:none' style = 'fill:none';
if (typeof defaultStyle !== 'undefined') { if (typeof defaultStyle !== 'undefined') {
style = defaultStyle style = defaultStyle;
} }
break break;
case 'dotted': case 'dotted':
style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;' style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;';
break break;
case 'thick': case 'thick':
style = 'stroke: #333; stroke-width: 3.5px;fill:none' style = 'stroke: #333; stroke-width: 3.5px;fill:none';
break break;
} }
} }
edgeData.style = style edgeData.style = style;
if (typeof edge.interpolate !== 'undefined') { if (typeof edge.interpolate !== 'undefined') {
edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear) edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear);
} else if (typeof edges.defaultInterpolate !== 'undefined') { } else if (typeof edges.defaultInterpolate !== 'undefined') {
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear) edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear);
} else { } else {
edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear) edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear);
} }
if (typeof edge.text === 'undefined') { if (typeof edge.text === 'undefined') {
if (typeof edge.style !== 'undefined') { if (typeof edge.style !== 'undefined') {
edgeData.arrowheadStyle = 'fill: #333' edgeData.arrowheadStyle = 'fill: #333';
} }
} else { } else {
edgeData.arrowheadStyle = 'fill: #333' edgeData.arrowheadStyle = 'fill: #333';
if (typeof edge.style === 'undefined') { if (typeof edge.style === 'undefined') {
edgeData.labelpos = 'c' edgeData.labelpos = 'c';
if (getConfig().flowchart.htmlLabels) { if (getConfig().flowchart.htmlLabels) {
edgeData.labelType = 'html' edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>' edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
} else { } else {
edgeData.labelType = 'text' edgeData.labelType = 'text';
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none' edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
edgeData.label = edge.text.replace(/<br>/g, '\n') edgeData.label = edge.text.replace(/<br>/g, '\n');
} }
} else { } else {
edgeData.label = edge.text.replace(/<br>/g, '\n') edgeData.label = edge.text.replace(/<br>/g, '\n');
} }
} }
// Add the edge to the graph // Add the edge to the graph
g.setEdge(edge.start, edge.end, edgeData, cnt) g.setEdge(edge.start, edge.end, edgeData, cnt);
}) });
} };
/** /**
* Returns the all the styles from classDef statements in the graph definition. * Returns the all the styles from classDef statements in the graph definition.
* @returns {object} classDef styles * @returns {object} classDef styles
*/ */
export const getClasses = function (text) { export const getClasses = function(text) {
logger.info('Extracting classes') logger.info('Extracting classes');
flowDb.clear() flowDb.clear();
const parser = flow.parser const parser = flow.parser;
parser.yy = flowDb parser.yy = flowDb;
// Parse the graph definition // Parse the graph definition
parser.parse(text) parser.parse(text);
return flowDb.getClasses() return flowDb.getClasses();
} };
/** /**
* Draws a flowchart in the tag with id: id based on the graph definition in text. * Draws a flowchart in the tag with id: id based on the graph definition in text.
* @param text * @param text
* @param id * @param id
*/ */
export const draw = function (text, id) { export const draw = function(text, id) {
logger.info('Drawing flowchart') logger.info('Drawing flowchart');
flowDb.clear() flowDb.clear();
const parser = flow.parser const parser = flow.parser;
parser.yy = flowDb parser.yy = flowDb;
// Parse the graph definition // Parse the graph definition
try { try {
parser.parse(text) parser.parse(text);
} catch (err) { } catch (err) {
logger.debug('Parsing failed') logger.debug('Parsing failed');
} }
// Fetch the default direction, use TD if none was found // Fetch the default direction, use TD if none was found
let dir = flowDb.getDirection() let dir = flowDb.getDirection();
if (typeof dir === 'undefined') { if (typeof dir === 'undefined') {
dir = 'TD' dir = 'TD';
} }
// Create the input mermaid.graph // Create the input mermaid.graph
@ -280,196 +294,238 @@ export const draw = function (text, id) {
rankdir: dir, rankdir: dir,
marginx: 20, marginx: 20,
marginy: 20 marginy: 20
})
.setDefaultEdgeLabel(function () {
return {}
}) })
.setDefaultEdgeLabel(function() {
return {};
});
let subG let subG;
const subGraphs = flowDb.getSubGraphs() const subGraphs = flowDb.getSubGraphs();
for (let i = subGraphs.length - 1; i >= 0; i--) { for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i] subG = subGraphs[i];
flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes) flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
} }
// Fetch the verices/nodes and edges/links from the parsed graph definition // Fetch the verices/nodes and edges/links from the parsed graph definition
const vert = flowDb.getVertices() const vert = flowDb.getVertices();
const edges = flowDb.getEdges() const edges = flowDb.getEdges();
let i = 0 let i = 0;
for (i = subGraphs.length - 1; i >= 0; i--) { for (i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i] subG = subGraphs[i];
d3.selectAll('cluster').append('text') d3.selectAll('cluster').append('text');
for (let j = 0; j < subG.nodes.length; j++) { for (let j = 0; j < subG.nodes.length; j++) {
g.setParent(subG.nodes[j], subG.id) g.setParent(subG.nodes[j], subG.id);
} }
} }
addVertices(vert, g, id) addVertices(vert, g, id);
addEdges(edges, g) addEdges(edges, g);
// Create the renderer // Create the renderer
const Render = dagreD3.render const Render = dagreD3.render;
const render = new Render() const render = new Render();
// Add custom shape for rhombus type of boc (decision) // Add custom shape for rhombus type of boc (decision)
render.shapes().question = function (parent, bbox, node) { render.shapes().question = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const s = (w + h) * 0.9 const s = (w + h) * 0.9;
const points = [ const points = [
{ x: s / 2, y: 0 }, { x: s / 2, y: 0 },
{ x: s, y: -s / 2 }, { x: s, y: -s / 2 },
{ x: s / 2, y: -s }, { x: s / 2, y: -s },
{ x: 0, y: -s / 2 } { x: 0, y: -s / 2 }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
points
.map(function(d) {
return d.x + ',' + d.y;
})
.join(' ')
)
.attr('rx', 5) .attr('rx', 5)
.attr('ry', 5) .attr('ry', 5)
.attr('transform', 'translate(' + (-s / 2) + ',' + (s * 2 / 4) + ')') .attr('transform', 'translate(' + -s / 2 + ',' + (s * 2) / 4 + ')');
node.intersect = function (point) { node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point) return dagreD3.intersect.polygon(node, points, point);
} };
return shapeSvg return shapeSvg;
} };
// Add custom shape for box with inverted arrow on left side // Add custom shape for box with inverted arrow on left side
render.shapes().rect_left_inv_arrow = function (parent, bbox, node) { render.shapes().rect_left_inv_arrow = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const points = [ const points = [
{ x: -h / 2, y: 0 }, { x: -h / 2, y: 0 },
{ x: w, y: 0 }, { x: w, y: 0 },
{ x: w, y: -h }, { x: w, y: -h },
{ x: -h / 2, y: -h }, { x: -h / 2, y: -h },
{ x: 0, y: -h / 2 } { x: 0, y: -h / 2 }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') points
node.intersect = function (point) { .map(function(d) {
return dagreD3.intersect.polygon(node, points, point) return d.x + ',' + d.y;
} })
return shapeSvg .join(' ')
} )
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side // Add custom shape for box with inverted arrow on left side
render.shapes().lean_right = function (parent, bbox, node) { render.shapes().lean_right = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const points = [ const points = [
{ x: -2 * h / 6, y: 0 }, { x: (-2 * h) / 6, y: 0 },
{ x: w - h / 6, y: 0 }, { x: w - h / 6, y: 0 },
{ x: w + 2 * h / 6, y: -h }, { x: w + (2 * h) / 6, y: -h },
{ x: h / 6, y: -h } { x: h / 6, y: -h }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') points
node.intersect = function (point) { .map(function(d) {
return dagreD3.intersect.polygon(node, points, point) return d.x + ',' + d.y;
} })
return shapeSvg .join(' ')
} )
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side // Add custom shape for box with inverted arrow on left side
render.shapes().lean_left = function (parent, bbox, node) { render.shapes().lean_left = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const points = [ const points = [
{ x: 2 * h / 6, y: 0 }, { x: (2 * h) / 6, y: 0 },
{ x: w + h / 6, y: 0 }, { x: w + h / 6, y: 0 },
{ x: w - 2 * h / 6, y: -h }, { x: w - (2 * h) / 6, y: -h },
{ x: -h / 6, y: -h } { x: -h / 6, y: -h }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') points
node.intersect = function (point) { .map(function(d) {
return dagreD3.intersect.polygon(node, points, point) return d.x + ',' + d.y;
} })
return shapeSvg .join(' ')
} )
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side // Add custom shape for box with inverted arrow on left side
render.shapes().trapezoid = function (parent, bbox, node) { render.shapes().trapezoid = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const points = [ const points = [
{ x: -2 * h / 6, y: 0 }, { x: (-2 * h) / 6, y: 0 },
{ x: w + 2 * h / 6, y: 0 }, { x: w + (2 * h) / 6, y: 0 },
{ x: w - h / 6, y: -h }, { x: w - h / 6, y: -h },
{ x: h / 6, y: -h } { x: h / 6, y: -h }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') points
node.intersect = function (point) { .map(function(d) {
return dagreD3.intersect.polygon(node, points, point) return d.x + ',' + d.y;
} })
return shapeSvg .join(' ')
} )
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on left side // Add custom shape for box with inverted arrow on left side
render.shapes().inv_trapezoid = function (parent, bbox, node) { render.shapes().inv_trapezoid = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const points = [ const points = [
{ x: h / 6, y: 0 }, { x: h / 6, y: 0 },
{ x: w - h / 6, y: 0 }, { x: w - h / 6, y: 0 },
{ x: w + 2 * h / 6, y: -h }, { x: w + (2 * h) / 6, y: -h },
{ x: -2 * h / 6, y: -h } { x: (-2 * h) / 6, y: -h }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') points
node.intersect = function (point) { .map(function(d) {
return dagreD3.intersect.polygon(node, points, point) return d.x + ',' + d.y;
} })
return shapeSvg .join(' ')
} )
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add custom shape for box with inverted arrow on right side // Add custom shape for box with inverted arrow on right side
render.shapes().rect_right_inv_arrow = function (parent, bbox, node) { render.shapes().rect_right_inv_arrow = function(parent, bbox, node) {
const w = bbox.width const w = bbox.width;
const h = bbox.height const h = bbox.height;
const points = [ const points = [
{ x: 0, y: 0 }, { x: 0, y: 0 },
{ x: w + h / 2, y: 0 }, { x: w + h / 2, y: 0 },
{ x: w, y: -h / 2 }, { x: w, y: -h / 2 },
{ x: w + h / 2, y: -h }, { x: w + h / 2, y: -h },
{ x: 0, y: -h } { x: 0, y: -h }
] ];
const shapeSvg = parent.insert('polygon', ':first-child') const shapeSvg = parent
.attr('points', points.map(function (d) { .insert('polygon', ':first-child')
return d.x + ',' + d.y .attr(
}).join(' ')) 'points',
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') points
node.intersect = function (point) { .map(function(d) {
return dagreD3.intersect.polygon(node, points, point) return d.x + ',' + d.y;
} })
return shapeSvg .join(' ')
} )
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
node.intersect = function(point) {
return dagreD3.intersect.polygon(node, points, point);
};
return shapeSvg;
};
// Add our custom arrow - an empty arrowhead // Add our custom arrow - an empty arrowhead
render.arrows().none = function normal (parent, id, edge, type) { render.arrows().none = function normal(parent, id, edge, type) {
const marker = parent.append('marker') const marker = parent
.append('marker')
.attr('id', id) .attr('id', id)
.attr('viewBox', '0 0 10 10') .attr('viewBox', '0 0 10 10')
.attr('refX', 9) .attr('refX', 9)
@ -477,16 +533,16 @@ export const draw = function (text, id) {
.attr('markerUnits', 'strokeWidth') .attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 8) .attr('markerWidth', 8)
.attr('markerHeight', 6) .attr('markerHeight', 6)
.attr('orient', 'auto') .attr('orient', 'auto');
const path = marker.append('path') const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
.attr('d', 'M 0 0 L 0 0 L 0 0 z') dagreD3.util.applyStyle(path, edge[type + 'Style']);
dagreD3.util.applyStyle(path, edge[type + 'Style']) };
}
// Override normal arrowhead defined in d3. Remove style & add class to allow css styling. // Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
render.arrows().normal = function normal (parent, id, edge, type) { render.arrows().normal = function normal(parent, id, edge, type) {
const marker = parent.append('marker') const marker = parent
.append('marker')
.attr('id', id) .attr('id', id)
.attr('viewBox', '0 0 10 10') .attr('viewBox', '0 0 10 10')
.attr('refX', 9) .attr('refX', 9)
@ -494,76 +550,76 @@ export const draw = function (text, id) {
.attr('markerUnits', 'strokeWidth') .attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 8) .attr('markerWidth', 8)
.attr('markerHeight', 6) .attr('markerHeight', 6)
.attr('orient', 'auto') .attr('orient', 'auto');
marker.append('path') marker
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z') .attr('d', 'M 0 0 L 10 5 L 0 10 z')
.attr('class', 'arrowheadPath') .attr('class', 'arrowheadPath')
.style('stroke-width', 1) .style('stroke-width', 1)
.style('stroke-dasharray', '1,0') .style('stroke-dasharray', '1,0');
} };
// Set up an SVG group so that we can translate the final graph. // Set up an SVG group so that we can translate the final graph.
const svg = d3.select(`[id="${id}"]`) const svg = d3.select(`[id="${id}"]`);
// Run the renderer. This is what draws the final graph. // Run the renderer. This is what draws the final graph.
const element = d3.select('#' + id + ' g') const element = d3.select('#' + id + ' g');
render(element, g) render(element, g);
element.selectAll('g.node') element.selectAll('g.node').attr('title', function() {
.attr('title', function () { return flowDb.getTooltip(this.id);
return flowDb.getTooltip(this.id) });
})
const padding = 8 const padding = 8;
const width = g.maxX - g.minX + padding * 2 const width = g.maxX - g.minX + padding * 2;
const height = g.maxY - g.minY + padding * 2 const height = g.maxY - g.minY + padding * 2;
svg.attr('width', '100%') svg.attr('width', '100%');
svg.attr('style', `max-width: ${width}px;`) svg.attr('style', `max-width: ${width}px;`);
svg.attr('viewBox', `0 0 ${width} ${height}`) svg.attr('viewBox', `0 0 ${width} ${height}`);
svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`) svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`);
// Index nodes // Index nodes
flowDb.indexNodes('subGraph' + i) flowDb.indexNodes('subGraph' + i);
// reposition labels // reposition labels
for (i = 0; i < subGraphs.length; i++) { for (i = 0; i < subGraphs.length; i++) {
subG = subGraphs[i] subG = subGraphs[i];
if (subG.title !== 'undefined') { if (subG.title !== 'undefined') {
const clusterRects = document.querySelectorAll('#' + id + ' #' + subG.id + ' rect') const clusterRects = document.querySelectorAll('#' + id + ' #' + subG.id + ' rect');
const clusterEl = document.querySelectorAll('#' + id + ' #' + subG.id) const clusterEl = document.querySelectorAll('#' + id + ' #' + subG.id);
const xPos = clusterRects[0].x.baseVal.value const xPos = clusterRects[0].x.baseVal.value;
const yPos = clusterRects[0].y.baseVal.value const yPos = clusterRects[0].y.baseVal.value;
const width = clusterRects[0].width.baseVal.value const width = clusterRects[0].width.baseVal.value;
const cluster = d3.select(clusterEl[0]) const cluster = d3.select(clusterEl[0]);
const te = cluster.select('.label') const te = cluster.select('.label');
te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`) te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`);
te.attr('id', id + 'Text') te.attr('id', id + 'Text');
} }
} }
// Add label rects for non html labels // Add label rects for non html labels
if (!getConfig().flowchart.htmlLabels) { if (!getConfig().flowchart.htmlLabels) {
const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label') const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label');
for (let k = 0; k < labels.length; k++) { for (let k = 0; k < labels.length; k++) {
const label = labels[k] const label = labels[k];
// Get dimensions of label // Get dimensions of label
const dim = label.getBBox() const dim = label.getBBox();
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('rx', 0) rect.setAttribute('rx', 0);
rect.setAttribute('ry', 0) rect.setAttribute('ry', 0);
rect.setAttribute('width', dim.width) rect.setAttribute('width', dim.width);
rect.setAttribute('height', dim.height) rect.setAttribute('height', dim.height);
rect.setAttribute('style', 'fill:#e8e8e8;') rect.setAttribute('style', 'fill:#e8e8e8;');
label.insertBefore(rect, label.firstChild) label.insertBefore(rect, label.firstChild);
} }
} }
} };
export default { export default {
setConf, setConf,
@ -571,4 +627,4 @@ export default {
addEdges, addEdges,
getClasses, getClasses,
draw draw
} };

View File

@ -1,38 +1,37 @@
import flowDb from '../flowDb' import flowDb from '../flowDb';
import flow from './flow' import flow from './flow';
import { setConfig } from '../../../config' import { setConfig } from '../../../config';
setConfig({ setConfig({
securityLevel: 'strict', securityLevel: 'strict'
}) });
describe('when parsing flowcharts', function () { describe('when parsing flowcharts', function() {
beforeEach(function () { beforeEach(function() {
flow.parser.yy = flowDb flow.parser.yy = flowDb;
flow.parser.yy.clear() flow.parser.yy.clear();
}) });
it('should handle chaining of vertices', function () { it('should handle chaining of vertices', function() {
const res = flow.parser.parse(` const res = flow.parser.parse(`
graph TD graph TD
A-->B-->C; A-->B-->C;
`); `);
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A') expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B') expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C') expect(vert['C'].id).toBe('C');
expect(edges.length).toBe(2) expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A') expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B') expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('') expect(edges[0].text).toBe('');
expect(edges[1].start).toBe('B') expect(edges[1].start).toBe('B');
expect(edges[1].end).toBe('C') expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow') expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('') expect(edges[1].text).toBe('');
}) });
});
})

File diff suppressed because it is too large Load Diff

View File

@ -1,223 +1,223 @@
import flowDb from '../flowDb' import flowDb from '../flowDb';
import flow from './flow' import flow from './flow';
import { setConfig } from '../../../config' import { setConfig } from '../../../config';
setConfig({ setConfig({
securityLevel: 'strict', securityLevel: 'strict'
}) });
describe('when parsing subgraphs', function () { describe('when parsing subgraphs', function() {
beforeEach(function () { beforeEach(function() {
flow.parser.yy = flowDb flow.parser.yy = flowDb;
flow.parser.yy.clear() flow.parser.yy.clear();
}) });
it('should handle subgraph with tab indentation', function () { it('should handle subgraph with tab indentation', function() {
const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2\nend') const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2\nend');
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2) expect(subgraph.nodes.length).toBe(2);
expect(subgraph.nodes[0]).toBe('a2') expect(subgraph.nodes[0]).toBe('a2');
expect(subgraph.nodes[1]).toBe('a1') expect(subgraph.nodes[1]).toBe('a1');
expect(subgraph.title).toBe('One') expect(subgraph.title).toBe('One');
expect(subgraph.id).toBe('One') expect(subgraph.id).toBe('One');
}) });
it('should handle subgraph with chaining nodes indentation', function () { it('should handle subgraph with chaining nodes indentation', function() {
const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2-->a3\nend') const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2-->a3\nend');
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(3) expect(subgraph.nodes.length).toBe(3);
expect(subgraph.nodes[0]).toBe('a3') expect(subgraph.nodes[0]).toBe('a3');
expect(subgraph.nodes[1]).toBe('a2') expect(subgraph.nodes[1]).toBe('a2');
expect(subgraph.nodes[2]).toBe('a1') expect(subgraph.nodes[2]).toBe('a1');
expect(subgraph.title).toBe('One') expect(subgraph.title).toBe('One');
expect(subgraph.id).toBe('One') expect(subgraph.id).toBe('One');
})
it('should handle subgraph with multiple words in title', function () {
const res = flow.parser.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend')
const subgraphs = flow.parser.yy.getSubGraphs()
expect(subgraphs.length).toBe(1)
const subgraph = subgraphs[0]
expect(subgraph.nodes.length).toBe(2)
expect(subgraph.nodes[0]).toBe('a2')
expect(subgraph.nodes[1]).toBe('a1')
expect(subgraph.title).toBe('Some Title')
expect(subgraph.id).toBe('subGraph0')
}); });
it('should handle subgraph with id and title notation', function () { it('should handle subgraph with multiple words in title', function() {
const res = flow.parser.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend') const res = flow.parser.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend');
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2) expect(subgraph.nodes.length).toBe(2);
expect(subgraph.nodes[0]).toBe('a2') expect(subgraph.nodes[0]).toBe('a2');
expect(subgraph.nodes[1]).toBe('a1') expect(subgraph.nodes[1]).toBe('a1');
expect(subgraph.title).toBe('Some Title') expect(subgraph.title).toBe('Some Title');
expect(subgraph.id).toBe('some-id') expect(subgraph.id).toBe('subGraph0');
}); });
xit('should handle subgraph without id and space in title', function () { it('should handle subgraph with id and title notation', function() {
const res = flow.parser.parse('graph TB\nsubgraph Some Title\n\ta1-->a2\nend') const res = flow.parser.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend');
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2) expect(subgraph.nodes.length).toBe(2);
expect(subgraph.nodes[0]).toBe('a1') expect(subgraph.nodes[0]).toBe('a2');
expect(subgraph.nodes[1]).toBe('a2') expect(subgraph.nodes[1]).toBe('a1');
expect(subgraph.title).toBe('Some Title') expect(subgraph.title).toBe('Some Title');
expect(subgraph.id).toBe('some-id') expect(subgraph.id).toBe('some-id');
}); });
it('should handle subgraph id starting with a number', function () { xit('should handle subgraph without id and space in title', function() {
const res = flow.parser.parse('graph TB\nsubgraph Some Title\n\ta1-->a2\nend');
const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
expect(subgraph.nodes[0]).toBe('a1');
expect(subgraph.nodes[1]).toBe('a2');
expect(subgraph.title).toBe('Some Title');
expect(subgraph.id).toBe('some-id');
});
it('should handle subgraph id starting with a number', function() {
const res = flow.parser.parse(`graph TD const res = flow.parser.parse(`graph TD
A[Christmas] -->|Get money| B(Go shopping) A[Christmas] -->|Get money| B(Go shopping)
subgraph 1test subgraph 1test
A A
end`) end`);
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(1) expect(subgraph.nodes.length).toBe(1);
expect(subgraph.nodes[0]).toBe('A') expect(subgraph.nodes[0]).toBe('A');
expect(subgraph.id).toBe('s1test') expect(subgraph.id).toBe('s1test');
}); });
it('should handle subgraphs1', function () { it('should handle subgraphs1', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs with title in quotes', function () { it('should handle subgraphs with title in quotes', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph "title in quotes";c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph "title in quotes";c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.title).toBe('title in quotes') expect(subgraph.title).toBe('title in quotes');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs in old style that was broken', function () { it('should handle subgraphs in old style that was broken', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph old style that is broken;c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph old style that is broken;c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.title).toBe('old style that is broken') expect(subgraph.title).toBe('old style that is broken');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs with dashes in the title', function () { it('should handle subgraphs with dashes in the title', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph a-b-c;c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph a-b-c;c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.title).toBe('a-b-c') expect(subgraph.title).toBe('a-b-c');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs with id and title in brackets', function () { it('should handle subgraphs with id and title in brackets', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph uid1[text of doom];c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph uid1[text of doom];c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.title).toBe('text of doom') expect(subgraph.title).toBe('text of doom');
expect(subgraph.id).toBe('uid1') expect(subgraph.id).toBe('uid1');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs with id and title in brackets and quotes', function () { it('should handle subgraphs with id and title in brackets and quotes', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph uid2["text of doom"];c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph uid2["text of doom"];c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.title).toBe('text of doom') expect(subgraph.title).toBe('text of doom');
expect(subgraph.id).toBe('uid2') expect(subgraph.id).toBe('uid2');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs with id and title in brackets without spaces', function () { it('should handle subgraphs with id and title in brackets without spaces', function() {
const res = flow.parser.parse('graph TD;A-->B;subgraph uid2[textofdoom];c-->d;end;') const res = flow.parser.parse('graph TD;A-->B;subgraph uid2[textofdoom];c-->d;end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
const subgraphs = flow.parser.yy.getSubGraphs() const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1) expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0] const subgraph = subgraphs[0];
expect(subgraph.title).toBe('textofdoom') expect(subgraph.title).toBe('textofdoom');
expect(subgraph.id).toBe('uid2') expect(subgraph.id).toBe('uid2');
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs2', function () { it('should handle subgraphs2', function() {
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\n\n c-->d \nend\n') const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\n\n c-->d \nend\n');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle nested subgraphs', function () { it('should handle nested subgraphs', function() {
const str = 'graph TD\n' + const str =
'A-->B\n' + 'graph TD\n' +
'subgraph myTitle\n\n' + 'A-->B\n' +
' c-->d \n\n' + 'subgraph myTitle\n\n' +
' subgraph inner\n\n e-->f \n end \n\n' + ' c-->d \n\n' +
' subgraph inner\n\n h-->i \n end \n\n' + ' subgraph inner\n\n e-->f \n end \n\n' +
'end\n' ' subgraph inner\n\n h-->i \n end \n\n' +
const res = flow.parser.parse(str) 'end\n';
}) const res = flow.parser.parse(str);
});
it('should handle subgraphs4', function () { it('should handle subgraphs4', function() {
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;') const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
it('should handle subgraphs5', function () { it('should handle subgraphs5', function() {
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;') const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;');
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges() const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow') expect(edges[0].type).toBe('arrow');
}) });
});
})