diff --git a/packages/mermaid-layout-elk/src/find-common-ancestor.ts b/packages/mermaid-layout-elk/src/find-common-ancestor.ts index 793ab19181..2d513b66dc 100644 --- a/packages/mermaid-layout-elk/src/find-common-ancestor.ts +++ b/packages/mermaid-layout-elk/src/find-common-ancestor.ts @@ -3,8 +3,7 @@ export interface TreeData { childrenById: Record; } -export const findCommonAncestor = (id1: string, id2: string, treeData: TreeData) => { - const { parentById } = treeData; +export const findCommonAncestor = (id1: string, id2: string, { parentById }: TreeData) => { const visited = new Set(); let currentId = id1; @@ -20,6 +19,7 @@ export const findCommonAncestor = (id1: string, id2: string, treeData: TreeData) } currentId = parentById[currentId]; } + currentId = id2; while (currentId) { if (visited.has(currentId)) { @@ -27,5 +27,6 @@ export const findCommonAncestor = (id1: string, id2: string, treeData: TreeData) } currentId = parentById[currentId]; } + return 'root'; }; diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index c75a5c1a76..0941b2484d 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -1,8 +1,8 @@ // @ts-nocheck File not ready to check types import { curveLinear } from 'd3'; import ELK from 'elkjs/lib/elk.bundled.js'; -import mermaid from 'mermaid'; -import { findCommonAncestor } from './find-common-ancestor.js'; +import mermaid, { type LayoutData } from 'mermaid'; +import { type TreeData, findCommonAncestor } from './find-common-ancestor.js'; const { common, @@ -201,13 +201,13 @@ const getNextPort = (node, edgeDirection, graphDirection) => { return result; }; -const addSubGraphs = function (nodeArr) { - const parentLookupDb = { parentById: {}, childrenById: {} }; +const addSubGraphs = (nodeArr): TreeData => { + const parentLookupDb: TreeData = { parentById: {}, childrenById: {} }; const subgraphs = nodeArr.filter((node) => node.isGroup); log.info('Subgraphs - ', subgraphs); - subgraphs.forEach(function (subgraph) { + subgraphs.forEach((subgraph) => { const children = nodeArr.filter((node) => node.parentId === subgraph.id); - children.forEach(function (node) { + children.forEach((node) => { parentLookupDb.parentById[node.id] = subgraph.id; if (parentLookupDb.childrenById[subgraph.id] === undefined) { parentLookupDb.childrenById[subgraph.id] = []; @@ -252,7 +252,7 @@ const getEdgeStartEndPoint = (edge, dir) => { return { source, target, sourceId, targetId }; }; -const calcOffset = function (src, dest, parentLookupDb) { +const calcOffset = function (src: string, dest: string, parentLookupDb: TreeData) { const ancestor = findCommonAncestor(src, dest, parentLookupDb); if (ancestor === undefined || ancestor === 'root') { return { x: 0, y: 0 }; @@ -351,11 +351,6 @@ export const addEdges = async function (dataForLayout, graph, svg) { edgeData.style = 'stroke-width: 3.5px;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; @@ -455,7 +450,7 @@ function setIncludeChildrenPolicy(nodeId: string, ancestorId: string) { } } -export const render = async (data4Layout, svg, element, algorithm) => { +export const render = async (data4Layout: LayoutData, svg, element, algorithm) => { const elk = new ELK(); // Add the arrowheads to the svg @@ -558,9 +553,7 @@ export const render = async (data4Layout, svg, element, algorithm) => { } }); - // log.info('before layout', JSON.stringify(elkGraph, null, 2)); const g = await elk.layout(elkGraph); - // log.info('after layout', JSON.stringify(g)); // debugger; drawNodes(0, 0, g.children, svg, subGraphsEl, 0); @@ -688,6 +681,32 @@ export const render = async (data4Layout, svg, element, algorithm) => { edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2; positionEdgeLabel(edge, paths); } + const src = edge.sections[0].startPoint; + const dest = edge.sections[0].endPoint; + const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : []; + + const segPoints = segments.map((segment) => { + return { x: segment.x + offset.x, y: segment.y + offset.y }; + }); + edge.points = [ + { x: src.x + offset.x, y: src.y + offset.y }, + ...segPoints, + { x: dest.x + offset.x, y: dest.y + offset.y }, + ]; + const paths = insertEdge( + edgesEl, + edge, + clusterDb, + data4Layout.type, + startNode, + endNode, + data4Layout.diagramId + ); + log.info('APA12 edge points after insert', JSON.stringify(edge.points)); + + edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2; + edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2; + positionEdgeLabel(edge, paths); }); }; @@ -696,10 +715,10 @@ function intersectLine(p1, p2, q1, q2) { // Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994, // p7 and p473. - var a1, a2, b1, b2, c1, c2; - var r1, r2, r3, r4; - var denom, offset, num; - var x, y; + let a1, a2, b1, b2, c1, c2; + let r1, r2, r3, r4; + let denom, offset, num; + let x, y; // Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x + // b1 y + c1 = 0. @@ -761,8 +780,8 @@ function sameSign(r1, r2) { return r1 * r2 > 0; } const diamondIntersection = (bounds, outsidePoint, insidePoint) => { - var x1 = bounds.x; - var y1 = bounds.y; + const x1 = bounds.x; + const y1 = bounds.y; const w = bounds.width; //+ bounds.padding; const h = bounds.height; // + bounds.padding; @@ -782,10 +801,10 @@ const diamondIntersection = (bounds, outsidePoint, insidePoint) => { polyPoints ); - var intersections = []; + const intersections = []; - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; + let minX = Number.POSITIVE_INFINITY; + let minY = Number.POSITIVE_INFINITY; if (typeof polyPoints.forEach === 'function') { polyPoints.forEach(function (entry) { minX = Math.min(minX, entry.x); @@ -796,13 +815,18 @@ const diamondIntersection = (bounds, outsidePoint, insidePoint) => { minY = Math.min(minY, polyPoints.y); } - var left = x1 - w / 2; - var top = y1 + h / 2; - - for (var i = 0; i < polyPoints.length; i++) { - var p1 = polyPoints[i]; - var p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0]; - var intersect = intersectLine(bounds, outsidePoint, { x: p1.x, y: p1.y }, { x: p2.x, y: p2.y }); + const left = x1 - w / 2; + const top = y1 + h / 2; + + for (let i = 0; i < polyPoints.length; i++) { + const p1 = polyPoints[i]; + const p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0]; + const intersect = intersectLine( + bounds, + outsidePoint, + { x: p1.x, y: p1.y }, + { x: p2.x, y: p2.y } + ); if (intersect) { intersections.push(intersect); @@ -818,13 +842,13 @@ const diamondIntersection = (bounds, outsidePoint, insidePoint) => { if (intersections.length > 1) { // More intersections, find the one nearest to edge end point intersections.sort(function (p, q) { - var pdx = p.x - outsidePoint.x; - var pdy = p.y - outsidePoint.y; - var distp = Math.sqrt(pdx * pdx + pdy * pdy); + const pdx = p.x - outsidePoint.x; + const pdy = p.y - outsidePoint.y; + const distp = Math.sqrt(pdx * pdx + pdy * pdy); - var qdx = q.x - outsidePoint.x; - var qdy = q.y - outsidePoint.y; - var distq = Math.sqrt(qdx * qdx + qdy * qdy); + const qdx = q.x - outsidePoint.x; + const qdy = q.y - outsidePoint.y; + const distq = Math.sqrt(qdx * qdx + qdy * qdy); return distp < distq ? -1 : distp === distq ? 0 : 1; }); @@ -852,7 +876,7 @@ export const intersection = (node, outsidePoint, insidePoint) => { if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) { // Intersection is top or bottom of rect. - let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y; + const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y; r = (R * q) / Q; const res = { x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r, @@ -881,7 +905,7 @@ export const intersection = (node, outsidePoint, insidePoint) => { // r = outsidePoint.x - w - x; r = x - w - outsidePoint.x; } - let q = (Q * r) / R; + const q = (Q * r) / R; // OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w; // OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r; let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r; @@ -925,7 +949,7 @@ const outsideNode = (node, point) => { */ const cutPathAtIntersect = (_points, bounds, isDiamond: boolean) => { console.log('UIO cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond); - let points = []; + const points = []; let lastPointOutside = _points[0]; let isInside = false; _points.forEach((point) => { @@ -939,7 +963,7 @@ const cutPathAtIntersect = (_points, bounds, isDiamond: boolean) => { let inter; if (isDiamond) { - let inter2 = diamondIntersection(bounds, lastPointOutside, point); + const inter2 = diamondIntersection(bounds, lastPointOutside, point); const distance = Math.sqrt( (lastPointOutside.x - inter2.x) ** 2 + (lastPointOutside.y - inter2.y) ** 2 ); diff --git a/packages/mermaid/src/dagre-wrapper/shapes/util.js b/packages/mermaid/src/dagre-wrapper/shapes/util.js index 234fe2a836..1d0d2d77e6 100644 --- a/packages/mermaid/src/dagre-wrapper/shapes/util.js +++ b/packages/mermaid/src/dagre-wrapper/shapes/util.js @@ -15,8 +15,6 @@ export const labelHelper = async (parent, node, _classes, isNode) => { classes = _classes; } - // console.log('parentY', parent.node()); - // Add outer g element const shapeSvg = parent .insert('g') @@ -35,7 +33,6 @@ export const labelHelper = async (parent, node, _classes, isNode) => { } const textNode = label.node(); - // console.log('parentX', parent, 'node',node,'labelText',labelText, textNode, node.labelType, 'label', label.node()); let text; if (node.labelType === 'markdown') { // text = textNode; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/detector.ts b/packages/mermaid/src/diagrams/flowchart/elk/detector.ts index 11c5b907e7..b476ff11ba 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/detector.ts +++ b/packages/mermaid/src/diagrams/flowchart/elk/detector.ts @@ -26,7 +26,7 @@ const detector: DiagramDetector = (txt, config): boolean => { } return false; }; -// @ts-ignore - TODO: Fix after refactor + const loader: DiagramLoader = async () => { const { diagram } = await import('../flowDiagram-v2.js'); return { id, diagram }; diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts index 67fc76d026..acd0353da8 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts @@ -729,14 +729,12 @@ export const destructLink = (_str: string, _startStr: string) => { // Todo optimizer this by caching existing nodes const exists = (allSgs: FlowSubGraph[], _id: string) => { - let res = false; - allSgs.forEach((sg) => { - const pos = sg.nodes.indexOf(_id); - if (pos >= 0) { - res = true; + for (const sg of allSgs) { + if (sg.nodes.indexOf(_id) >= 0) { + return true; } - }); - return res; + } + return false; }; /** * Deletes an id from all subgraphs @@ -787,6 +785,7 @@ const destructEdgeType = (type: string | undefined) => { } return { arrowTypeStart, arrowTypeEnd }; }; + const addNodeFromVertex = ( vertex: FlowVertex, nodes: Node[], @@ -799,7 +798,11 @@ const addNodeFromVertex = ( const isGroup = subGraphDB.get(vertex.id) ?? false; const node = findNode(nodes, vertex.id); - if (!node) { + if (node) { + node.cssStyles = vertex.styles; + node.cssCompiledStyles = getCompiledStyles(vertex.classes); + node.cssClasses = vertex.classes.join(' '); + } else { nodes.push({ id: vertex.id, label: vertex.text, @@ -818,10 +821,6 @@ const addNodeFromVertex = ( linkTarget: vertex.linkTarget, tooltip: getTooltip(vertex.id), }); - } else { - node.cssStyles = vertex.styles; - node.cssCompiledStyles = getCompiledStyles(vertex.classes); - node.cssClasses = vertex.classes.join(' '); } }; @@ -829,14 +828,11 @@ function getCompiledStyles(classDefs: string[]) { let compiledStyles: string[] = []; for (const customClass of classDefs) { const cssClass = classes.get(customClass); - // log.debug('IPI cssClass in flowDb', cssClass); - if (cssClass) { - if (cssClass.styles) { - compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])].map((s) => s.trim()); - } - if (cssClass.textStyles) { - compiledStyles = [...compiledStyles, ...(cssClass.textStyles ?? [])].map((s) => s.trim()); - } + if (cssClass?.styles) { + compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])].map((s) => s.trim()); + } + if (cssClass?.textStyles) { + compiledStyles = [...compiledStyles, ...(cssClass.textStyles ?? [])].map((s) => s.trim()); } } return compiledStyles; @@ -857,9 +853,9 @@ export const getData = () => { if (subGraph.nodes.length > 0) { subGraphDB.set(subGraph.id, true); } - subGraph.nodes.forEach((id) => { + for (const id of subGraph.nodes) { parentDB.set(id, subGraph.id); - }); + } } // Data is setup, add the nodes @@ -917,8 +913,6 @@ export const getData = () => { edges.push(edge); }); - // log.debug('IPI nodes', JSON.stringify(nodes, null, 2)); - return { nodes, edges, other: {}, config }; }; diff --git a/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts b/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts index fa4decf6b4..dda5a67f0d 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts @@ -18,7 +18,6 @@ const detector: DiagramDetector = (txt, config) => { return /^\s*flowchart/.test(txt); }; -// @ts-ignore - TODO: Fix after refactor const loader: DiagramLoader = async () => { const { diagram } = await import('./flowDiagram-v2.js'); return { id, diagram }; diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts b/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts index bf57687f12..85867e3ab4 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts @@ -9,7 +9,6 @@ import { setConfig } from '../../diagram-api/diagramAPI.js'; export const diagram = { parser: flowParser, db: flowDb, - // renderer: flowRendererV2, renderer: flowRendererV3, styles: flowStyles, init: (cnf: MermaidConfig) => { diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram-v3-unified.ts b/packages/mermaid/src/diagrams/flowchart/flowDiagram-v3-unified.ts deleted file mode 100644 index 368a98ccae..0000000000 --- a/packages/mermaid/src/diagrams/flowchart/flowDiagram-v3-unified.ts +++ /dev/null @@ -1,25 +0,0 @@ -// @ts-ignore: JISON doesn't support types -import flowParser from './parser/flow.jison'; -import flowDb from './flowDb.js'; -import flowRendererV2 from './flowRenderer-v2.js'; -import flowStyles from './styles.js'; -import type { MermaidConfig } from '../../config.type.js'; -import { setConfig } from '../../diagram-api/diagramAPI.js'; - -export const diagram = { - parser: flowParser, - db: flowDb, - renderer: flowRendererV2, - styles: flowStyles, - init: (cnf: MermaidConfig) => { - if (!cnf.flowchart) { - cnf.flowchart = {}; - } - cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - // flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf - setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } }); - flowRendererV2.setConf(cnf.flowchart); - flowDb.clear(); - flowDb.setGen('gen-2'); - }, -}; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index a0cb74c6af..148ff2f869 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -29,7 +29,6 @@ export const setConf = function (cnf) { */ export const addVertices = async function (vert, g, svgId, root, doc, diagObj) { const svg = root.select(`[id="${svgId}"]`); - // console.log('SVG:', svg, svg.node(), 'root:', root, root.node()); const keys = vert.keys(); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts index 812053936d..396c53abe2 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts @@ -1,30 +1,18 @@ -import { log } from '../../logger.js'; -import type { DiagramStyleClassDef } from '../../diagram-api/types.js'; -import type { LayoutData } from '../../rendering-util/types.js'; +import { select } from 'd3'; import { getConfig } from '../../diagram-api/diagramAPI.js'; -import { render } from '../../rendering-util/render.js'; +import type { DiagramStyleClassDef } from '../../diagram-api/types.js'; +import { log } from '../../logger.js'; import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js'; +import { render } from '../../rendering-util/render.js'; import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js'; -import { getDirection } from './flowDb.js'; - +import type { LayoutData } from '../../rendering-util/types.js'; import utils from '../../utils.js'; -import { select } from 'd3'; - -// Configuration -const conf: Record = {}; - -export const setConf = function (cnf: Record) { - const keys = Object.keys(cnf); - for (const key of keys) { - conf[key] = cnf[key]; - } -}; +import { getDirection } from './flowDb.js'; export const getClasses = function ( text: string, diagramObj: any -): Record { - // diagramObj.db.extract(diagramObj.db.getRootDocV2()); +): Map { return diagramObj.db.getClasses(); }; @@ -42,8 +30,6 @@ export const draw = async function (text: string, id: string, _version: string, // @ts-ignore - document is always available const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; - const DIR = getDirection(); - // The getData method provided in all supported diagrams is used to extract the data from the parsed structure // into the Layout data format log.debug('Before getData: '); @@ -51,10 +37,11 @@ export const draw = async function (text: string, id: string, _version: string, log.debug('Data: ', data4Layout); // Create the root SVG - the element is the div containing the SVG element const { element, svg } = getDiagramElements(id, securityLevel); + const direction = getDirection(); data4Layout.type = diag.type; data4Layout.layoutAlgorithm = layout; - data4Layout.direction = DIR; + data4Layout.direction = direction; data4Layout.nodeSpacing = conf?.nodeSpacing || 50; data4Layout.rankSpacing = conf?.rankSpacing || 50; data4Layout.markers = ['point', 'circle', 'cross']; @@ -72,43 +59,41 @@ export const draw = async function (text: string, id: string, _version: string, setupViewPortForSVG(svg, padding, 'flowchart', conf?.useMaxWidth || false); // If node has a link, wrap it in an anchor SVG object. - data4Layout.nodes.forEach((vertex) => { - if (vertex.link) { - const node = select('#' + id + ' [id="' + vertex.id + '"]'); - if (node) { - const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a'); - link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.cssClasses); - link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); - if (securityLevel === 'sandbox') { - link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top'); - } else if (vertex.linkTarget) { - link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget); - } + for (const vertex of data4Layout.nodes) { + const node = select(`#${id} [id="${vertex.id}"]`); + if (!node || !vertex.link) { + continue; + } + const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a'); + link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.cssClasses); + link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); + if (securityLevel === 'sandbox') { + link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top'); + } else if (vertex.linkTarget) { + link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget); + } - const linkNode = node.insert(function () { - return link; - }, ':first-child'); + const linkNode = node.insert(function () { + return link; + }, ':first-child'); - const shape = node.select('.label-container'); - if (shape) { - linkNode.append(function () { - return shape.node(); - }); - } + const shape = node.select('.label-container'); + if (shape) { + linkNode.append(function () { + return shape.node(); + }); + } - const label = node.select('.label'); - if (label) { - linkNode.append(function () { - return label.node(); - }); - } - } + const label = node.select('.label'); + if (label) { + linkNode.append(function () { + return label.node(); + }); } - }); + } }; export default { - setConf, getClasses, draw, }; diff --git a/packages/mermaid/src/diagrams/state/dataFetcher.js b/packages/mermaid/src/diagrams/state/dataFetcher.js index f8280fd5c4..760bf9ef8b 100644 --- a/packages/mermaid/src/diagrams/state/dataFetcher.js +++ b/packages/mermaid/src/diagrams/state/dataFetcher.js @@ -35,7 +35,7 @@ import { } from './stateCommon.js'; // List of nodes created from the parsed diagram statement items -let nodeDb = {}; +let nodeDb = new Map(); let graphItemCount = 0; // used to construct ids, etc. @@ -104,7 +104,6 @@ const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, l look, }; edges.push(edgeData); - //g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount); graphItemCount++; } break; @@ -214,6 +213,7 @@ function getStylesFromDbInfo(dbInfoItem) { } } } + export const dataFetcher = ( parent, parsedItem, @@ -244,17 +244,17 @@ export const dataFetcher = ( } // Add the node to our list (nodeDb) - if (!nodeDb[itemId]) { - nodeDb[itemId] = { + if (!nodeDb.get(itemId)) { + nodeDb.set(itemId, { id: itemId, shape, description: common.sanitizeText(itemId, getConfig()), cssClasses: `${classStr} ${CSS_DIAGRAM_STATE}`, cssStyles: style, - }; + }); } - const newNode = nodeDb[itemId]; + const newNode = nodeDb.get(itemId); // Save data for description and group so that for instance a statement without description overwrites // one with description @todo TODO What does this mean? If important, add a test for it @@ -290,7 +290,6 @@ export const dataFetcher = ( } else { newNode.shape = SHAPE_STATE; } - //newNode.shape = SHAPE_STATE; } // group @@ -300,12 +299,7 @@ export const dataFetcher = ( newNode.isGroup = true; newNode.dir = getDir(parsedItem); newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP; - newNode.cssClasses = - newNode.cssClasses + - ' ' + - CSS_DIAGRAM_CLUSTER + - ' ' + - (altFlag ? CSS_DIAGRAM_CLUSTER_ALT : ''); + newNode.cssClasses = `${newNode.cssClasses} ${CSS_DIAGRAM_CLUSTER} ${altFlag ? CSS_DIAGRAM_CLUSTER_ALT : ''}`; } // This is what will be added to the graph @@ -421,6 +415,6 @@ export const dataFetcher = ( }; export const reset = () => { - nodeDb = {}; + nodeDb.clear(); graphItemCount = 0; }; diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v2.ts b/packages/mermaid/src/diagrams/state/stateDiagram-v2.ts index 9d0a82a871..a27fc18791 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram-v2.ts +++ b/packages/mermaid/src/diagrams/state/stateDiagram-v2.ts @@ -3,7 +3,6 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; import parser from './parser/stateDiagram.jison'; import db from './stateDb.js'; import styles from './styles.js'; -// import renderer from './stateRenderer-v2.js'; import renderer from './stateRenderer-v3-unified.js'; export const diagram: DiagramDefinition = { diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v3-unified.ts b/packages/mermaid/src/diagrams/state/stateDiagram-v3-unified.ts deleted file mode 100644 index a27fc18791..0000000000 --- a/packages/mermaid/src/diagrams/state/stateDiagram-v3-unified.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { DiagramDefinition } from '../../diagram-api/types.js'; -// @ts-ignore: JISON doesn't support types -import parser from './parser/stateDiagram.jison'; -import db from './stateDb.js'; -import styles from './styles.js'; -import renderer from './stateRenderer-v3-unified.js'; - -export const diagram: DiagramDefinition = { - parser, - db, - renderer, - styles, - init: (cnf) => { - if (!cnf.state) { - cnf.state = {}; - } - cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - db.clear(); - }, -}; diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js deleted file mode 100644 index acb10427b0..0000000000 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ /dev/null @@ -1,479 +0,0 @@ -import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; -import { select } from 'd3'; -import { getConfig } from '../../diagram-api/diagramAPI.js'; -import { render } from '../../dagre-wrapper/index.js'; -import { log } from '../../logger.js'; -import { configureSvgSize } from '../../setupGraphViewbox.js'; -import common from '../common/common.js'; -import utils, { getEdgeId } from '../../utils.js'; - -import { - DEFAULT_DIAGRAM_DIRECTION, - DEFAULT_NESTED_DOC_DIR, - STMT_STATE, - STMT_RELATION, - DEFAULT_STATE_TYPE, - DIVIDER_TYPE, -} from './stateCommon.js'; - -// -------------------------------------- -// Shapes -const SHAPE_STATE = 'rect'; -const SHAPE_STATE_WITH_DESC = 'rectWithTitle'; -const SHAPE_START = 'start'; -const SHAPE_END = 'end'; -const SHAPE_DIVIDER = 'divider'; -const SHAPE_GROUP = 'roundedWithTitle'; -const SHAPE_NOTE = 'note'; -const SHAPE_NOTEGROUP = 'noteGroup'; - -// -------------------------------------- -// CSS classes -const CSS_DIAGRAM = 'statediagram'; -const CSS_STATE = 'state'; -const CSS_DIAGRAM_STATE = `${CSS_DIAGRAM}-${CSS_STATE}`; -const CSS_EDGE = 'transition'; -const CSS_NOTE = 'note'; -const CSS_NOTE_EDGE = 'note-edge'; -const CSS_EDGE_NOTE_EDGE = `${CSS_EDGE} ${CSS_NOTE_EDGE}`; -const CSS_DIAGRAM_NOTE = `${CSS_DIAGRAM}-${CSS_NOTE}`; -const CSS_CLUSTER = 'cluster'; -const CSS_DIAGRAM_CLUSTER = `${CSS_DIAGRAM}-${CSS_CLUSTER}`; -const CSS_CLUSTER_ALT = 'cluster-alt'; -const CSS_DIAGRAM_CLUSTER_ALT = `${CSS_DIAGRAM}-${CSS_CLUSTER_ALT}`; - -// -------------------------------------- -// DOM and element IDs -const PARENT = 'parent'; -const NOTE = 'note'; -const DOMID_STATE = 'state'; -const DOMID_TYPE_SPACER = '----'; -const NOTE_ID = `${DOMID_TYPE_SPACER}${NOTE}`; -const PARENT_ID = `${DOMID_TYPE_SPACER}${PARENT}`; -// -------------------------------------- -// Graph edge settings -const G_EDGE_STYLE = 'fill:none'; -const G_EDGE_ARROWHEADSTYLE = 'fill: #333'; -const G_EDGE_LABELPOS = 'c'; -const G_EDGE_LABELTYPE = 'text'; -const G_EDGE_THICKNESS = 'normal'; - -// -------------------------------------- -// List of nodes created from the parsed diagram statement items -let nodeDb = {}; - -let graphItemCount = 0; // used to construct ids, etc. - -// Configuration -const conf = {}; - -// ----------------------------------------------------------------------- - -export const setConf = function (cnf) { - const keys = Object.keys(cnf); - for (const key of keys) { - conf[key] = cnf[key]; - } -}; - -/** - * Returns the all the classdef styles (a.k.a. classes) from classDef statements in the graph definition. - * - * @param {string} text - the diagram text to be parsed - * @param diagramObj - * @returns {Map} ClassDef styles (a Map with keys = strings, values = ) - */ -export const getClasses = function (text, diagramObj) { - diagramObj.db.extract(diagramObj.db.getRootDocV2()); - return diagramObj.db.getClasses(); -}; - -/** - * Get classes from the db for the info item. - * If there aren't any or if dbInfoItem isn't defined, return an empty string. - * Else create 1 string from the list of classes found - * - * @param {undefined | null | object} dbInfoItem - * @returns {string} - */ -function getClassesFromDbInfo(dbInfoItem) { - if (dbInfoItem === undefined || dbInfoItem === null) { - return ''; - } else { - if (dbInfoItem.classes) { - return dbInfoItem.classes.join(' '); - } else { - return ''; - } - } -} - -/** - * Create a standard string for the dom ID of an item. - * If a type is given, insert that before the counter, preceded by the type spacer - * - * @param itemId - * @param counter - * @param {string | null} type - * @param typeSpacer - * @returns {string} - */ -export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) { - const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : ''; - return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`; -} - -/** - * Create a graph node based on the statement information - * - * @param g - graph - * @param {object} parent - * @param {object} parsedItem - parsed statement item - * @param {Map} diagramStates - the list of all known states for the diagram - * @param {object} diagramDb - * @param {boolean} altFlag - for clusters, add the "statediagram-cluster-alt" CSS class - */ -const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) => { - const itemId = parsedItem.id; - const classStr = getClassesFromDbInfo(diagramStates.get(itemId)); - - if (itemId !== 'root') { - let shape = SHAPE_STATE; - if (parsedItem.start === true) { - shape = SHAPE_START; - } - if (parsedItem.start === false) { - shape = SHAPE_END; - } - if (parsedItem.type !== DEFAULT_STATE_TYPE) { - shape = parsedItem.type; - } - - // Add the node to our list (nodeDb) - if (!nodeDb[itemId]) { - nodeDb[itemId] = { - id: itemId, - shape, - description: common.sanitizeText(itemId, getConfig()), - classes: `${classStr} ${CSS_DIAGRAM_STATE}`, - }; - } - - const newNode = nodeDb[itemId]; - - // Save data for description and group so that for instance a statement without description overwrites - // one with description @todo TODO What does this mean? If important, add a test for it - - // Build of the array of description strings - if (parsedItem.description) { - if (Array.isArray(newNode.description)) { - // There already is an array of strings,add to it - newNode.shape = SHAPE_STATE_WITH_DESC; - newNode.description.push(parsedItem.description); - } else { - if (newNode.description.length > 0) { - // if there is a description already transform it to an array - newNode.shape = SHAPE_STATE_WITH_DESC; - if (newNode.description === itemId) { - // If the previous description was this, remove it - newNode.description = [parsedItem.description]; - } else { - newNode.description = [newNode.description, parsedItem.description]; - } - } else { - newNode.shape = SHAPE_STATE; - newNode.description = parsedItem.description; - } - } - newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig()); - } - - // If there's only 1 description entry, just use a regular state shape - if (newNode.description.length === 1 && newNode.shape === SHAPE_STATE_WITH_DESC) { - newNode.shape = SHAPE_STATE; - } - - // group - if (!newNode.type && parsedItem.doc) { - log.info('Setting cluster for ', itemId, getDir(parsedItem)); - newNode.type = 'group'; - newNode.dir = getDir(parsedItem); - newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP; - newNode.classes = - newNode.classes + - ' ' + - CSS_DIAGRAM_CLUSTER + - ' ' + - (altFlag ? CSS_DIAGRAM_CLUSTER_ALT : ''); - } - - // This is what will be added to the graph - const nodeData = { - labelStyle: '', - shape: newNode.shape, - labelText: newNode.description, - // typeof newNode.description === 'object' - // ? newNode.description[0] - // : newNode.description, - classes: newNode.classes, - style: '', //styles.style, - id: itemId, - dir: newNode.dir, - domId: stateDomId(itemId, graphItemCount), - type: newNode.type, - padding: 15, //getConfig().flowchart.padding - }; - // if (useHtmlLabels) { - nodeData.centerLabel = true; - // } - - if (parsedItem.note) { - // Todo: set random id - const noteData = { - labelStyle: '', - shape: SHAPE_NOTE, - labelText: parsedItem.note.text, - classes: CSS_DIAGRAM_NOTE, - // useHtmlLabels: false, - style: '', // styles.style, - id: itemId + NOTE_ID + '-' + graphItemCount, - domId: stateDomId(itemId, graphItemCount, NOTE), - type: newNode.type, - padding: 15, //getConfig().flowchart.padding - }; - const groupData = { - labelStyle: '', - shape: SHAPE_NOTEGROUP, - labelText: parsedItem.note.text, - classes: newNode.classes, - style: '', // styles.style, - id: itemId + PARENT_ID, - domId: stateDomId(itemId, graphItemCount, PARENT), - type: 'group', - padding: 0, //getConfig().flowchart.padding - }; - - const parentNodeId = itemId + PARENT_ID; - g.setNode(parentNodeId, groupData); - - g.setNode(noteData.id, noteData); - g.setNode(itemId, nodeData); - - g.setParent(itemId, parentNodeId); - g.setParent(noteData.id, parentNodeId); - - let from = itemId; - let to = noteData.id; - - if (parsedItem.note.position === 'left of') { - from = noteData.id; - to = itemId; - } - - g.setEdge(from, to, { - arrowhead: 'none', - arrowType: '', - style: G_EDGE_STYLE, - labelStyle: '', - id: getEdgeId(from, to, { - counter: graphItemCount, - }), - classes: CSS_EDGE_NOTE_EDGE, - arrowheadStyle: G_EDGE_ARROWHEADSTYLE, - labelpos: G_EDGE_LABELPOS, - labelType: G_EDGE_LABELTYPE, - thickness: G_EDGE_THICKNESS, - }); - - graphItemCount++; - } else { - g.setNode(itemId, nodeData); - } - } - - if (parent && parent.id !== 'root') { - log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id); - g.setParent(itemId, parent.id); - } - if (parsedItem.doc) { - log.trace('Adding nodes children '); - setupDoc(g, parsedItem, parsedItem.doc, diagramStates, diagramDb, !altFlag); - } -}; - -/** - * Turn parsed statements (item.stmt) into nodes, relationships, etc. for a document. - * (A document may be nested within others.) - * - * @param g - * @param parentParsedItem - parsed Item that is the parent of this document (doc) - * @param doc - the document to set up; it is a list of parsed statements - * @param {Map} diagramStates - the list of all known states for the diagram - * @param diagramDb - * @param {boolean} altFlag - * @todo This duplicates some of what is done in stateDb.js extract method - */ -const setupDoc = (g, parentParsedItem, doc, diagramStates, diagramDb, altFlag) => { - // graphItemCount = 0; - log.trace('items', doc); - doc.forEach((item) => { - switch (item.stmt) { - case STMT_STATE: - setupNode(g, parentParsedItem, item, diagramStates, diagramDb, altFlag); - break; - case DEFAULT_STATE_TYPE: - setupNode(g, parentParsedItem, item, diagramStates, diagramDb, altFlag); - break; - case STMT_RELATION: - { - setupNode(g, parentParsedItem, item.state1, diagramStates, diagramDb, altFlag); - setupNode(g, parentParsedItem, item.state2, diagramStates, diagramDb, altFlag); - const edgeData = { - id: getEdgeId(item.state1.id, item.state2.id, { - counter: graphItemCount, - }), - arrowhead: 'normal', - arrowTypeEnd: 'arrow_barb', - style: G_EDGE_STYLE, - labelStyle: '', - label: common.sanitizeText(item.description, getConfig()), - arrowheadStyle: G_EDGE_ARROWHEADSTYLE, - labelpos: G_EDGE_LABELPOS, - labelType: G_EDGE_LABELTYPE, - thickness: G_EDGE_THICKNESS, - classes: CSS_EDGE, - }; - g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount); - graphItemCount++; - } - break; - } - }); -}; - -/** - * Get the direction from the statement items. - * Look through all of the documents (docs) in the parsedItems - * Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction. - * @param {object[]} parsedItem - the parsed statement item to look through - * @param [defaultDir] - the direction to use if none is found - * @returns {string} - */ -const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => { - let dir = defaultDir; - if (parsedItem.doc) { - for (const parsedItemDoc of parsedItem.doc) { - if (parsedItemDoc.stmt === 'dir') { - dir = parsedItemDoc.value; - } - } - } - return dir; -}; - -/** - * Draws a state diagram in the tag with id: id based on the graph definition in text. - * - * @param {any} text - * @param {any} id - * @param _version - * @param diag - */ -export const draw = async function (text, id, _version, diag) { - log.info('Drawing state diagram (v2)', id); - nodeDb = {}; - // Fetch the default direction, use TD if none was found - let dir = diag.db.getDirection(); - if (dir === undefined) { - dir = DEFAULT_DIAGRAM_DIRECTION; - } - - const { securityLevel, state: conf } = getConfig(); - const nodeSpacing = conf.nodeSpacing || 50; - const rankSpacing = conf.rankSpacing || 50; - - log.info(diag.db.getRootDocV2()); - - // This parses the diagram text and sets the classes, relations, styles, classDefs, etc. - diag.db.extract(diag.db.getRootDocV2()); - log.info(diag.db.getRootDocV2()); - - const diagramStates = diag.db.getStates(); - - // Create the input mermaid.graph - const g = new graphlib.Graph({ - multigraph: true, - compound: true, - }) - .setGraph({ - rankdir: getDir(diag.db.getRootDocV2()), - nodesep: nodeSpacing, - ranksep: rankSpacing, - marginx: 8, - marginy: 8, - }) - .setDefaultEdgeLabel(function () { - return {}; - }); - - setupNode(g, undefined, diag.db.getRootDocV2(), diagramStates, diag.db, true); - - // Set up an SVG group so that we can translate the final graph. - let sandboxElement; - if (securityLevel === 'sandbox') { - sandboxElement = select('#i' + id); - } - const root = - securityLevel === 'sandbox' - ? select(sandboxElement.nodes()[0].contentDocument.body) - : select('body'); - const svg = root.select(`[id="${id}"]`); - - // Run the renderer. This is what draws the final graph. - - const element = root.select('#' + id + ' g'); - await render(element, g, ['barb'], CSS_DIAGRAM, id); - - const padding = 8; - - utils.insertTitle(svg, 'statediagramTitleText', conf.titleTopMargin, diag.db.getDiagramTitle()); - - const bounds = svg.node().getBBox(); - const width = bounds.width + padding * 2; - const height = bounds.height + padding * 2; - - // Zoom in a bit - svg.attr('class', CSS_DIAGRAM); - - const svgBounds = svg.node().getBBox(); - - configureSvgSize(svg, height, width, conf.useMaxWidth); - - // Ensure the viewBox includes the whole svgBounds area with extra space for padding - const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; - log.debug(`viewBox ${vBox}`); - svg.attr('viewBox', vBox); - - // Add label rects for non html labels - // if (!evaluate(conf.htmlLabels) || true) { - const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); - for (const label of labels) { - // Get dimensions of label - const dim = label.getBBox(); - - const rect = document.createElementNS('http://www.w3.org/2000/svg', SHAPE_STATE); - rect.setAttribute('rx', 0); - rect.setAttribute('ry', 0); - rect.setAttribute('width', dim.width); - rect.setAttribute('height', dim.height); - - label.insertBefore(rect, label.firstChild); - // } - } -}; - -export default { - setConf, - getClasses, - draw, -}; diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.spec.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.spec.js deleted file mode 100644 index a190fe05be..0000000000 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import { expectTypeOf } from 'vitest'; - -import { parser } from './parser/stateDiagram.jison'; -import stateDb from './stateDb.js'; -import stateRendererV2 from './stateRenderer-v2.js'; - -// Can use this instead of having to register diagrams and load/orchestrate them, etc. -class FauxDiagramObj { - db = stateDb; - parser = parser; - renderer = stateRendererV2; - - constructor(options = { db: stateDb, parser: parser, renderer: stateRendererV2 }) { - this.db = options.db; - this.parser = options.parser; - this.renderer = options.renderer; - this.parser.yy = this.db; - } -} - -describe('stateRenderer-v2', () => { - describe('getClasses', () => { - const diagramText = 'statediagram-v2\n'; - const fauxStateDiagram = new FauxDiagramObj(); - - it('returns a {}', () => { - const result = stateRendererV2.getClasses(diagramText, fauxStateDiagram); - expectTypeOf(result).toBeObject(); - }); - }); -}); diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts b/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts index 9ad6666557..b257533cbd 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts @@ -16,15 +16,19 @@ import { CSS_DIAGRAM, DEFAULT_NESTED_DOC_DIR } from './stateCommon.js'; * @param defaultDir - the direction to use if none is found * @returns The direction to use */ -export const getDir = (parsedItem: any, defaultDir = DEFAULT_NESTED_DOC_DIR) => { +const getDir = (parsedItem: any, defaultDir = DEFAULT_NESTED_DOC_DIR) => { + if (!parsedItem.doc) { + return defaultDir; + } + let dir = defaultDir; - if (parsedItem.doc) { - for (const parsedItemDoc of parsedItem.doc) { - if (parsedItemDoc.stmt === 'dir') { - dir = parsedItemDoc.value; - } + + for (const parsedItemDoc of parsedItem.doc) { + if (parsedItemDoc.stmt === 'dir') { + dir = parsedItemDoc.value; } } + return dir; }; @@ -53,17 +57,6 @@ export const draw = async function (text: string, id: string, _version: string, // Create the root SVG - the element is the div containing the SVG element const { element, svg } = getDiagramElements(id, securityLevel); - // // For some diagrams this call is not needed, but in the state diagram it is - // await insertElementsForSize(element, data4Layout); - - // console.log('data4Layout:', data4Layout); - - // // Now we have layout data with real sizes, we can perform the layout - // const data4Rendering = doLayout(data4Layout, id, _version, 'dagre-wrapper'); - - // // The performRender method provided in all supported diagrams is used to render the data - // performRender(data4Rendering); - data4Layout.type = diag.type; data4Layout.layoutAlgorithm = layout; diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index de5eb90368..aa07375927 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -19,6 +19,7 @@ import { addDiagrams } from './diagram-api/diagram-orchestration.js'; import { registerLayoutLoaders } from './rendering-util/render.js'; import type { LayoutLoaderDefinition } from './rendering-util/render.js'; import { internalHelpers } from './internals.js'; +import type { LayoutData } from './rendering-util/types.js'; export type { MermaidConfig, @@ -30,6 +31,7 @@ export type { ParseResult, UnknownDiagramError, LayoutLoaderDefinition, + LayoutData, }; export interface RunOptions { diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 7958f397e0..dd8b3bbc81 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -30,7 +30,6 @@ vi.mock('./diagrams/packet/renderer.js'); vi.mock('./diagrams/xychart/xychartRenderer.js'); vi.mock('./diagrams/requirement/requirementRenderer.js'); vi.mock('./diagrams/sequence/sequenceRenderer.js'); -vi.mock('./diagrams/state/stateRenderer-v2.js'); // ------------------------------------- diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts index 542e622ea4..1e65c0282c 100644 --- a/packages/mermaid/src/rendering-util/createText.ts +++ b/packages/mermaid/src/rendering-util/createText.ts @@ -9,7 +9,7 @@ import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdo import { decodeEntities } from '../utils.js'; import { splitLineToFitWidth } from './splitText.js'; import type { MarkdownLine, MarkdownWord } from './types.js'; -import common, { hasKatex, renderKatex, hasKatex } from '$root/diagrams/common/common.js'; +import common, { hasKatex, renderKatex } from '$root/diagrams/common/common.js'; import { getConfig } from '$root/diagram-api/diagramAPI.js'; function applyStyle(dom, styleFn) { diff --git a/packages/mermaid/src/rendering-util/doLayout.ts b/packages/mermaid/src/rendering-util/doLayout.ts deleted file mode 100644 index dabd13dd79..0000000000 --- a/packages/mermaid/src/rendering-util/doLayout.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { log } from '$root/logger.js'; -import type { LayoutData, LayoutMethod, RenderData } from './types.js'; - -const performLayout = ( - layoutData: LayoutData, - id: string, - _version: string, - layoutMethod: LayoutMethod -): RenderData => { - log.info('Performing layout', layoutData, id, _version, layoutMethod); - return { items: [] }; -}; -export default performLayout; diff --git a/packages/mermaid/src/rendering-util/insertElementsForSize.js b/packages/mermaid/src/rendering-util/insertElementsForSize.js index 136e9e31f9..ff0b30ac64 100644 --- a/packages/mermaid/src/rendering-util/insertElementsForSize.js +++ b/packages/mermaid/src/rendering-util/insertElementsForSize.js @@ -1,8 +1,6 @@ -// import type { LayoutData } from './types'; import { select } from 'd3'; import { insertNode } from '../dagre-wrapper/nodes.js'; -// export const getDiagramElements = (id: string, securityLevel: any) => { export const getDiagramElements = (id, securityLevel) => { let sandboxElement; if (securityLevel === 'sandbox') { @@ -22,12 +20,6 @@ export const getDiagramElements = (id, securityLevel) => { return { svg, element }; }; -// export function insertElementsForSize(el: SVGElement, data: LayoutData): void { -/** - * - * @param el - * @param data - */ export function insertElementsForSize(el, data) { const nodesElem = el.insert('g').attr('class', 'nodes'); el.insert('g').attr('class', 'edges'); @@ -60,5 +52,3 @@ export function insertElementsForSize(el, data) { // document.body.appendChild(element); }); } - -export default insertElementsForSize; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/clusters.js b/packages/mermaid/src/rendering-util/rendering-elements/clusters.js index 1914b0a279..d2e9b5522e 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/clusters.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/clusters.js @@ -54,7 +54,6 @@ const rect = async (parent, node) => { } const padding = 0 * node.padding; - const halfPadding = padding / 2; const width = (node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width) + padding; @@ -293,7 +292,7 @@ const divider = (parent, node) => { const siteConfig = getConfig(); const { themeVariables, handdrawnSeed } = siteConfig; - const { compositeTitleBackground, nodeBorder } = themeVariables; + const { nodeBorder } = themeVariables; // Add outer g element const shapeSvg = parent diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js index dc22b6a03b..566e07031b 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js @@ -9,14 +9,13 @@ import { curveBasis, line, select } from 'd3'; import rough from 'roughjs'; import createLabel from './createLabel.js'; import { addEdgeMarkers } from './edgeMarker.ts'; -//import type { Edge } from '$root/rendering-util/types.d.ts'; -let edgeLabels = {}; -let terminalLabels = {}; +const edgeLabels = new Map(); +const terminalLabels = new Map(); export const clear = () => { - edgeLabels = {}; - terminalLabels = {}; + edgeLabels.clear(); + terminalLabels.clear(); }; export const getLabelStyles = (styleArray) => { @@ -27,16 +26,6 @@ export const getLabelStyles = (styleArray) => { export const insertEdgeLabel = async (elem, edge) => { let useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels); - // Create the actual text element - // const labelElement = - // edge.labelType === 'markdown' - // ? await createText(elem, edge.label, { - // style: labelStyles, - // useHtmlLabels, - // addSvgBackground: true, - // }) - // : await createLabel(edge.label, getLabelStyles(edge.labelStyle)); - const labelElement = await createText(elem, edge.label, { style: getLabelStyles(edge.labelStyle), useHtmlLabels, @@ -64,7 +53,7 @@ export const insertEdgeLabel = async (elem, edge) => { label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')'); // Make element accessible by id for positioning - edgeLabels[edge.id] = edgeLabel; + edgeLabels.set(edge.id, edgeLabel); // Update the abstract data of the edge with the new information about its width and height edge.width = bbox.width; @@ -82,10 +71,10 @@ export const insertEdgeLabel = async (elem, edge) => { fo = inner.node().appendChild(startLabelElement); const slBox = startLabelElement.getBBox(); inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); - if (!terminalLabels[edge.id]) { - terminalLabels[edge.id] = {}; + if (!terminalLabels.get(edge.id)) { + terminalLabels.set(edge.id, {}); } - terminalLabels[edge.id].startLeft = startEdgeLabelLeft; + terminalLabels.get(edge.id).startLeft = startEdgeLabelLeft; setTerminalWidth(fo, edge.startLabelLeft); } if (edge.startLabelRight) { @@ -101,10 +90,10 @@ export const insertEdgeLabel = async (elem, edge) => { const slBox = startLabelElement.getBBox(); inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); - if (!terminalLabels[edge.id]) { - terminalLabels[edge.id] = {}; + if (!terminalLabels.get(edge.id)) { + terminalLabels.set(edge.id, {}); } - terminalLabels[edge.id].startRight = startEdgeLabelRight; + terminalLabels.get(edge.id).startRight = startEdgeLabelRight; setTerminalWidth(fo, edge.startLabelRight); } if (edge.endLabelLeft) { @@ -118,10 +107,10 @@ export const insertEdgeLabel = async (elem, edge) => { endEdgeLabelLeft.node().appendChild(endLabelElement); - if (!terminalLabels[edge.id]) { - terminalLabels[edge.id] = {}; + if (!terminalLabels.get(edge.id)) { + terminalLabels.set(edge.id, {}); } - terminalLabels[edge.id].endLeft = endEdgeLabelLeft; + terminalLabels.get(edge.id).endLeft = endEdgeLabelLeft; setTerminalWidth(fo, edge.endLabelLeft); } if (edge.endLabelRight) { @@ -135,10 +124,10 @@ export const insertEdgeLabel = async (elem, edge) => { inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); endEdgeLabelRight.node().appendChild(endLabelElement); - if (!terminalLabels[edge.id]) { - terminalLabels[edge.id] = {}; + if (!terminalLabels.get(edge.id)) { + terminalLabels.set(edge.id, {}); } - terminalLabels[edge.id].endRight = endEdgeLabelRight; + terminalLabels.get(edge.id).endRight = endEdgeLabelRight; setTerminalWidth(fo, edge.endLabelRight); } return labelElement; @@ -156,12 +145,12 @@ function setTerminalWidth(fo, value) { } export const positionEdgeLabel = (edge, paths) => { - log.debug('Moving label abc88 ', edge.id, edge.label, edgeLabels[edge.id], paths); + log.debug('Moving label abc88 ', edge.id, edge.label, edgeLabels.get(edge.id), paths); let path = paths.updatedPath ? paths.updatedPath : paths.originalPath; const siteConfig = getConfig(); const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig); if (edge.label) { - const el = edgeLabels[edge.id]; + const el = edgeLabels.get(edge.id); let x = edge.x; let y = edge.y; if (path) { @@ -188,7 +177,7 @@ export const positionEdgeLabel = (edge, paths) => { //let path = paths.updatedPath ? paths.updatedPath : paths.originalPath; if (edge.startLabelLeft) { - const el = terminalLabels[edge.id].startLeft; + const el = terminalLabels.get(edge.id).startLeft; let x = edge.x; let y = edge.y; if (path) { @@ -200,7 +189,7 @@ export const positionEdgeLabel = (edge, paths) => { el.attr('transform', `translate(${x}, ${y})`); } if (edge.startLabelRight) { - const el = terminalLabels[edge.id].startRight; + const el = terminalLabels.get(edge.id).startRight; let x = edge.x; let y = edge.y; if (path) { @@ -216,7 +205,7 @@ export const positionEdgeLabel = (edge, paths) => { el.attr('transform', `translate(${x}, ${y})`); } if (edge.endLabelLeft) { - const el = terminalLabels[edge.id].endLeft; + const el = terminalLabels.get(edge.id).endLeft; let x = edge.x; let y = edge.y; if (path) { @@ -228,11 +217,10 @@ export const positionEdgeLabel = (edge, paths) => { el.attr('transform', `translate(${x}, ${y})`); } if (edge.endLabelRight) { - const el = terminalLabels[edge.id].endRight; + const el = terminalLabels.get(edge.id).endRight; let x = edge.x; let y = edge.y; if (path) { - // debugger; const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_right', path); x = pos.x; y = pos.y; @@ -242,17 +230,13 @@ export const positionEdgeLabel = (edge, paths) => { }; const outsideNode = (node, point) => { - // log.warn('Checking bounds ', node, point); const x = node.x; const y = node.y; const dx = Math.abs(point.x - x); const dy = Math.abs(point.y - y); const w = node.width / 2; const h = node.height / 2; - if (dx >= w || dy >= h) { - return true; - } - return false; + return dx >= w || dy >= h; }; export const intersection = (node, outsidePoint, insidePoint) => { @@ -264,7 +248,6 @@ export const intersection = (node, outsidePoint, insidePoint) => { const y = node.y; const dx = Math.abs(x - insidePoint.x); - // const dy = Math.abs(y - insidePoint.y); const w = node.width / 2; let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx; const h = node.height / 2; @@ -300,7 +283,6 @@ export const intersection = (node, outsidePoint, insidePoint) => { if (insidePoint.x < outsidePoint.x) { r = outsidePoint.x - w - x; } else { - // r = outsidePoint.x - w - x; r = x - w - outsidePoint.x; } let q = (Q * r) / R; @@ -530,8 +512,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod lineData.splice(-1, 0, midPoint); } // This is the accessor function we talked about above - let curve; - curve = curveBasis; + let curve = curveBasis; // curve = curveCardinal; // curve = curveLinear; // curve = curveNatural; @@ -540,6 +521,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod // curve = curveCardinal.tension(0.7); // curve = curveMonotoneY; // let curve = interpolateToCurve([5], curveNatural, 0.01, 10); + // Currently only flowcharts get the curve from the settings, perhaps this should // be expanded to a common setting? Restricting it for now in order not to cause side-effects that // have not been thought through diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-circle.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-circle.js index 8f5ba72dfc..b71d297eda 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-circle.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-circle.js @@ -1,10 +1,5 @@ import intersectEllipse from './intersect-ellipse.js'; -/** - * @param node - * @param rx - * @param point - */ function intersectCircle(node, rx, point) { return intersectEllipse(node, rx, rx, point); } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-ellipse.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-ellipse.js index 96f4a166e6..def637b2c5 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-ellipse.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-ellipse.js @@ -1,9 +1,3 @@ -/** - * @param node - * @param rx - * @param ry - * @param point - */ function intersectEllipse(node, rx, ry, point) { // Formulae from: https://mathworld.wolfram.com/Ellipse-LineIntersection.html diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js index e97ae6f0dc..bd3eb497f9 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-line.js @@ -1,10 +1,5 @@ /** * Returns the point at which two lines, p and q, intersect or returns undefined if they do not intersect. - * - * @param p1 - * @param p2 - * @param q1 - * @param q2 */ function intersectLine(p1, p2, q1, q2) { // Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994, @@ -67,10 +62,6 @@ function intersectLine(p1, p2, q1, q2) { return { x: x, y: y }; } -/** - * @param r1 - * @param r2 - */ function sameSign(r1, r2) { return r1 * r2 > 0; } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-node.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-node.js index 6bc1ea4803..08ed7b4c85 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-node.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-node.js @@ -1,7 +1,3 @@ -/** - * @param node - * @param point - */ function intersectNode(node, point) { return node.intersect(point); } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js index 39f6fddeb7..2e48ba8d01 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/intersect/intersect-polygon.js @@ -1,25 +1,17 @@ -/* eslint "no-console": off */ - import intersectLine from './intersect-line.js'; -export default intersectPolygon; - /** * Returns the point ({x, y}) at which the point argument intersects with the node argument assuming * that it has the shape specified by polygon. - * - * @param node - * @param polyPoints - * @param point */ function intersectPolygon(node, polyPoints, point) { - var x1 = node.x; - var y1 = node.y; + let x1 = node.x; + let y1 = node.y; - var intersections = []; + let intersections = []; - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; + let minX = Number.POSITIVE_INFINITY; + let minY = Number.POSITIVE_INFINITY; if (typeof polyPoints.forEach === 'function') { polyPoints.forEach(function (entry) { minX = Math.min(minX, entry.x); @@ -30,13 +22,13 @@ function intersectPolygon(node, polyPoints, point) { minY = Math.min(minY, polyPoints.y); } - var left = x1 - node.width / 2 - minX; - var top = y1 - node.height / 2 - minY; + let left = x1 - node.width / 2 - minX; + let top = y1 - node.height / 2 - minY; - for (var i = 0; i < polyPoints.length; i++) { - var p1 = polyPoints[i]; - var p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0]; - var intersect = intersectLine( + for (let i = 0; i < polyPoints.length; i++) { + let p1 = polyPoints[i]; + let p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0]; + let intersect = intersectLine( node, point, { x: left + p1.x, y: top + p1.y }, @@ -54,16 +46,18 @@ function intersectPolygon(node, polyPoints, point) { if (intersections.length > 1) { // More intersections, find the one nearest to edge end point intersections.sort(function (p, q) { - var pdx = p.x - point.x; - var pdy = p.y - point.y; - var distp = Math.sqrt(pdx * pdx + pdy * pdy); + let pdx = p.x - point.x; + let pdy = p.y - point.y; + let distp = Math.sqrt(pdx * pdx + pdy * pdy); - var qdx = q.x - point.x; - var qdy = q.y - point.y; - var distq = Math.sqrt(qdx * qdx + qdy * qdy); + let qdx = q.x - point.x; + let qdy = q.y - point.y; + let distq = Math.sqrt(qdx * qdx + qdy * qdy); return distp < distq ? -1 : distp === distq ? 0 : 1; }); } return intersections[0]; } + +export default intersectPolygon; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js index 03f8389923..54d4ddf3ee 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js @@ -49,7 +49,7 @@ const shapes = { labelRect, }; -let nodeElems = {}; +const nodeElems = new Map(); export const insertNode = async (elem, node, dir) => { let newEl; @@ -81,26 +81,23 @@ export const insertNode = async (elem, node, dir) => { if (node.tooltip) { el.attr('title', node.tooltip); } - // if (node.class) { - // el.attr('class', 'node default ' + node.class); - // } - nodeElems[node.id] = newEl; + nodeElems.set(node.id, newEl); if (node.haveCallback) { - nodeElems[node.id].attr('class', nodeElems[node.id].attr('class') + ' clickable'); + nodeElems.get(node.id).attr('class', nodeElems.get(node.id).attr('class') + ' clickable'); } return newEl; }; export const setNodeElem = (elem, node) => { - nodeElems[node.id] = elem; + nodeElems.set(node.id, elem); }; export const clear = () => { - nodeElems = {}; + nodeElems.clear(); }; export const positionNode = (node) => { - const el = nodeElems[node.id]; + const el = nodeElems.get(node.id); log.trace( 'Transforming node', node.diff, diff --git a/packages/mermaid/src/rendering-util/types.d.ts b/packages/mermaid/src/rendering-util/types.d.ts index 52d158b77f..33b8c4b78a 100644 --- a/packages/mermaid/src/rendering-util/types.d.ts +++ b/packages/mermaid/src/rendering-util/types.d.ts @@ -136,39 +136,3 @@ export type LayoutMethod = | 'fdp' | 'osage' | 'grid'; - -export function createDomElement(node: Node): Node { - // Create a new DOM element. Assuming we're creating a div as an example - const element = document.createElement('div'); - - // Check if node.domId is set, if not generate a unique identifier for it - if (!node.domId) { - // This is a simplistic approach to generate a unique ID - // In a real application, you might want to use a more robust method - node.domId = `node-${Math.random().toString(36).substr(2, 9)}`; - } - - // Set the ID of the DOM element - element.id = node.domId; - - // Optional: Apply styles and classes to the element - if (node.cssStyles) { - element.style.cssText = node.cssStyles; - } - if (node.classes) { - element.className = node.classes; - } - - // Optional: Add content or additional attributes to the element - // This can be based on other properties of the node - if (node.label) { - element.textContent = node.label; - } - - // Append the newly created element to the document body or a specific container - // This is just an example; in a real application, you might append it somewhere specific - document.body.appendChild(element); - - // Return the updated node with its domId set - return node; -}