@mermaid-js/layout-elk
Version:
ELK layout engine for mermaid
5 lines • 68.6 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../src/render.ts", "../../../src/find-common-ancestor.ts", "../../../src/geometry.ts"],
"sourcesContent": ["import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from 'mermaid';\n// @ts-ignore TODO: Investigate D3 issue\nimport { curveLinear } from 'd3';\nimport ELK from 'elkjs/lib/elk.bundled.js';\nimport { type TreeData, findCommonAncestor } from './find-common-ancestor.js';\n\nimport {\n type P,\n type RectLike,\n outsideNode,\n computeNodeIntersection,\n replaceEndpoint,\n onBorder,\n} from './geometry.js';\n\ntype Node = LayoutData['nodes'][number];\n\n// Minimal structural type to avoid depending on d3 Selection typings\ninterface D3Selection<T extends Element> {\n node(): T | null;\n attr(name: string, value: string): D3Selection<T>;\n}\n\ninterface LabelData {\n width: number;\n height: number;\n wrappingWidth?: number;\n labelNode?: SVGGElement | null;\n}\n\ninterface NodeWithVertex extends Omit<Node, 'domId'> {\n children?: LayoutData['nodes'];\n labelData?: LabelData;\n domId?: D3Selection<SVGAElement | SVGGElement>;\n}\n\nexport const render = async (\n data4Layout: LayoutData,\n svg: SVG,\n {\n common,\n getConfig,\n insertCluster,\n insertEdge,\n insertEdgeLabel,\n insertMarkers,\n insertNode,\n interpolateToCurve,\n labelHelper,\n log,\n positionEdgeLabel,\n }: InternalHelpers,\n { algorithm }: RenderOptions\n) => {\n const nodeDb: Record<string, any> = {};\n const clusterDb: Record<string, any> = {};\n\n const addVertex = async (\n nodeEl: SVGGroup,\n graph: { children: NodeWithVertex[] },\n nodeArr: Node[],\n node: Node\n ) => {\n const labelData: LabelData = { width: 0, height: 0 };\n\n const config = getConfig();\n\n // Add the element to the DOM\n if (!node.isGroup) {\n // const child = node as NodeWithVertex;\n const child: NodeWithVertex = {\n id: node.id,\n width: node.width,\n height: node.height,\n // Store the original node data for later use\n label: node.label,\n isGroup: node.isGroup,\n shape: node.shape,\n padding: node.padding,\n cssClasses: node.cssClasses,\n cssStyles: node.cssStyles,\n look: node.look,\n // Include parentId for subgraph processing\n parentId: node.parentId,\n };\n graph.children.push(child);\n nodeDb[node.id] = node;\n\n const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });\n const boundingBox = childNodeEl.node()!.getBBox();\n // Store the domId separately for rendering, not in the ELK graph\n child.domId = childNodeEl;\n child.width = boundingBox.width;\n child.height = boundingBox.height;\n } else {\n // A subgraph\n const child: NodeWithVertex & { children: NodeWithVertex[] } = {\n ...node,\n domId: undefined,\n children: [],\n };\n // Let elk render with the copy\n graph.children.push(child);\n // Save the original containing the intersection function\n nodeDb[node.id] = child;\n await addVertices(nodeEl, nodeArr, child, node.id);\n\n if (node.label) {\n // @ts-ignore TODO: fix this\n const { shapeSvg, bbox } = await labelHelper(nodeEl, node, undefined, true);\n labelData.width = bbox.width;\n labelData.wrappingWidth = config.flowchart!.wrappingWidth;\n // Give some padding for elk\n labelData.height = bbox.height - 2;\n labelData.labelNode = shapeSvg.node();\n // We need the label hight to be able to size the subgraph;\n shapeSvg.remove();\n } else {\n // Subgraph without label\n labelData.width = 0;\n labelData.height = 0;\n }\n child.labelData = labelData;\n child.domId = nodeEl;\n }\n };\n\n const addVertices = async function (\n nodeEl: SVGGroup,\n nodeArr: Node[],\n graph: { children: NodeWithVertex[] },\n parentId?: string\n ) {\n const siblings = nodeArr.filter((node) => node?.parentId === parentId);\n log.info('addVertices APA12', siblings, parentId);\n // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition\n await Promise.all(\n siblings.map(async (node) => {\n await addVertex(nodeEl, graph, nodeArr, node);\n })\n );\n return graph;\n };\n\n const drawNodes = async (\n relX: number,\n relY: number,\n nodeArray: any[],\n svg: any,\n subgraphsEl: SVGGroup,\n depth: number\n ) => {\n await Promise.all(\n nodeArray.map(async function (node: {\n id: string | number;\n x: any;\n y: any;\n width: number;\n labels: { width: any }[];\n height: number;\n isGroup: any;\n labelData: any;\n offset: { posX: number; posY: number };\n shape: any;\n domId: { node: () => any; attr: (arg0: string, arg1: string) => void };\n }) {\n if (node) {\n nodeDb[node.id] ??= {};\n nodeDb[node.id].offset = {\n posX: node.x + relX,\n posY: node.y + relY,\n x: relX,\n y: relY,\n depth,\n width: Math.max(node.width, node.labels ? node.labels[0]?.width || 0 : 0),\n height: node.height,\n };\n if (node.isGroup) {\n log.debug('Id abc88 subgraph = ', node.id, node.x, node.y, node.labelData);\n const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph');\n // TODO use faster way of cloning\n const clusterNode = JSON.parse(JSON.stringify(node));\n clusterNode.x = node.offset.posX + node.width / 2;\n clusterNode.y = node.offset.posY + node.height / 2;\n clusterNode.width = Math.max(clusterNode.width, node.labelData.width);\n await insertCluster(subgraphEl, clusterNode);\n\n log.debug('Id (UIO)= ', node.id, node.width, node.shape, node.labels);\n } else {\n log.info(\n 'Id NODE = ',\n node.id,\n node.x,\n node.y,\n relX,\n relY,\n node.domId.node(),\n `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`\n );\n node.domId.attr(\n 'transform',\n `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`\n );\n }\n }\n })\n );\n\n await Promise.all(\n nodeArray.map(async function (node: { isGroup: any; x: any; y: any; children: any }) {\n if (node?.isGroup) {\n await drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, depth + 1);\n }\n })\n );\n };\n\n const addSubGraphs = (nodeArr: any[]): TreeData => {\n const parentLookupDb: TreeData = { parentById: {}, childrenById: {} };\n const subgraphs = nodeArr.filter((node: { isGroup: any }) => node.isGroup);\n log.info('Subgraphs - ', subgraphs);\n subgraphs.forEach((subgraph: { id: string }) => {\n const children = nodeArr.filter((node: { parentId: any }) => node.parentId === subgraph.id);\n children.forEach((node: any) => {\n parentLookupDb.parentById[node.id] = subgraph.id;\n if (parentLookupDb.childrenById[subgraph.id] === undefined) {\n parentLookupDb.childrenById[subgraph.id] = [];\n }\n parentLookupDb.childrenById[subgraph.id].push(node);\n });\n });\n\n return parentLookupDb;\n };\n\n const getEdgeStartEndPoint = (edge: any) => {\n // edge.start and edge.end are IDs (string/number) in our layout data\n const sourceId: string | number = edge.start;\n const targetId: string | number = edge.end;\n\n const source = sourceId;\n const target = targetId;\n\n const startNode = nodeDb[sourceId];\n const endNode = nodeDb[targetId];\n\n if (!startNode || !endNode) {\n return { source, target };\n }\n\n // Add the edge to the graph\n return { source, target, sourceId, targetId };\n };\n\n const calcOffset = function (src: string, dest: string, parentLookupDb: TreeData) {\n const ancestor = findCommonAncestor(src, dest, parentLookupDb);\n if (ancestor === undefined || ancestor === 'root') {\n return { x: 0, y: 0 };\n }\n\n const ancestorOffset = nodeDb[ancestor].offset;\n return { x: ancestorOffset.posX, y: ancestorOffset.posY };\n };\n\n /**\n * Add edges to graph based on parsed graph definition\n */\n // Edge helper maps and utilities (de-duplicated)\n const ARROW_MAP: Record<string, [string, string]> = {\n arrow_open: ['arrow_open', 'arrow_open'],\n arrow_cross: ['arrow_open', 'arrow_cross'],\n double_arrow_cross: ['arrow_cross', 'arrow_cross'],\n arrow_point: ['arrow_open', 'arrow_point'],\n double_arrow_point: ['arrow_point', 'arrow_point'],\n arrow_circle: ['arrow_open', 'arrow_circle'],\n double_arrow_circle: ['arrow_circle', 'arrow_circle'],\n };\n\n const computeStroke = (\n stroke: string | undefined,\n defaultStyle?: string,\n defaultLabelStyle?: string\n ) => {\n // Defaults correspond to 'normal'\n let thickness = 'normal';\n let pattern = 'solid';\n let style = '';\n let labelStyle = '';\n\n if (stroke === 'dotted') {\n pattern = 'dotted';\n style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';\n } else if (stroke === 'thick') {\n thickness = 'thick';\n style = 'stroke-width: 3.5px;fill:none;';\n } else {\n // normal\n style = defaultStyle ?? 'fill:none;';\n if (defaultLabelStyle !== undefined) {\n labelStyle = defaultLabelStyle;\n }\n }\n return { thickness, pattern, style, labelStyle };\n };\n\n const getCurve = (edgeInterpolate: any, edgesDefaultInterpolate: any, confCurve: any) => {\n if (edgeInterpolate !== undefined) {\n return interpolateToCurve(edgeInterpolate, curveLinear);\n }\n if (edgesDefaultInterpolate !== undefined) {\n return interpolateToCurve(edgesDefaultInterpolate, curveLinear);\n }\n // @ts-ignore TODO: fix this\n return interpolateToCurve(confCurve, curveLinear);\n };\n const buildEdgeData = (\n edge: any,\n defaults: {\n defaultStyle?: string;\n defaultLabelStyle?: string;\n defaultInterpolate?: any;\n confCurve: any;\n },\n common: any\n ) => {\n const edgeData: any = { style: '', labelStyle: '' };\n edgeData.minlen = edge.length || 1;\n // maintain legacy behavior\n edge.text = edge.label;\n\n // Arrowhead fill vs none\n edgeData.arrowhead = edge.type === 'arrow_open' ? 'none' : 'normal';\n\n // Arrow types\n const arrowMap = ARROW_MAP[edge.type] ?? ARROW_MAP.arrow_open;\n edgeData.arrowTypeStart = arrowMap[0];\n edgeData.arrowTypeEnd = arrowMap[1];\n\n // Optional edge label positioning flags\n edgeData.startLabelRight = edge.startLabelRight;\n edgeData.endLabelLeft = edge.endLabelLeft;\n\n // Stroke\n const strokeRes = computeStroke(edge.stroke, defaults.defaultStyle, defaults.defaultLabelStyle);\n edgeData.thickness = strokeRes.thickness;\n edgeData.pattern = strokeRes.pattern;\n edgeData.style = (edgeData.style || '') + (strokeRes.style || '');\n edgeData.labelStyle = (edgeData.labelStyle || '') + (strokeRes.labelStyle || '');\n\n // Curve\n // @ts-ignore - defaults.confCurve is present at runtime but missing in type\n edgeData.curve = getCurve(edge.interpolate, defaults.defaultInterpolate, defaults.confCurve);\n\n // Arrowhead style + labelpos when we have label text\n const hasText = (edge?.text ?? '') !== '';\n if (hasText) {\n edgeData.arrowheadStyle = 'fill: #333';\n edgeData.labelpos = 'c';\n } else if (edge.style !== undefined) {\n edgeData.arrowheadStyle = 'fill: #333';\n }\n\n edgeData.labelType = edge.labelType;\n edgeData.label = (edge?.text ?? '').replace(common.lineBreakRegex, '\\n');\n\n if (edge.style === undefined) {\n edgeData.style = edgeData.style ?? 'stroke: #333; stroke-width: 1.5px;fill:none;';\n }\n\n edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');\n return edgeData;\n };\n\n const addEdges = async function (\n dataForLayout: { edges: any; direction?: string },\n graph: {\n id?: string;\n layoutOptions?: {\n 'elk.hierarchyHandling': string;\n 'elk.algorithm': any;\n 'nodePlacement.strategy': any;\n 'elk.layered.mergeEdges': any;\n 'elk.direction': string;\n 'spacing.baseValue': number;\n };\n children?: never[];\n edges: any;\n },\n svg: SVG\n ) {\n log.info('abc78 DAGA edges = ', dataForLayout);\n const edges = dataForLayout.edges;\n const labelsEl = svg.insert('g').attr('class', 'edgeLabels');\n const linkIdCnt: any = {};\n let defaultStyle: string | undefined;\n let defaultLabelStyle: string | undefined;\n\n await Promise.all(\n edges.map(async function (edge: {\n id: string;\n start: string;\n end: string;\n length: number;\n text: undefined;\n label: any;\n type: string;\n stroke: any;\n interpolate: undefined;\n style: undefined;\n labelType: any;\n startLabelRight?: string;\n endLabelLeft?: string;\n }) {\n // Identify Link\n const linkIdBase = edge.id; // 'L-' + edge.start + '-' + edge.end;\n // count the links from+to the same node to give unique id\n if (linkIdCnt[linkIdBase] === undefined) {\n linkIdCnt[linkIdBase] = 0;\n log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);\n } else {\n linkIdCnt[linkIdBase]++;\n log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);\n }\n const linkId = linkIdBase; // + '_' + linkIdCnt[linkIdBase];\n edge.id = linkId;\n log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);\n const linkNameStart = 'LS_' + edge.start;\n const linkNameEnd = 'LE_' + edge.end;\n\n const conf = getConfig();\n const edgeData = buildEdgeData(\n edge,\n {\n defaultStyle,\n defaultLabelStyle,\n defaultInterpolate: edges.defaultInterpolate,\n // @ts-ignore - conf.curve exists at runtime but is missing from typing\n confCurve: conf.curve,\n },\n common\n );\n\n edgeData.id = linkId;\n edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;\n\n const labelEl = await insertEdgeLabel(labelsEl, edgeData);\n\n // calculate start and end points of the edge, note that the source and target\n // can be modified for shapes that have ports\n\n const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge);\n log.debug('abc78 source and target', source, target);\n // Add the edge to the graph\n graph.edges.push({\n ...edge,\n sources: [source],\n targets: [target],\n sourceId,\n targetId,\n labelEl: labelEl,\n labels: [\n {\n width: edgeData.width,\n height: edgeData.height,\n orgWidth: edgeData.width,\n orgHeight: edgeData.height,\n text: edgeData.label,\n layoutOptions: {\n 'edgeLabels.inline': 'true',\n 'edgeLabels.placement': 'CENTER',\n },\n },\n ],\n edgeData,\n });\n })\n );\n return graph;\n };\n\n function dir2ElkDirection(dir: any) {\n switch (dir) {\n case 'LR':\n return 'RIGHT';\n case 'RL':\n return 'LEFT';\n case 'TB':\n case 'TD': // TD is an alias for TB in Mermaid\n return 'DOWN';\n case 'BT':\n return 'UP';\n default:\n return 'DOWN';\n }\n }\n\n function setIncludeChildrenPolicy(nodeId: string, ancestorId: string) {\n const node = nodeDb[nodeId];\n\n if (!node) {\n return;\n }\n if (node?.layoutOptions === undefined) {\n node.layoutOptions = {};\n }\n node.layoutOptions['elk.hierarchyHandling'] = 'INCLUDE_CHILDREN';\n if (node.id !== ancestorId) {\n setIncludeChildrenPolicy(node.parentId, ancestorId);\n }\n }\n\n // Node bounds helpers (global)\n const getEffectiveGroupWidth = (node: any): number => {\n const labelW = node?.labels?.[0]?.width ?? 0;\n const padding = node?.padding ?? 0;\n return Math.max(node.width ?? 0, labelW + padding);\n };\n\n const boundsFor = (node: any): RectLike => {\n const width = node?.isGroup ? getEffectiveGroupWidth(node) : node.width;\n return {\n x: node.offset.posX + node.width / 2,\n y: node.offset.posY + node.height / 2,\n width,\n height: node.height,\n padding: node.padding,\n };\n };\n // Helper utilities for endpoint handling around cutter2\n type Side = 'start' | 'end';\n const approxEq = (a: number, b: number, eps = 1e-6) => Math.abs(a - b) < eps;\n const isCenterApprox = (pt: P, node: { x: number; y: number }) =>\n approxEq(pt.x, node.x) && approxEq(pt.y, node.y);\n\n const getCandidateBorderPoint = (\n points: P[],\n node: any,\n side: Side\n ): { candidate: P; centerApprox: boolean } => {\n if (!points?.length) {\n return { candidate: { x: node.x, y: node.y } as P, centerApprox: true };\n }\n if (side === 'start') {\n const first = points[0];\n const centerApprox = isCenterApprox(first, node);\n const candidate = centerApprox && points.length > 1 ? points[1] : first;\n return { candidate, centerApprox };\n } else {\n const last = points[points.length - 1];\n const centerApprox = isCenterApprox(last, node);\n const candidate = centerApprox && points.length > 1 ? points[points.length - 2] : last;\n return { candidate, centerApprox };\n }\n };\n\n const dropAutoCenterPoint = (points: P[], side: Side, doDrop: boolean) => {\n if (!doDrop) {\n return;\n }\n if (side === 'start') {\n if (points.length > 0) {\n points.shift();\n }\n } else {\n if (points.length > 0) {\n points.pop();\n }\n }\n };\n\n const applyStartIntersectionIfNeeded = (points: P[], startNode: any, startBounds: RectLike) => {\n let firstOutsideStartIndex = -1;\n for (const [i, p] of points.entries()) {\n if (outsideNode(startBounds, p)) {\n firstOutsideStartIndex = i;\n break;\n }\n }\n if (firstOutsideStartIndex !== -1) {\n const outsidePointForStart = points[firstOutsideStartIndex];\n const startCenter = points[0];\n const startIntersection = computeNodeIntersection(\n startNode,\n startBounds,\n outsidePointForStart,\n startCenter\n );\n replaceEndpoint(points, 'start', startIntersection);\n log.debug('UIO cutter2: start-only intersection applied', { startIntersection });\n }\n };\n\n const applyEndIntersectionIfNeeded = (points: P[], endNode: any, endBounds: RectLike) => {\n let outsideIndexForEnd = -1;\n for (let i = points.length - 1; i >= 0; i--) {\n if (outsideNode(endBounds, points[i])) {\n outsideIndexForEnd = i;\n break;\n }\n }\n if (outsideIndexForEnd !== -1) {\n const outsidePointForEnd = points[outsideIndexForEnd];\n const endCenter = points[points.length - 1];\n const endIntersection = computeNodeIntersection(\n endNode,\n endBounds,\n outsidePointForEnd,\n endCenter\n );\n replaceEndpoint(points, 'end', endIntersection);\n log.debug('UIO cutter2: end-only intersection applied', { endIntersection });\n }\n };\n\n const cutter2 = (startNode: any, endNode: any, _points: any[]) => {\n const startBounds = boundsFor(startNode);\n const endBounds = boundsFor(endNode);\n\n if (_points.length === 0) {\n return [];\n }\n\n // Copy the original points array\n const points: P[] = [..._points] as P[];\n\n // The first point is the center of sNode, the last point is the center of eNode\n const startCenter = points[0];\n const endCenter = points[points.length - 1];\n\n // Minimal, structured logging for diagnostics\n log.debug('PPP cutter2: bounds', { startBounds, endBounds });\n log.debug('PPP cutter2: original points', _points);\n\n let firstOutsideStartIndex = -1;\n\n // Single iteration through the array\n for (const [i, point] of points.entries()) {\n if (firstOutsideStartIndex === -1 && outsideNode(startBounds, point)) {\n firstOutsideStartIndex = i;\n }\n if (outsideNode(endBounds, point)) {\n // keep scanning; we'll also scan from the end for the last outside point\n }\n }\n\n // Calculate intersection with start node if we found a point outside it\n if (firstOutsideStartIndex !== -1) {\n const outsidePointForStart = points[firstOutsideStartIndex];\n const startIntersection = computeNodeIntersection(\n startNode,\n startBounds,\n outsidePointForStart,\n startCenter\n );\n log.debug('UIO cutter2: start intersection', startIntersection);\n replaceEndpoint(points, 'start', startIntersection);\n }\n\n // Calculate intersection with end node\n let outsidePointForEnd = null;\n let outsideIndexForEnd = -1;\n\n for (let i = points.length - 1; i >= 0; i--) {\n if (outsideNode(endBounds, points[i])) {\n outsidePointForEnd = points[i];\n outsideIndexForEnd = i;\n break;\n }\n }\n\n if (!outsidePointForEnd && points.length > 1) {\n outsidePointForEnd = points[points.length - 2];\n outsideIndexForEnd = points.length - 2;\n }\n\n if (outsidePointForEnd) {\n const endIntersection = computeNodeIntersection(\n endNode,\n endBounds,\n outsidePointForEnd,\n endCenter\n );\n log.debug('UIO cutter2: end intersection', { endIntersection, outsideIndexForEnd });\n replaceEndpoint(points, 'end', endIntersection);\n }\n\n // Final cleanup: Check if the last point is too close to the previous point\n if (points.length > 1) {\n const lastPoint = points[points.length - 1];\n const secondLastPoint = points[points.length - 2];\n const distance = Math.sqrt(\n (lastPoint.x - secondLastPoint.x) ** 2 + (lastPoint.y - secondLastPoint.y) ** 2\n );\n if (distance < 2) {\n log.debug('UIO cutter2: trimming tail point (too close)', {\n distance,\n lastPoint,\n secondLastPoint,\n });\n points.pop();\n }\n }\n\n log.debug('UIO cutter2: final points', points);\n\n return points;\n };\n\n // @ts-ignore - ELK is not typed\n const elk = new ELK();\n const element = svg.select('g');\n // Add the arrowheads to the svg\n insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);\n\n // Setup the graph with the layout options and the data for the layout\n let elkGraph: any = {\n id: 'root',\n layoutOptions: {\n 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',\n 'elk.algorithm': algorithm,\n 'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,\n 'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,\n 'elk.direction': 'DOWN',\n 'spacing.baseValue': 40,\n 'elk.layered.crossingMinimization.forceNodeModelOrder':\n data4Layout.config.elk?.forceNodeModelOrder,\n 'elk.layered.considerModelOrder.strategy': data4Layout.config.elk?.considerModelOrder,\n 'elk.layered.unnecessaryBendpoints': true,\n 'elk.layered.cycleBreaking.strategy': data4Layout.config.elk?.cycleBreakingStrategy,\n\n // 'elk.layered.cycleBreaking.strategy': 'GREEDY_MODEL_ORDER',\n // 'elk.layered.cycleBreaking.strategy': 'MODEL_ORDER',\n // 'spacing.nodeNode': 20,\n // 'spacing.nodeNodeBetweenLayers': 25,\n // 'spacing.edgeNode': 20,\n // 'spacing.edgeNodeBetweenLayers': 10,\n // 'spacing.edgeEdge': 10,\n // 'spacing.edgeEdgeBetweenLayers': 20,\n // 'spacing.nodeSelfLoop': 20,\n\n // Tweaking options\n // 'nodePlacement.favorStraightEdges': true,\n // 'elk.layered.nodePlacement.favorStraightEdges': true,\n // 'nodePlacement.feedbackEdges': true,\n 'elk.layered.wrapping.multiEdge.improveCuts': true,\n 'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,\n // 'elk.layered.wrapping.strategy': 'MULTI_EDGE',\n // 'elk.layered.wrapping.strategy': 'SINGLE_EDGE',\n 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',\n 'elk.layered.mergeHierarchyEdges': true,\n\n // 'elk.layered.feedbackEdges': true,\n // 'elk.layered.crossingMinimization.semiInteractive': true,\n // 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,\n // 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,\n // 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',\n // 'elk.insideSelfLoops.activate': true,\n // 'elk.separateConnectedComponents': true,\n // 'elk.alg.layered.options.EdgeStraighteningStrategy': 'NONE',\n // 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES', // NODES_AND_EDGES\n // 'elk.layered.considerModelOrder.strategy': 'EDGES', // NODES_AND_EDGES\n // 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES\n },\n children: [],\n edges: [],\n };\n\n log.info('Drawing flowchart using v4 renderer', elk);\n\n // Set the direction of the graph based on the parsed information\n const dir = data4Layout.direction ?? 'DOWN';\n elkGraph.layoutOptions['elk.direction'] = dir2ElkDirection(dir);\n\n // Create the lookup db for the subgraphs and their children to used when creating\n // the tree structured graph\n const parentLookupDb: any = addSubGraphs(data4Layout.nodes);\n\n // Add elements in the svg to be used to hold the subgraphs container\n // elements and the nodes\n const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');\n\n const nodeEl = svg.insert('g').attr('class', 'nodes');\n\n // Add the nodes to the graph, this will entail creating the actual nodes\n // in order to get the size of the node. You can't get the size of a node\n // that is not in the dom so we need to add it to the dom, get the size\n // we will position the nodes when we get the layout from elkjs\n elkGraph = await addVertices(nodeEl, data4Layout.nodes, elkGraph);\n // Time for the edges, we start with adding an element in the node to hold the edges\n const edgesEl = svg.insert('g').attr('class', 'edges edgePaths');\n\n // Add the edges to the elk graph, this will entail creating the actual edges\n elkGraph = await addEdges(data4Layout, elkGraph, svg);\n\n // Iterate through all nodes and add the top level nodes to the graph\n const nodes = data4Layout.nodes;\n nodes.forEach((n: { id: string | number }) => {\n const node = nodeDb[n.id];\n\n // Subgraph\n if (parentLookupDb.childrenById[node.id] !== undefined) {\n // Set label and adjust node width separately (avoid side effects in labels array)\n node.labels = [\n {\n text: node.label,\n width: node?.labelData?.width ?? 50,\n height: node?.labelData?.height ?? 50,\n },\n ];\n node.width = node.width + 2 * node.padding;\n log.debug('UIO node label', node?.labelData?.width, node.padding);\n node.layoutOptions = {\n 'spacing.baseValue': 30,\n 'nodeLabels.placement': '[H_CENTER V_TOP, INSIDE]',\n };\n if (node.dir) {\n node.layoutOptions = {\n ...node.layoutOptions,\n 'elk.algorithm': algorithm,\n 'elk.direction': dir2ElkDirection(node.dir),\n 'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,\n 'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,\n 'elk.hierarchyHandling': 'SEPARATE_CHILDREN',\n };\n }\n delete node.x;\n delete node.y;\n delete node.width;\n delete node.height;\n }\n });\n log.debug('APA01 processing edges, count:', elkGraph.edges.length);\n elkGraph.edges.forEach((edge: any, index: number) => {\n log.debug('APA01 processing edge', index, ':', edge);\n const source = edge.sources[0];\n const target = edge.targets[0];\n log.debug('APA01 source:', source, 'target:', target);\n log.debug('APA01 nodeDb[source]:', nodeDb[source]);\n log.debug('APA01 nodeDb[target]:', nodeDb[target]);\n\n if (nodeDb[source] && nodeDb[target] && nodeDb[source].parentId !== nodeDb[target].parentId) {\n const ancestorId = findCommonAncestor(source, target, parentLookupDb);\n // an edge that breaks a subgraph has been identified, set configuration accordingly\n setIncludeChildrenPolicy(source, ancestorId);\n setIncludeChildrenPolicy(target, ancestorId);\n }\n });\n\n log.debug('APA01 before');\n log.debug('APA01 elkGraph structure:', JSON.stringify(elkGraph, null, 2));\n log.debug('APA01 elkGraph.children length:', elkGraph.children?.length);\n log.debug('APA01 elkGraph.edges length:', elkGraph.edges?.length);\n\n // Validate that all edge references exist as nodes\n elkGraph.edges?.forEach((edge: any, index: number) => {\n log.debug(`APA01 validating edge ${index}:`, edge);\n if (edge.sources) {\n edge.sources.forEach((sourceId: any) => {\n const sourceExists = elkGraph.children?.some((child: any) => child.id === sourceId);\n log.debug(`APA01 source ${sourceId} exists:`, sourceExists);\n });\n }\n if (edge.targets) {\n edge.targets.forEach((targetId: any) => {\n const targetExists = elkGraph.children?.some((child: any) => child.id === targetId);\n log.debug(`APA01 target ${targetId} exists:`, targetExists);\n });\n }\n });\n\n let g;\n try {\n g = await elk.layout(elkGraph);\n log.debug('APA01 after - success');\n log.info('APA01 layout result:', JSON.stringify(g, null, 2));\n } catch (error) {\n log.error('APA01 ELK layout error:', error);\n log.error('APA01 elkGraph that caused error:', JSON.stringify(elkGraph, null, 2));\n throw error;\n }\n\n // debugger;\n await drawNodes(0, 0, g.children, svg, subGraphsEl, 0);\n\n g.edges?.map(\n (edge: {\n sources: (string | number)[];\n targets: (string | number)[];\n start: any;\n end: any;\n sections: { startPoint: any; endPoint: any; bendPoints: any }[];\n points: any[];\n x: any;\n labels: { height: number; width: number; x: number; y: number }[];\n y: any;\n curve?: any;\n }) => {\n // (elem, edge, clusterDb, diagramType, graph, id)\n const startNode = nodeDb[edge.sources[0]];\n const startCluster = parentLookupDb[edge.sources[0]];\n const endNode = nodeDb[edge.targets[0]];\n const sourceId = edge.start;\n const targetId = edge.end;\n\n const offset = calcOffset(sourceId, targetId, parentLookupDb);\n log.debug(\n 'APA18 offset',\n offset,\n sourceId,\n ' ==> ',\n targetId,\n 'edge:',\n edge,\n 'cluster:',\n startCluster,\n startNode\n );\n if (edge.sections) {\n const src = edge.sections[0].startPoint;\n const dest = edge.sections[0].endPoint;\n const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];\n\n const segPoints = segments.map((segment: { x: any; y: any }) => {\n return { x: segment.x + offset.x, y: segment.y + offset.y };\n });\n edge.points = [\n { x: src.x + offset.x, y: src.y + offset.y },\n ...segPoints,\n { x: dest.x + offset.x, y: dest.y + offset.y },\n ];\n\n let sw = startNode.width;\n let ew = endNode.width;\n if (startNode.isGroup) {\n const bbox = startNode.domId.node().getBBox();\n // sw = Math.max(bbox.width, startNode.width, startNode.labels[0].width);\n sw = Math.max(startNode.width, startNode.labels[0].width + startNode.padding);\n // sw = startNode.width;\n log.info(\n 'UIO width',\n startNode.id,\n startNode.width,\n 'bbox.width=',\n bbox.width,\n 'lw=',\n startNode.labels[0].width,\n 'node:',\n startNode.width,\n 'SW = ',\n sw\n // 'HTML:',\n // startNode.domId.node().innerHTML\n );\n }\n if (endNode.isGroup) {\n const bbox = endNode.domId.node().getBBox();\n ew = Math.max(endNode.width, endNode.labels[0].width + endNode.padding);\n\n log.debug(\n 'UIO width',\n startNode.id,\n startNode.width,\n bbox.width,\n 'EW = ',\n ew,\n 'HTML:',\n startNode.innerHTML\n );\n }\n startNode.x = startNode.offset.posX + startNode.width / 2;\n startNode.y = startNode.offset.posY + startNode.height / 2;\n endNode.x = endNode.offset.posX + endNode.width / 2;\n endNode.y = endNode.offset.posY + endNode.height / 2;\n\n // Only add center points for non-subgraph nodes or when the edge path doesn't already end near the target\n const shouldAddStartCenter = startNode.shape !== 'rect33';\n const shouldAddEndCenter = endNode.shape !== 'rect33';\n\n if (shouldAddStartCenter) {\n edge.points.unshift({\n x: startNode.x,\n y: startNode.y,\n });\n }\n\n if (shouldAddEndCenter) {\n edge.points.push({\n x: endNode.x,\n y: endNode.y,\n });\n }\n\n // Debug and sanitize points around cutter2\n const prevPoints = Array.isArray(edge.points) ? [...edge.points] : [];\n const endBounds = boundsFor(endNode);\n log.debug(\n 'PPP cutter2: Points before cutter2:',\n JSON.stringify(edge.points),\n 'endBounds:',\n endBounds,\n onBorder(endBounds, edge.points[edge.points.length - 1])\n );\n // Block for reducing variable scope and guardrails for the cutter function\n {\n const startBounds = boundsFor(startNode);\n const endBounds = boundsFor(endNode);\n\n const startIsGroup = !!startNode?.isGroup;\n const endIsGroup = !!endNode?.isGroup;\n\n const { candidate: startCandidate, centerApprox: startCenterApprox } =\n getCandidateBorderPoint(prevPoints as P[], startNode, 'start');\n const { candidate: endCandidate, centerApprox: endCenterApprox } =\n getCandidateBorderPoint(prevPoints as P[], endNode, 'end');\n\n const skipStart = startIsGroup && onBorder(startBounds, startCandidate);\n const skipEnd = endIsGroup && onBorder(endBounds, endCandidate);\n\n dropAutoCenterPoint(prevPoints as P[], 'start', skipStart && startCenterApprox);\n dropAutoCenterPoint(prevPoints as P[], 'end', skipEnd && endCenterApprox);\n\n if (skipStart || skipEnd) {\n if (!skipStart) {\n applyStartIntersectionIfNeeded(prevPoints as P[], startNode, startBounds);\n }\n if (!skipEnd) {\n applyEndIntersectionIfNeeded(prevPoints as P[], endNode, endBounds);\n }\n\n log.debug('PPP cutter2: skipping cutter2 due to on-border group endpoint(s)', {\n skipStart,\n skipEnd,\n startCenterApprox,\n endCenterApprox,\n startCandidate,\n endCandidate,\n });\n edge.points = prevPoints;\n } else {\n edge.points = cutter2(startNode, endNode, prevPoints);\n }\n }\n log.debug('PPP cutter2: Points after cutter2:', JSON.stringify(edge.points));\n const hasNaN = (pts: { x: number; y: number }[]) =>\n pts?.some((p) => !Number.isFinite(p?.x) || !Number.isFinite(p?.y));\n if (!Array.isArray(edge.points) || edge.points.length < 2 || hasNaN(edge.points)) {\n log.warn(\n 'POI cutter2: Invalid points from cutter2, falling back to prevPoints',\n edge.points\n );\n // Fallback to previous points and strip any invalid ones just in case\n const cleaned = prevPoints.filter((p) => Number.isFinite(p?.x) && Number.isFinite(p?.y));\n edge.points = cleaned.length >= 2 ? cleaned : prevPoints;\n }\n log.debug('UIO cutter2: Points after cutter2 (sanitized):', edge.points);\n // Remove consecutive duplicate points to avoid zero-length segments in path builders\n const deduped = edge.points.filter(\n (p: { x: number; y: number }, i: number, arr: { x: number; y: number }[]) => {\n if (i === 0) {\n return true;\n }\n const prev = arr[i - 1];\n return Math.abs(p.x - prev.x) > 1e-6 || Math.abs(p.y - prev.y) > 1e-6;\n }\n );\n if (deduped.length !== edge.points.length) {\n log.debug('UIO cutter2: removed consecutive duplicate points', {\n before: edge.points,\n after: deduped,\n });\n }\n edge.points = deduped;\n // ELK produces orthogonal edge routes \u2014 override the curve to 'rounded' (right-angle\n // segments with rounded corners) so basis/smooth interpolation doesn't distort them.\n edge.curve = 'rounded';\n const paths = insertEdge(\n edgesEl,\n edge,\n clusterDb,\n data4Layout.type,\n startNode,\n endNode,\n data4Layout.diagramId,\n true\n );\n log.info('APA12 edge points after insert', JSON.stringify(edge.points));\n\n edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;\n edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;\n positionEdgeLabel(edge, paths);\n }\n }\n );\n};\n", "export interface TreeData {\n parentById: Record<string, string>;\n childrenById: Record<string, string[]>;\n}\n\nexport const findCommonAncestor = (id1: string, id2: string, { parentById }: TreeData) => {\n const visited = new Set();\n let currentId = id1;\n\n // Edge case with self edges\n if (id1 === id2) {\n return parentById[id1] || 'root';\n }\n\n while (currentId) {\n visited.add(currentId);\n if (currentId === id2) {\n return currentId;\n }\n currentId = parentById[currentId];\n }\n\n currentId = id2;\n while (currentId) {\n if (visited.has(currentId)) {\n return currentId;\n }\n currentId = parentById[currentId];\n }\n\n return 'root';\n};\n", "/* Geometry utilities extracted from render.ts for reuse and testing */\n\nexport interface P {\n x: number;\n y: number;\n}\n\nexport interface RectLike {\n x: number; // center x\n y: number; // center y\n width: number;\n height: number;\n padding?: number;\n}\n\nexport interface NodeLike {\n intersect?: (p: P) => P | null;\n}\n\nexport const EPS = 1;\nexport const PUSH_OUT = 10;\n\nexport const onBorder = (bounds: RectLike, p: P, tol = 0.5): boolean => {\n const halfW = bounds.width / 2;\n const halfH = bounds.height / 2;\n const left = bounds.x - halfW;\n const right = bounds.x + halfW;\n const top = bounds.y - halfH;\n const bottom = bounds.y + halfH;\n\n const onLeft = Math.abs(p.x - left) <= tol && p.y >= top - tol && p.y <= bottom + tol;\n const onRight = Math.abs(p.x - right) <= tol && p.y >= top - tol && p.y <= bottom + tol;\n const onTop = Math.abs(p.y - top) <= tol && p.x >= left - tol && p.x <= right + tol;\n const onBottom = Math.abs(p.y - bottom) <= tol && p.x >= left - tol && p.x <= right + tol;\n return onLeft || onRight || onTop || onBottom;\n};\n\n/**\n * Compute intersection between a rectangle (center x/y, width/height) and the line\n * segment from insidePoint -\\> outsidePoint. Returns the point on the rectangle border.\n *\n * This version avoids snapping to outsidePoint when certain variables evaluate to 0\n * (previously caused vertical top/bottom cases to miss the border). It only enforces\n * axis-constant behavior for purely vertical/horizontal approaches.\n */\nexport const intersection = (node: RectLike, outsidePoint: P, insidePoint: P): P => {\n const x = node.x;\n const y = node.y;\n\n const dx = Math.abs(x - insidePoint.x);\n const w = node.width / 2;\n let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;\n const h = node.height / 2;\n\n const Q = Math.abs(outsidePoint.y - insidePoint.y);\n const R = Math.abs(outsidePoint.x - insidePoint.x);\n\n if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {\n // Intersection is top or bottom of rect.\n const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;\n r = (R * q) / Q;\n const res = {\n x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,\n y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,\n };\n\n // Keep axis-constant special-cases only\n if (R === 0) {\n res.x = outsidePoint.x;\n }\n if (Q === 0) {\n res.y = outsidePoint.y;\n }\n return res;\n } else {\n // Intersection on sides of rect\n if (insidePoint.x < outsidePoint.x) {\n r = outsidePoint.x - w - x;\n } else {\n r = x - w - outsidePoint.x;\n }\n const q = (Q * r) / R;\n let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;\n let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;\n\n // Only handle axis-constant cases\n if (R === 0) {\n _x = outsidePoint.x;\n }\n if (Q === 0) {\n _y = outsidePoint.y;\n }\n\n return { x: _x, y: _y };\n }\n};\n\nexport const outsideNode = (node: RectLike, point: P): boolean => {\n const x = node.x;\n const y = node.y;\n const dx = Math.abs(point.x - x);\n const dy = Math.abs(point.y - y);\n const w = node.width / 2;\n const h = node.height / 2;\n return dx >= w || dy >= h;\n};\n\nexport const ensureTrulyOutside = (bounds: RectLike, p: P, push = PUSH_OUT): P => {\n const dx = Math.abs(p.x - bounds.x);\n const dy = Math.abs(p.y - bounds.y);\n const w = bounds.width / 2;\n const h = bounds.height / 2;\n if (Math.abs(dx - w) < EPS || Math.abs(dy - h) < EPS) {\n const dirX = p.x - bounds.x;\n const dirY = p.y - bounds.y;\n const len = Math.sqrt(dirX * dirX + dirY * dirY);\n if (len > 0) {\n return {\n x: bounds.x + (dirX / len) * (len + push),\n y: bounds.y + (dirY / len) * (len + push),\n };\n }\n }\n return p;\n};\n\nexport const makeInsidePoint = (bounds: RectLike, outside: P, center: P): P => {\n const isVertical = Math.abs(outside.x - bounds.x) < EPS;\n const isHorizontal = Math.abs(outside.y - bounds.y) < EPS;\n return {\n x: isVertical\n ? outside.x\n : outside.x < bounds.x\n ? bounds.x - bounds.width / 4\n : bounds.x + bounds.width / 4,\n y: isHorizontal ? outside.y : center.y,\n };\n};\n\nexport const tryNodeIntersect = (node: NodeLike, bounds: RectLike, outside: P): P | null => {\n if (!node?.intersect) {\n return null;\n }\n const res = node.intersect(outside);\n if (!res) {\n return null;\n }\n const wrongSide =\n (outside.x < bounds.x && res.x > bounds.x) || (outside.x > bounds.x && res.x < bounds.x);\n if (wrongSide) {\n return null;\n }\n const dist = Math.hypot(outside.x - res.x, outside.y - res.y);\n if (dist <= EPS) {\n return null;\n }\n return res;\n};\n\nexport const fallbackIntersection = (bounds: RectLike, outside: P, center: P): P => {\n const inside = makeInsidePoint(bounds, outside, center);\n return intersection(bounds, outside, inside);\n};\n\nexport const computeNodeIntersection = (\n node: NodeLike,\n bounds: RectLike,\n outside: P,\n center: P\n): P => {\n const outside2 = ensureTrulyOutside(bounds, outside);\n return tryNodeIntersect(node, bounds, outside2) ?? fallbackIntersection(bounds, outside2, center);\n};\n\nexport const replaceEndpoint = (\n points: P[],\n which: 'start' | 'end',\n value: P | null | undefined,\n tol = 0.1\n) => {\n if (!value || points.length === 0) {\n return;\n }\n\n if (which === 'start') {\n if (\n points.length > 0 &&\n Math.abs(points[0].x - value.x) < tol &&\n Math.abs(points[0].y - value.y) < tol\n ) {\n // duplicate start remove it\n points.shift();\n } else {\n points[0] = value;\n }\n } else {\n const last = points.length - 1;\n if (\n points.length > 0 &&\n Math.abs(points[last].x - value.x) < tol &&\n Math.abs(points[last].y - value.y) < tol\n ) {\n // duplicate end remove it\n points.pop();\n } else {\n points[last] = value;\n }\n }\n};\n"],
"mappings": "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