diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 7e89d9cd7..ddbb78756 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -47,6 +47,7 @@ export const registerDiagram = ( throw new Error(`Diagram ${id} already registered.`); } diagrams[id] = diagram; + if (detector) { addDetector(id, detector); } diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js index f7e1a38db..228a678ae 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.js +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js @@ -54,6 +54,23 @@ export const lookUpDomId = function (id) { return id; }; +/** + * Function to lookup domId from id in the graph definition. + * + * @param id + * @param domId + * @public + */ +export const lookUpId = function (domId) { + const veritceKeys = Object.keys(vertices); + for (const veritceKey of veritceKeys) { + if (vertices[veritceKey].domId === domId) { + return vertices[veritceKey].id; + } + } + return domId; +}; + /** * Function called by parser when a node definition has been found * @@ -775,6 +792,7 @@ export default { setAccDescription, addVertex, lookUpDomId, + lookUpId, addLink, updateLinkInterpolate, updateLink, diff --git a/packages/mermaid/src/diagrams/flowchart/swimlane/detector.ts b/packages/mermaid/src/diagrams/flowchart/swimlane/detector.ts index e72101f88..6da1d6c06 100644 --- a/packages/mermaid/src/diagrams/flowchart/swimlane/detector.ts +++ b/packages/mermaid/src/diagrams/flowchart/swimlane/detector.ts @@ -4,14 +4,9 @@ import type { ExternalDiagramDefinition, DiagramDetector } from '../../../diagra const id = 'swimlane'; const detector: DiagramDetector = (txt: string, config?: MermaidConfig): boolean => { - - if ( - - txt.match(/^\s*swimlane/)) { - console.log("swimlane detector true"); + if (txt.match(/^\s*swimlane/)) { return true; } - console.log("swimlane detector false"); return false; }; diff --git a/packages/mermaid/src/diagrams/flowchart/swimlane/setup-graph.js b/packages/mermaid/src/diagrams/flowchart/swimlane/setup-graph.js new file mode 100644 index 000000000..5347c7392 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/swimlane/setup-graph.js @@ -0,0 +1,395 @@ +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; +import { select, curveLinear, selectAll } from 'd3'; +import { getConfig } from '../../../config.js'; +import utils from '../../../utils.js'; + +import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; +import { log } from '../../../logger.js'; +import common, { evaluate } from '../../common/common.js'; +import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; + +const conf = {}; +export const setConf = function (cnf) { + const keys = Object.keys(cnf); + for (const key of keys) { + conf[key] = cnf[key]; + } +}; + +/** + * Add edges to graph based on parsed graph definition + * + * @param {object} edges The edges to add to the graph + * @param {object} g The graph object + * @param diagObj + */ +export const addEdges = function (edges, g, diagObj) { + log.info('abc78 edges = ', edges); + let cnt = 0; + let linkIdCnt = {}; + + let defaultStyle; + let defaultLabelStyle; + + if (edges.defaultStyle !== undefined) { + const defaultStyles = getStylesFromArray(edges.defaultStyle); + defaultStyle = defaultStyles.style; + defaultLabelStyle = defaultStyles.labelStyle; + } + + edges.forEach(function (edge) { + cnt++; + + // Identify Link + var linkIdBase = 'L-' + edge.start + '-' + edge.end; + // count the links from+to the same node to give unique id + if (linkIdCnt[linkIdBase] === undefined) { + linkIdCnt[linkIdBase] = 0; + log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); + } else { + linkIdCnt[linkIdBase]++; + log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); + } + let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase]; + log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]); + var linkNameStart = 'LS-' + edge.start; + var linkNameEnd = 'LE-' + edge.end; + + const edgeData = { style: '', labelStyle: '' }; + edgeData.minlen = edge.length || 1; + //edgeData.id = 'id' + cnt; + + // Set link type for rendering + if (edge.type === 'arrow_open') { + edgeData.arrowhead = 'none'; + } else { + edgeData.arrowhead = 'normal'; + } + + // Check of arrow types, placed here in order not to break old rendering + edgeData.arrowTypeStart = 'arrow_open'; + edgeData.arrowTypeEnd = 'arrow_open'; + + /* eslint-disable no-fallthrough */ + switch (edge.type) { + case 'double_arrow_cross': + edgeData.arrowTypeStart = 'arrow_cross'; + case 'arrow_cross': + edgeData.arrowTypeEnd = 'arrow_cross'; + break; + case 'double_arrow_point': + edgeData.arrowTypeStart = 'arrow_point'; + case 'arrow_point': + edgeData.arrowTypeEnd = 'arrow_point'; + break; + case 'double_arrow_circle': + edgeData.arrowTypeStart = 'arrow_circle'; + case 'arrow_circle': + edgeData.arrowTypeEnd = 'arrow_circle'; + break; + } + + let style = ''; + let labelStyle = ''; + + switch (edge.stroke) { + case 'normal': + style = 'fill:none;'; + if (defaultStyle !== undefined) { + style = defaultStyle; + } + if (defaultLabelStyle !== undefined) { + labelStyle = defaultLabelStyle; + } + edgeData.thickness = 'normal'; + edgeData.pattern = 'solid'; + break; + case 'dotted': + edgeData.thickness = 'normal'; + edgeData.pattern = 'dotted'; + edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;'; + break; + case 'thick': + edgeData.thickness = 'thick'; + edgeData.pattern = 'solid'; + edgeData.style = 'stroke-width: 3.5px;fill:none;'; + break; + case 'invisible': + edgeData.thickness = 'invisible'; + edgeData.pattern = 'solid'; + edgeData.style = 'stroke-width: 0;fill:none;'; + break; + } + if (edge.style !== undefined) { + const styles = getStylesFromArray(edge.style); + style = styles.style; + labelStyle = styles.labelStyle; + } + + edgeData.style = edgeData.style += style; + edgeData.labelStyle = edgeData.labelStyle += labelStyle; + + if (edge.interpolate !== undefined) { + edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); + } else if (edges.defaultInterpolate !== undefined) { + edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear); + } else { + edgeData.curve = interpolateToCurve(conf.curve, curveLinear); + } + + if (edge.text === undefined) { + if (edge.style !== undefined) { + edgeData.arrowheadStyle = 'fill: #333'; + } + } else { + edgeData.arrowheadStyle = 'fill: #333'; + edgeData.labelpos = 'c'; + } + + edgeData.labelType = edge.labelType; + edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); + + if (edge.style === undefined) { + edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;'; + } + + edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:'); + + edgeData.id = linkId; + edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd; + + // Add the edge to the graph + g.setEdge(edge.start, edge.end, edgeData, cnt); + }); +}; + +/** + * Function that adds the vertices found during parsing to the graph to be rendered. + * + * @param vert Object containing the vertices. + * @param g The graph that is to be drawn. + * @param svgId + * @param root + * @param doc + * @param diagObj + */ +export const addVertices = function (vert, g, svgId, root, doc, diagObj) { + const svg = root.select(`[id="${svgId}"]`); + const keys = Object.keys(vert); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + keys.forEach(function (id) { + const vertex = vert[id]; + + /** + * Variable for storing the classes for the vertex + * + * @type {string} + */ + let classStr = 'default'; + if (vertex.classes.length > 0) { + classStr = vertex.classes.join(' '); + } + classStr = classStr + ' flowchart-label'; + const styles = getStylesFromArray(vertex.styles); + + // 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; + + // We create a SVG label, either by delegating to addHtmlLabel or manually + let vertexNode; + log.info('vertex', vertex, vertex.labelType); + if (vertex.labelType === 'markdown') { + log.info('vertex', vertex, vertex.labelType); + } else { + if (evaluate(getConfig().flowchart.htmlLabels) && svg.html) { + // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? + const node = { + label: vertexText.replace( + /fa[blrs]?:fa-[\w-]+/g, + (s) => `` + ), + }; + vertexNode = addHtmlLabel(svg, node).node(); + vertexNode.parentNode.removeChild(vertexNode); + } else { + const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); + svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); + + const rows = vertexText.split(common.lineBreakRegex); + + for (const row of rows) { + const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + tspan.setAttribute('dy', '1em'); + tspan.setAttribute('x', '1'); + tspan.textContent = row; + svgLabel.appendChild(tspan); + } + vertexNode = svgLabel; + } + } + + let radious = 0; + let _shape = ''; + // Set the shape based parameters + switch (vertex.type) { + case 'round': + radious = 5; + _shape = 'rect'; + break; + case 'square': + _shape = 'rect'; + break; + case 'diamond': + _shape = 'question'; + break; + case 'hexagon': + _shape = 'hexagon'; + break; + case 'odd': + _shape = 'rect_left_inv_arrow'; + break; + case 'lean_right': + _shape = 'lean_right'; + break; + case 'lean_left': + _shape = 'lean_left'; + break; + case 'trapezoid': + _shape = 'trapezoid'; + break; + case 'inv_trapezoid': + _shape = 'inv_trapezoid'; + break; + case 'odd_right': + _shape = 'rect_left_inv_arrow'; + break; + case 'circle': + _shape = 'circle'; + break; + case 'ellipse': + _shape = 'ellipse'; + break; + case 'stadium': + _shape = 'stadium'; + break; + case 'subroutine': + _shape = 'subroutine'; + break; + case 'cylinder': + _shape = 'cylinder'; + break; + case 'group': + _shape = 'rect'; + break; + case 'doublecircle': + _shape = 'doublecircle'; + break; + default: + _shape = 'rect'; + } + // Add the node + g.setNode(vertex.id, { + labelStyle: styles.labelStyle, + shape: _shape, + labelText: vertexText, + labelType: vertex.labelType, + rx: radious, + ry: radious, + class: classStr, + style: styles.style, + id: vertex.id, + link: vertex.link, + linkTarget: vertex.linkTarget, + tooltip: diagObj.db.getTooltip(vertex.id) || '', + domId: diagObj.db.lookUpDomId(vertex.id), + haveCallback: vertex.haveCallback, + width: vertex.type === 'group' ? 500 : undefined, + dir: vertex.dir, + type: vertex.type, + props: vertex.props, + padding: getConfig().flowchart.padding, + }); + + log.info('setNode', { + labelStyle: styles.labelStyle, + labelType: vertex.labelType, + shape: _shape, + labelText: vertexText, + rx: radious, + ry: radious, + class: classStr, + style: styles.style, + id: vertex.id, + domId: diagObj.db.lookUpDomId(vertex.id), + width: vertex.type === 'group' ? 500 : undefined, + type: vertex.type, + dir: vertex.dir, + props: vertex.props, + padding: getConfig().flowchart.padding, + }); + }); +}; + +/** + * + * @param diagObj + * @param id + * @param root + * @param doc + */ +function setupGraph(diagObj, id, root, doc) { + const { securityLevel, flowchart: conf } = getConfig(); + const nodeSpacing = conf.nodeSpacing || 50; + const rankSpacing = conf.rankSpacing || 50; + + // Fetch the default direction, use TD if none was found + let dir = diagObj.db.getDirection(); + if (dir === undefined) { + dir = 'TD'; + } + + // Create the input mermaid.graph + const g = new graphlib.Graph({ + multigraph: true, + compound: true, + }) + .setGraph({ + rankdir: dir, + nodesep: nodeSpacing, + ranksep: rankSpacing, + marginx: 0, + marginy: 0, + }) + .setDefaultEdgeLabel(function () { + return {}; + }); + + let subG; + const subGraphs = diagObj.db.getSubGraphs(); + + // Fetch the vertices/nodes and edges/links from the parsed graph definition + const vert = diagObj.db.getVertices(); + + const edges = diagObj.db.getEdges(); + + log.info('Edges', edges); + let i = 0; + // for (i = subGraphs.length - 1; i >= 0; i--) { + // // for (let i = 0; i < subGraphs.length; i++) { + // subG = subGraphs[i]; + + // selectAll('cluster').append('text'); + + // for (let j = 0; j < subG.nodes.length; j++) { + // log.info('Setting up subgraphs', subG.nodes[j], subG.id); + // g.setParent(subG.nodes[j], subG.id); + // } + // } + addVertices(vert, g, id, root, doc, diagObj); + addEdges(edges, g, diagObj); + return g; +} + +export default setupGraph; diff --git a/packages/mermaid/src/diagrams/flowchart/swimlane/swimlane-layout.js b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlane-layout.js new file mode 100644 index 000000000..8d48d9106 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlane-layout.js @@ -0,0 +1,74 @@ +import { log } from '../../../logger.js'; +import flowDb from '../flowDb.js'; + +export const getSubgraphLookupTable = function (diagObj) { + const subGraphs = diagObj.db.getSubGraphs(); + const subgraphDb = {}; + log.info('Subgraphs - ', subGraphs); + for (let i = subGraphs.length - 1; i >= 0; i--) { + const subG = subGraphs[i]; + log.info('Subgraph - ', subG); + for (let j = 0; j < subG.nodes.length; j++) { + log.info('Setting up subgraphs', subG.nodes[j], subG.id); + subgraphDb[flowDb.lookUpId(subG.nodes[j])] = subG.id; + } + } + return subgraphDb; +}; + +/** + * + * @param graph + * @param subgraphLĂ–ookupTable + * @param subgraphLookupTable + */ +export function assignRanks(graph, subgraphLookupTable) { + const visited = new Set(); + const ranks = new Map(); + + // DFS function for graph traversal + /** + * + * @param nodeId + * @param currentRank + * @param previousNodeId + */ + function dfs(nodeId, currentRank, previousNodeId) { + if (visited.has(nodeId)) { + return; + } + visited.add(nodeId); + + // Assign the maximum rank between the current rank and previously assigned rank + const existingRank = ranks.get(nodeId) || 0; + + ranks.set(nodeId, Math.max(existingRank, currentRank)); + + graph.successors(nodeId).forEach((targetId) => { + // Special swimlane case if the previous node is from another swimlane, keep the rank + if (subgraphLookupTable[targetId] !== subgraphLookupTable[nodeId]) { + dfs(targetId, currentRank, nodeId); + } else { + dfs(targetId, currentRank + 1, nodeId); + } + }); + } + + // Start DFS from nodes with no incoming edges + graph.nodes().forEach((nodeId) => { + if (graph.predecessors(nodeId).length === 0) { + dfs(nodeId, 0); + } + }); + + return ranks; +} + +/** + * + * @param graph + */ +export function swimlaneLayout(graph) { + const ranks = assignRanks(graph); + return graph; +} diff --git a/packages/mermaid/src/diagrams/flowchart/swimlane/swimlane-layout.spec.ts b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlane-layout.spec.ts new file mode 100644 index 000000000..d4e01ddfa --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlane-layout.spec.ts @@ -0,0 +1,58 @@ +import flowDb from '../flowDb.js'; +import { cleanupComments } from '../../../diagram-api/comments.js'; +import setupGraph from './setup-graph.js'; +import { select } from 'd3'; +import { swimlaneLayout, assignRanks, getSubgraphLookupTable } from './swimlane-layout.js'; +import { getDiagramFromText } from '../../../Diagram.js'; +import { addDiagrams } from '../../../diagram-api/diagram-orchestration.ts'; +import jsdom from 'jsdom'; + +const { JSDOM } = jsdom; + +addDiagrams(); +describe('When doing a layout specific for swim lanes ', () => { + let root; + let doc; + beforeEach(function () { + const dom = new JSDOM(`
My First JSDOM!
`); + root = select(dom.window.document.getElementById('swimmer')); + root.html = () => { + ' return
hello
'; + }; + + doc = dom.window.document; + }); + + it('should rank the nodes:', async () => { + const diagram = await getDiagramFromText(`swimlane LR + subgraph "\`one\`" + start --> cat --> rat + end`); + const g = setupGraph(diagram, 'swimmer', root, doc); + const subgraphLookupTable = getSubgraphLookupTable(diagram); + const ranks = assignRanks(g, subgraphLookupTable); + expect(ranks.get('start')).toEqual(0); + expect(ranks.get('cat')).toEqual(1); + expect(ranks.get('rat')).toEqual(2); + }); + + it('should rank the nodes:', async () => { + const diagram = await getDiagramFromText(`swimlane LR + subgraph "\`one\`" + start --> cat --> rat + end + subgraph "\`two\`" + monkey --> dog --> done + end + cat --> monkey`); + const g = setupGraph(diagram, 'swimmer', root, doc); + const subgraphLookupTable = getSubgraphLookupTable(diagram); + const ranks = assignRanks(g, subgraphLookupTable); + expect(ranks.get('start')).toEqual(0); + expect(ranks.get('cat')).toEqual(1); + expect(ranks.get('rat')).toEqual(2); + expect(ranks.get('monkey')).toEqual(1); + expect(ranks.get('dog')).toEqual(2); + expect(ranks.get('done')).toEqual(3); + }); +}); diff --git a/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js index 6d96c4730..fb6b6d783 100644 --- a/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/swimlane/swimlaneRenderer.js @@ -1,16 +1,14 @@ import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { select, curveLinear, selectAll } from 'd3'; +import swimlaneLayout from './swimlane-layout.js'; -import flowDb from './flowDb.js'; -import { getConfig } from '../../config.js'; -import utils from '../../utils.js'; - -import { render } from '../../dagre-wrapper/index.js'; -import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; -import { log } from '../../logger.js'; -import common, { evaluate } from '../common/common.js'; -import { interpolateToCurve, getStylesFromArray } from '../../utils.js'; -import { setupGraphViewbox } from '../../setupGraphViewbox.js'; +import flowDb from '../flowDb.js'; +import { getConfig } from '../../../config.js'; +import utils from '../../../utils.js'; +import setupGraph, { addEdges, addVertices } from './setup-graph.js'; +import { render } from '../../../dagre-wrapper/index.js'; +import { log } from '../../../logger.js'; +import { setupGraphViewbox } from '../../../setupGraphViewbox.js'; const conf = {}; export const setConf = function (cnf) { @@ -20,338 +18,14 @@ export const setConf = function (cnf) { } }; - /** - * - * @param graph - */ - function swimlaneLayout(graph) { +/** + * + * @param element + * @param graph + */ +function swimlaneRender(element, graph) { return graph; } - /** - * - * @param element - * @param graph - */ - function swimlaneRender(element,graph) { - return graph; -} - - -/** - * Function that adds the vertices found during parsing to the graph to be rendered. - * - * @param vert Object containing the vertices. - * @param g The graph that is to be drawn. - * @param svgId - * @param root - * @param doc - * @param diagObj - */ -export const addVertices = function (vert, g, svgId, root, doc, diagObj) { - const svg = root.select(`[id="${svgId}"]`); - const keys = Object.keys(vert); - - // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition - keys.forEach(function (id) { - const vertex = vert[id]; - - /** - * Variable for storing the classes for the vertex - * - * @type {string} - */ - let classStr = 'default'; - if (vertex.classes.length > 0) { - classStr = vertex.classes.join(' '); - } - classStr = classStr + ' flowchart-label'; - const styles = getStylesFromArray(vertex.styles); - - // 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; - - // We create a SVG label, either by delegating to addHtmlLabel or manually - let vertexNode; - log.info('vertex', vertex, vertex.labelType); - if (vertex.labelType === 'markdown') { - log.info('vertex', vertex, vertex.labelType); - } else { - if (evaluate(getConfig().flowchart.htmlLabels)) { - // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? - const node = { - label: vertexText.replace( - /fa[blrs]?:fa-[\w-]+/g, - (s) => `` - ), - }; - vertexNode = addHtmlLabel(svg, node).node(); - vertexNode.parentNode.removeChild(vertexNode); - } else { - const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); - svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); - - const rows = vertexText.split(common.lineBreakRegex); - - for (const row of rows) { - const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); - tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - tspan.setAttribute('dy', '1em'); - tspan.setAttribute('x', '1'); - tspan.textContent = row; - svgLabel.appendChild(tspan); - } - vertexNode = svgLabel; - } - } - - let radious = 0; - let _shape = ''; - // Set the shape based parameters - switch (vertex.type) { - case 'round': - radious = 5; - _shape = 'rect'; - break; - case 'square': - _shape = 'rect'; - break; - case 'diamond': - _shape = 'question'; - break; - case 'hexagon': - _shape = 'hexagon'; - break; - case 'odd': - _shape = 'rect_left_inv_arrow'; - break; - case 'lean_right': - _shape = 'lean_right'; - break; - case 'lean_left': - _shape = 'lean_left'; - break; - case 'trapezoid': - _shape = 'trapezoid'; - break; - case 'inv_trapezoid': - _shape = 'inv_trapezoid'; - break; - case 'odd_right': - _shape = 'rect_left_inv_arrow'; - break; - case 'circle': - _shape = 'circle'; - break; - case 'ellipse': - _shape = 'ellipse'; - break; - case 'stadium': - _shape = 'stadium'; - break; - case 'subroutine': - _shape = 'subroutine'; - break; - case 'cylinder': - _shape = 'cylinder'; - break; - case 'group': - _shape = 'rect'; - break; - case 'doublecircle': - _shape = 'doublecircle'; - break; - default: - _shape = 'rect'; - } - // Add the node - g.setNode(vertex.id, { - labelStyle: styles.labelStyle, - shape: _shape, - labelText: vertexText, - labelType: vertex.labelType, - rx: radious, - ry: radious, - class: classStr, - style: styles.style, - id: vertex.id, - link: vertex.link, - linkTarget: vertex.linkTarget, - tooltip: diagObj.db.getTooltip(vertex.id) || '', - domId: diagObj.db.lookUpDomId(vertex.id), - haveCallback: vertex.haveCallback, - width: vertex.type === 'group' ? 500 : undefined, - dir: vertex.dir, - type: vertex.type, - props: vertex.props, - padding: getConfig().flowchart.padding, - }); - - log.info('setNode', { - labelStyle: styles.labelStyle, - labelType: vertex.labelType, - shape: _shape, - labelText: vertexText, - rx: radious, - ry: radious, - class: classStr, - style: styles.style, - id: vertex.id, - domId: diagObj.db.lookUpDomId(vertex.id), - width: vertex.type === 'group' ? 500 : undefined, - type: vertex.type, - dir: vertex.dir, - props: vertex.props, - padding: getConfig().flowchart.padding, - }); - }); -}; - -/** - * Add edges to graph based on parsed graph definition - * - * @param {object} edges The edges to add to the graph - * @param {object} g The graph object - * @param diagObj - */ -export const addEdges = function (edges, g, diagObj) { - log.info('abc78 edges = ', edges); - let cnt = 0; - let linkIdCnt = {}; - - let defaultStyle; - let defaultLabelStyle; - - if (edges.defaultStyle !== undefined) { - const defaultStyles = getStylesFromArray(edges.defaultStyle); - defaultStyle = defaultStyles.style; - defaultLabelStyle = defaultStyles.labelStyle; - } - - edges.forEach(function (edge) { - cnt++; - - // Identify Link - var linkIdBase = 'L-' + edge.start + '-' + edge.end; - // count the links from+to the same node to give unique id - if (linkIdCnt[linkIdBase] === undefined) { - linkIdCnt[linkIdBase] = 0; - log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); - } else { - linkIdCnt[linkIdBase]++; - log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); - } - let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase]; - log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]); - var linkNameStart = 'LS-' + edge.start; - var linkNameEnd = 'LE-' + edge.end; - - const edgeData = { style: '', labelStyle: '' }; - edgeData.minlen = edge.length || 1; - //edgeData.id = 'id' + cnt; - - // Set link type for rendering - if (edge.type === 'arrow_open') { - edgeData.arrowhead = 'none'; - } else { - edgeData.arrowhead = 'normal'; - } - - // Check of arrow types, placed here in order not to break old rendering - edgeData.arrowTypeStart = 'arrow_open'; - edgeData.arrowTypeEnd = 'arrow_open'; - - /* eslint-disable no-fallthrough */ - switch (edge.type) { - case 'double_arrow_cross': - edgeData.arrowTypeStart = 'arrow_cross'; - case 'arrow_cross': - edgeData.arrowTypeEnd = 'arrow_cross'; - break; - case 'double_arrow_point': - edgeData.arrowTypeStart = 'arrow_point'; - case 'arrow_point': - edgeData.arrowTypeEnd = 'arrow_point'; - break; - case 'double_arrow_circle': - edgeData.arrowTypeStart = 'arrow_circle'; - case 'arrow_circle': - edgeData.arrowTypeEnd = 'arrow_circle'; - break; - } - - let style = ''; - let labelStyle = ''; - - switch (edge.stroke) { - case 'normal': - style = 'fill:none;'; - if (defaultStyle !== undefined) { - style = defaultStyle; - } - if (defaultLabelStyle !== undefined) { - labelStyle = defaultLabelStyle; - } - edgeData.thickness = 'normal'; - edgeData.pattern = 'solid'; - break; - case 'dotted': - edgeData.thickness = 'normal'; - edgeData.pattern = 'dotted'; - edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;'; - break; - case 'thick': - edgeData.thickness = 'thick'; - edgeData.pattern = 'solid'; - edgeData.style = 'stroke-width: 3.5px;fill:none;'; - break; - case 'invisible': - edgeData.thickness = 'invisible'; - edgeData.pattern = 'solid'; - edgeData.style = 'stroke-width: 0;fill:none;'; - break; - } - if (edge.style !== undefined) { - const styles = getStylesFromArray(edge.style); - style = styles.style; - labelStyle = styles.labelStyle; - } - - edgeData.style = edgeData.style += style; - edgeData.labelStyle = edgeData.labelStyle += labelStyle; - - if (edge.interpolate !== undefined) { - edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); - } else if (edges.defaultInterpolate !== undefined) { - edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear); - } else { - edgeData.curve = interpolateToCurve(conf.curve, curveLinear); - } - - if (edge.text === undefined) { - if (edge.style !== undefined) { - edgeData.arrowheadStyle = 'fill: #333'; - } - } else { - edgeData.arrowheadStyle = 'fill: #333'; - edgeData.labelpos = 'c'; - } - - edgeData.labelType = edge.labelType; - edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); - - if (edge.style === undefined) { - edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;'; - } - - edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:'); - - edgeData.id = linkId; - edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd; - - // Add the edge to the graph - g.setEdge(edge.start, edge.end, edgeData, cnt); - }); -}; /** * Returns the all the styles from classDef statements in the graph definition. @@ -386,15 +60,7 @@ export const draw = async function (text, id, _version, diagObj) { // Parse the graph definition diagObj.parser.parse(text); - // Fetch the default direction, use TD if none was found - let dir = diagObj.db.getDirection(); - if (dir === undefined) { - dir = 'TD'; - } - const { securityLevel, flowchart: conf } = getConfig(); - const nodeSpacing = conf.nodeSpacing || 50; - const rankSpacing = conf.rankSpacing || 50; // Handle root and document for when rendering in sandbox mode let sandboxElement; @@ -407,22 +73,7 @@ export const draw = async function (text, id, _version, diagObj) { : select('body'); const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; - // Create the input mermaid.graph - const g = new graphlib.Graph({ - multigraph: true, - compound: true, - }) - .setGraph({ - rankdir: dir, - nodesep: nodeSpacing, - ranksep: rankSpacing, - marginx: 0, - marginy: 0, - }) - .setDefaultEdgeLabel(function () { - return {}; - }); - + const g = setupGraphViewbox(diagObj); let subG; const subGraphs = diagObj.db.getSubGraphs(); log.info('Subgraphs - ', subGraphs); @@ -457,8 +108,7 @@ export const draw = async function (text, id, _version, diagObj) { g.setParent(subG.nodes[j], subG.id); } } - addVertices(vert, g, id, root, doc, diagObj); - addEdges(edges, g, diagObj); + setupGraph(diagObj, id, root, doc); // Add custom shapes // flowChartShapes.addToRenderV2(addShape); @@ -470,7 +120,7 @@ export const draw = async function (text, id, _version, diagObj) { const element = root.select('#' + id + ' g'); swimlaneLayout(g, conf); - swimlaneRender(element,g, conf); + swimlaneRender(element, g, conf); await render(element, g, ['point', 'circle', 'cross'], 'flowchart', id); utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());