import { computeEdgePositions, entityCodesToText, getTransformAttr, } from "../utils.js"; const parseSubGraph = (data, containerEl) => { // Extract only node id for better reference // e.g. full element id = "flowchart-c1-205" will map to "c1" const nodeIds = data.nodes.map((n) => { if (n.startsWith("flowchart-")) { return n.split("-")[1]; } return n; }); // Get position const el = containerEl.querySelector(`[id='${data.id}']`); if (!el) { throw new Error("SubGraph element not found"); } const position = computeElementPosition(el, containerEl); // Get dimension const boundingBox = el.getBBox(); const dimension = { width: boundingBox.width, height: boundingBox.height, }; // Remove irrelevant properties data.classes = undefined; data.dir = undefined; return { ...data, nodeIds, ...position, ...dimension, text: entityCodesToText(data.title), }; }; const parseVertex = (data, containerEl) => { // Find Vertex element const el = containerEl.querySelector(`[id*="flowchart-${data.id}-"]`); if (!el) { return undefined; } // Check if Vertex attached with link let link; if (el.parentElement?.tagName.toLowerCase() === "a") { link = el.parentElement.getAttribute("xlink:href"); } // Get position const position = computeElementPosition(link ? el.parentElement : el, containerEl); // Get dimension const boundingBox = el.getBBox(); const dimension = { width: boundingBox.width, height: boundingBox.height, }; // Extract style const labelContainerStyleText = el .querySelector(".label-container") ?.getAttribute("style"); const labelStyleText = el.querySelector(".label")?.getAttribute("style"); const containerStyle = {}; labelContainerStyleText?.split(";").forEach((property) => { if (!property) { return; } const key = property.split(":")[0].trim(); const value = property.split(":")[1].trim(); containerStyle[key] = value; }); const labelStyle = {}; labelStyleText?.split(";").forEach((property) => { if (!property) { return; } const key = property.split(":")[0].trim(); const value = property.split(":")[1].trim(); labelStyle[key] = value; }); return { id: data.id, labelType: data.labelType, text: entityCodesToText(data.text), type: data.type, link: link || undefined, ...position, ...dimension, containerStyle, labelStyle, }; }; const parseEdge = (data, edgeIndex, containerEl) => { // Find edge element const edge = containerEl.querySelector(`[id*="L-${data.start}-${data.end}-${edgeIndex}"]`); if (!edge) { throw new Error("Edge element not found"); } // Compute edge position data const position = computeElementPosition(edge, containerEl); const edgePositionData = computeEdgePositions(edge, position); // Remove irrelevant properties data.length = undefined; return { ...data, ...edgePositionData, text: entityCodesToText(data.text), }; }; // Compute element position const computeElementPosition = (el, containerEl) => { if (!el) { throw new Error("Element not found"); } let root = el.parentElement?.parentElement; const childElement = el.childNodes[0]; let childPosition = { x: 0, y: 0 }; if (childElement) { const { transformX, transformY } = getTransformAttr(childElement); const boundingBox = childElement.getBBox(); childPosition = { x: Number(childElement.getAttribute("x")) || transformX + boundingBox.x || 0, y: Number(childElement.getAttribute("y")) || transformY + boundingBox.y || 0, }; } const { transformX, transformY } = getTransformAttr(el); const position = { x: transformX + childPosition.x, y: transformY + childPosition.y, }; while (root && root.id !== containerEl.id) { if (root.classList.value === "root" && root.hasAttribute("transform")) { const { transformX, transformY } = getTransformAttr(root); position.x += transformX; position.y += transformY; } root = root.parentElement; } return position; }; export const parseMermaidFlowChartDiagram = (diagram, containerEl) => { // This does some cleanup and initialization making sure // diagram is parsed correctly. Useful when multiple diagrams are // parsed together one after another, eg in playground // https://github.com/mermaid-js/mermaid/blob/e561cbd3be2a93b8bedfa4839484966faad92ccf/packages/mermaid/src/Diagram.ts#L43 diagram.parse(); // Get mermaid parsed data from parser shared variable `yy` //@ts-ignore const mermaidParser = diagram.parser.yy; const vertices = mermaidParser.getVertices(); Object.keys(vertices).forEach((id) => { vertices[id] = parseVertex(vertices[id], containerEl); }); // Track the count of edges based on the edge id const edgeCountMap = new Map(); const edges = mermaidParser .getEdges() .filter((edge) => { // Sometimes mermaid parser returns edges which are not present in the DOM hence this is a safety check to only consider edges present in the DOM, issue - https://github.com/mermaid-js/mermaid/issues/5516 return containerEl.querySelector(`[id*="L-${edge.start}-${edge.end}"]`); }) .map((data) => { const edgeId = `${data.start}-${data.end}`; const count = edgeCountMap.get(edgeId) || 0; edgeCountMap.set(edgeId, count + 1); return parseEdge(data, count, containerEl); }); const subGraphs = mermaidParser .getSubGraphs() .map((data) => parseSubGraph(data, containerEl)); return { type: "flowchart", subGraphs, vertices, edges, }; };