mermaid
Version:
Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.
8 lines (7 loc) • 36.6 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../src/rendering-util/rendering-elements/lineJump.ts"],
"sourcesContent": ["/**\n * Line jumps (\"hops\") for edge crossings.\n *\n * Detects true segment crossings between edge polylines and rewrites the SVG\n * path of the later edge so the crossing renders as either a small arc\n * (`jumpStyle: 'arc'`) or a visible break (`jumpStyle: 'gap'`).\n *\n * The pure functions (`findEdgeIntersections`, `processEdgesWithJumps`) are\n * DOM-free. The DOM-side `applyLineJumpsToSvg` helper reads geometry from\n * layout data and leaves curved (non-`M`/`L`) rendered paths untouched.\n */\n\nimport type { D3Selection } from '../../types.js';\nimport { markerOffsets } from '../../utils/lineWithOffset.js';\n\n/** Radius used by edges.js' generateRoundedPath. Kept in sync so rewritten\n * paths look like the originals at bends. */\nconst ROUNDED_CORNER_RADIUS = 5;\n\n/** Skip the jump if its clamped radius falls below this \u2014 avoids invisible\n * zero-length arcs on very crowded paths. */\nconst CORNER_EPSILON = 1e-5;\n\n/**\n * Straight run kept between a hop and the bend next to it.\n *\n * Without it a hop may start exactly at the tangent point of a rounded corner,\n * so the path leaves the corner's quadratic and enters the arc with no straight\n * run between them. The two curves read as one malformed squiggle rather than\n * as a corner followed by a hop.\n */\nconst CORNER_JUMP_CLEARANCE = 2;\n\n/**\n * Smallest share of the requested radius a hop may shrink to before it is\n * dropped instead of drawn.\n *\n * A hop close to a bend has little room, and the clamps below will happily fit\n * one into whatever is left. That is the wrong trade: an arc at half radius no\n * longer clears the stroke it is meant to hop, so the lines still touch and the\n * result looks like a rendering fault rather than a crossing. An undrawn hop is\n * just an ordinary crossing, which is what every diagram looked like before\n * hops existed \u2014 a much better failure than a broken-looking one.\n *\n * This happens for real: ELK routes subgraph-internal edges into lanes 10px\n * apart, and a 10px offset cannot hold a 7.07px corner cut plus a 6px hop, so\n * every crossing in such a lane was being drawn at 2.9px hard against the bend.\n */\nconst MIN_USEFUL_RADIUS_RATIO = 0.6;\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface EdgeGeom {\n id: string;\n points: Point[];\n /**\n * Optional curve hint matching `edge.curve` from the rendering layer.\n * When set, line jumps are only applied for orthogonal-friendly curves\n * (`'linear'`, `'rounded'`, `'step'`, `'stepBefore'`, `'stepAfter'`, or\n * undefined). Other curves (basis, monotoneX, \u2026) are skipped to avoid\n * corrupting smoothed geometry.\n */\n curve?: string;\n /** Arrow type at the start (first point) \u2014 used to apply marker offset so\n * the rewritten path's endpoint matches the original rendered geometry and\n * the arrow marker orients correctly. */\n arrowTypeStart?: string;\n /** Arrow type at the end (last point). */\n arrowTypeEnd?: string;\n}\n\nexport interface LineJumpConfig {\n enabled: boolean;\n jumpRadius: number;\n jumpStyle: 'arc' | 'gap';\n}\n\nexport interface Crossing {\n jumpEdgeId: string;\n otherEdgeId: string;\n /** Index of the segment within the jumping edge's polyline. */\n segIndex: number;\n /** Position of the crossing along the jumping edge's segment, 0..1. */\n t: number;\n point: Point;\n}\n\nconst ENDPOINT_EPSILON = 1e-6;\n\ninterface Segment {\n a: Point;\n b: Point;\n}\n\nfunction buildSegmentList(points: Point[]): Segment[] {\n const segments: Segment[] = [];\n for (let i = 0; i < points.length - 1; i++) {\n segments.push({ a: points[i], b: points[i + 1] });\n }\n return segments;\n}\n\ninterface SegmentIntersection {\n point: Point;\n tA: number;\n tB: number;\n}\n\n/**\n * Parametric segment-segment intersection. Returns null if the segments are\n * parallel, do not intersect, or only meet at one of their endpoints (within\n * `ENDPOINT_EPSILON`). Endpoint rejection prevents normal joins, T-junctions,\n * and shared-start edges from being treated as crossings.\n */\nfunction segmentIntersection(\n a1: Point,\n a2: Point,\n b1: Point,\n b2: Point\n): SegmentIntersection | null {\n const dxA = a2.x - a1.x;\n const dyA = a2.y - a1.y;\n const dxB = b2.x - b1.x;\n const dyB = b2.y - b1.y;\n\n const denom = dxA * dyB - dyA * dxB;\n if (denom === 0) {\n return null;\n }\n\n const dx = b1.x - a1.x;\n const dy = b1.y - a1.y;\n\n const tA = (dx * dyB - dy * dxB) / denom;\n const tB = (dx * dyA - dy * dxA) / denom;\n\n if (\n tA <= ENDPOINT_EPSILON ||\n tA >= 1 - ENDPOINT_EPSILON ||\n tB <= ENDPOINT_EPSILON ||\n tB >= 1 - ENDPOINT_EPSILON\n ) {\n return null;\n }\n\n return {\n point: { x: a1.x + tA * dxA, y: a1.y + tA * dyA },\n tA,\n tB,\n };\n}\n\n/** True if the segment is horizontally dominant (abs(dx) is at least abs(dy)).\n * Ties go to horizontal to keep pure-diagonal edges grouped with the\n * horizontal bucket \u2014 they don't occur in orthogonal layouts anyway. */\nfunction isHorizontalSeg(seg: Segment): boolean {\n return Math.abs(seg.b.x - seg.a.x) >= Math.abs(seg.b.y - seg.a.y);\n}\n\n/**\n * True if a crossing on `edge`'s segment `segIndex` at parameter `t` falls\n * inside the stretch where the drawn stroke has left the polyline to round a\n * bend.\n *\n * Crossings are found on polylines, but a `rounded` edge is not drawn as its\n * polyline: `generateRoundedPath` replaces each bend with a quadratic that\n * departs the line up to `cutLen` before the vertex and rejoins it `cutLen`\n * after. Inside that stretch the polyline says the stroke is somewhere it is\n * not, so a \"crossing\" computed there is at best mislocated and at worst\n * fictional \u2014 and a hop drawn for it arches over blank paper while the two\n * strokes still touch alongside it.\n *\n * Only `rounded` edges lie this way; every other supported curve is drawn as\n * the polyline it describes.\n */\nfunction crossingSitsInRoundedCorner(edge: EdgeGeom, segIndex: number, t: number): boolean {\n if (edge.curve !== 'rounded') {\n return false;\n }\n const pts = edge.points;\n const a = pts[segIndex];\n const b = pts[segIndex + 1];\n if (!a || !b) {\n return false;\n }\n const segLen = Math.hypot(b.x - a.x, b.y - a.y);\n const d = t * segLen;\n\n const entering =\n segIndex > 0 ? computeRoundedCorner(pts[segIndex - 1], a, b, ROUNDED_CORNER_RADIUS) : null;\n if (entering && d < entering.cutLen) {\n return true;\n }\n\n const leaving =\n segIndex + 2 < pts.length\n ? computeRoundedCorner(a, b, pts[segIndex + 2], ROUNDED_CORNER_RADIUS)\n : null;\n return leaving !== null && segLen - d < leaving.cutLen;\n}\n\nexport function findEdgeIntersections(edges: EdgeGeom[]): Crossing[] {\n const crossings: Crossing[] = [];\n\n for (let i = 0; i < edges.length; i++) {\n const edgeA = edges[i];\n const segmentsA = buildSegmentList(edgeA.points);\n for (let j = i + 1; j < edges.length; j++) {\n const edgeB = edges[j];\n const segmentsB = buildSegmentList(edgeB.points);\n\n for (const [si, segA] of segmentsA.entries()) {\n for (const [sj, segB] of segmentsB.entries()) {\n const hit = segmentIntersection(segA.a, segA.b, segB.a, segB.b);\n if (!hit) {\n continue;\n }\n\n // Either edge rounding a bend here means the polyline is not where\n // the stroke is, so there is nothing trustworthy to hop over.\n if (\n crossingSitsInRoundedCorner(edgeA, si, hit.tA) ||\n crossingSitsInRoundedCorner(edgeB, sj, hit.tB)\n ) {\n continue;\n }\n\n // Orthogonal-orientation rule: when one segment is horizontal-\n // dominant and the other vertical-dominant, the HORIZONTAL one\n // gets the jump (classic line-hop convention \u2014 arcs arch upward\n // over the vertical line beneath). Falls back to later-index-wins\n // when both segments share an orientation.\n const aHoriz = isHorizontalSeg(segA);\n const bHoriz = isHorizontalSeg(segB);\n const orthogonalPair = aHoriz !== bHoriz;\n const jumpOnA = orthogonalPair ? aHoriz : false;\n\n if (jumpOnA) {\n crossings.push({\n jumpEdgeId: edgeA.id,\n otherEdgeId: edgeB.id,\n segIndex: si,\n t: hit.tA,\n point: hit.point,\n });\n } else {\n crossings.push({\n jumpEdgeId: edgeB.id,\n otherEdgeId: edgeA.id,\n segIndex: sj,\n t: hit.tB,\n point: hit.point,\n });\n }\n }\n }\n }\n }\n\n return crossings;\n}\n\nfunction fmt(n: number): string {\n // Strip trailing zeros so \"5.00\" \u2192 \"5\"; keep up to 3 decimals otherwise.\n const rounded = Math.round(n * 1000) / 1000;\n return Number.isInteger(rounded) ? `${rounded}` : `${rounded}`;\n}\n\nfunction pointToString(p: Point): string {\n return `${fmt(p.x)},${fmt(p.y)}`;\n}\n\n/**\n * Determines the SVG arc sweep flag so the jump bumps in the conventional\n * direction: horizontal segments bump up (smaller y in SVG), vertical segments\n * bump right (larger x).\n */\nfunction getArcSweepFlag(seg: Segment): 0 | 1 {\n const dx = seg.b.x - seg.a.x;\n const dy = seg.b.y - seg.a.y;\n if (Math.abs(dx) >= Math.abs(dy)) {\n // Horizontal-dominant: bump up (smaller y in SVG's y-down frame).\n // Going +x \u2192 sweep=1 sweeps through increasing angle 180\u00B0\u2192270\u00B0\u21920\u00B0,\n // which passes through (mid, y-r) = up.\n // Going -x \u2192 sweep=0 (reverse direction) also lands the bump above.\n return dx >= 0 ? 1 : 0;\n }\n // Vertical-dominant: bump right (positive x).\n // Going +y \u2192 sweep=1; going -y \u2192 sweep=0.\n return dy >= 0 ? 1 : 0;\n}\n\ninterface JumpOnSegment {\n t: number;\n point: Point;\n /** Distance from segment start along the segment direction. */\n d: number;\n /** Effective radius after boundary + adjacency clamping. */\n r: number;\n}\n\n/**\n * Shifts the first/last point inward along the edge direction by the amount\n * required for their arrow markers, matching `applyMarkerOffsetsToPoints` in\n * edges.js so the rewritten path ends exactly where the original did.\n */\nfunction applyMarkerOffsets(points: Point[], edge: EdgeGeom): Point[] {\n if (points.length < 2) {\n return points.map((p) => ({ ...p }));\n }\n const out = points.map((p) => ({ ...p }));\n const startOff =\n edge.arrowTypeStart && markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets];\n if (startOff) {\n const a = points[0];\n const b = points[1];\n const ang = Math.atan2(b.y - a.y, b.x - a.x);\n out[0].x = a.x + startOff * Math.cos(ang);\n out[0].y = a.y + startOff * Math.sin(ang);\n }\n const endOff =\n edge.arrowTypeEnd && markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets];\n if (endOff) {\n const n = points.length;\n const a = points[n - 2];\n const b = points[n - 1];\n const ang = Math.atan2(b.y - a.y, b.x - a.x);\n out[n - 1].x = b.x - endOff * Math.cos(ang);\n out[n - 1].y = b.y - endOff * Math.sin(ang);\n }\n return out;\n}\n\n/**\n * Emits the arc or gap command for a crossing, in the segment's direction.\n * Returns the part strings; caller inserts them in order.\n */\nfunction emitJump(\n jump: JumpOnSegment,\n ux: number,\n uy: number,\n sweep: 0 | 1,\n style: 'arc' | 'gap'\n): string[] {\n const cx = jump.point.x;\n const cy = jump.point.y;\n const pre = { x: cx - ux * jump.r, y: cy - uy * jump.r };\n const post = { x: cx + ux * jump.r, y: cy + uy * jump.r };\n const out = [`L${pointToString(pre)}`];\n if (style === 'arc') {\n out.push(`A${fmt(jump.r)},${fmt(jump.r)} 0 0 ${sweep} ${pointToString(post)}`);\n } else {\n out.push(`M${pointToString(post)}`);\n }\n return out;\n}\n\n/**\n * Mirrors the corner-rounding logic of `generateRoundedPath` in edges.js:\n * given a bend at `curr` between segments `prev\u2192curr` and `curr\u2192next`,\n * computes (startX, startY) just before curr on the incoming segment and\n * (endX, endY) just after curr on the outgoing segment, plus the Q control\n * point (which is curr itself). Returns `null` if the angle is degenerate\n * and the caller should just emit a straight `L curr`.\n */\ninterface RoundedCorner {\n startX: number;\n startY: number;\n endX: number;\n endY: number;\n ctrlX: number;\n ctrlY: number;\n /** How much the start of the rounded corner eats into the incoming segment. */\n cutLen: number;\n}\nfunction computeRoundedCorner(\n prev: Point,\n curr: Point,\n next: Point,\n radius: number\n): RoundedCorner | null {\n const dx1 = curr.x - prev.x;\n const dy1 = curr.y - prev.y;\n const dx2 = next.x - curr.x;\n const dy2 = next.y - curr.y;\n const len1 = Math.hypot(dx1, dy1);\n const len2 = Math.hypot(dx2, dy2);\n if (len1 < CORNER_EPSILON || len2 < CORNER_EPSILON) {\n return null;\n }\n const nx1 = dx1 / len1;\n const ny1 = dy1 / len1;\n const nx2 = dx2 / len2;\n const ny2 = dy2 / len2;\n const dot = nx1 * nx2 + ny1 * ny2;\n const clamped = Math.max(-1, Math.min(1, dot));\n const angle = Math.acos(clamped);\n if (angle < CORNER_EPSILON || Math.abs(Math.PI - angle) < CORNER_EPSILON) {\n return null;\n }\n const cutLen = Math.min(radius / Math.sin(angle / 2), len1 / 2, len2 / 2);\n return {\n startX: curr.x - nx1 * cutLen,\n startY: curr.y - ny1 * cutLen,\n endX: curr.x + nx2 * cutLen,\n endY: curr.y + ny2 * cutLen,\n ctrlX: curr.x,\n ctrlY: curr.y,\n cutLen,\n };\n}\n\nfunction rewriteEdgePath(edge: EdgeGeom, jumps: Crossing[], config: LineJumpConfig): string {\n const rawPoints = edge.points;\n if (rawPoints.length < 2) {\n return '';\n }\n\n // Match edges.js: shift the first/last point inward so arrow markers line up.\n const points = applyMarkerOffsets(rawPoints, edge);\n const rounded = edge.curve === 'rounded';\n\n // Jumps are indexed into the ORIGINAL (un-offset) segment list. For mid-\n // segments (i > 0 and i < n-2) the offsets don't change anything, and for\n // the first/last segment the shift is tiny compared to jump radius so\n // reusing the same (segIndex, t) is fine.\n const segments = buildSegmentList(points);\n const bySeg = new Map<number, JumpOnSegment[]>();\n for (const j of jumps) {\n const seg = segments[j.segIndex];\n if (!seg) {\n continue;\n }\n const segLen = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);\n const list = bySeg.get(j.segIndex) ?? [];\n list.push({\n t: j.t,\n point: j.point,\n d: j.t * segLen,\n r: config.jumpRadius,\n });\n bySeg.set(j.segIndex, list);\n }\n\n const parts: string[] = [`M${pointToString(points[0])}`];\n // Running cursor along the current segment measured from seg.a.\n // Consumed at the front by the previous corner's cutLen (for rounded) and\n // after that by mid-segment jumps.\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const segLen = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);\n const ux = segLen === 0 ? 0 : (seg.b.x - seg.a.x) / segLen;\n const uy = segLen === 0 ? 0 : (seg.b.y - seg.a.y) / segLen;\n const sweep = getArcSweepFlag(seg);\n\n // How much of the front of this segment was consumed by the previous\n // corner's Q end-point (endX,endY). Default 0.\n let segStartConsumed = 0;\n if (rounded && i > 0) {\n const corner = computeRoundedCorner(\n points[i - 1],\n points[i],\n points[i + 1] ?? points[i],\n ROUNDED_CORNER_RADIUS\n );\n if (corner) {\n segStartConsumed = corner.cutLen;\n }\n }\n\n // Rounded: if there's a next corner ahead, we stop short of it by cutLen.\n let segEndStop = segLen;\n let upcomingCorner: RoundedCorner | null = null;\n if (rounded && i < segments.length - 1) {\n upcomingCorner = computeRoundedCorner(\n points[i],\n points[i + 1],\n points[i + 2] ?? points[i + 1],\n ROUNDED_CORNER_RADIUS\n );\n if (upcomingCorner) {\n segEndStop = segLen - upcomingCorner.cutLen;\n }\n }\n\n // Clamp each jump to the room between the bends at either end of the\n // segment, then drop the ones with too little room to be worth drawing.\n // Dropping happens BEFORE the adjacency pass below so that a hop being\n // squeezed out by a corner does not also shrink its neighbours.\n const minUsefulRadius = config.jumpRadius * MIN_USEFUL_RADIUS_RATIO;\n const segJumps = [...(bySeg.get(i) ?? [])]\n .sort((a, b) => a.t - b.t)\n .filter((j) => {\n const room = Math.min(j.d - segStartConsumed, segEndStop - j.d) - CORNER_JUMP_CLEARANCE;\n j.r = Math.min(j.r, room);\n return j.r >= minUsefulRadius;\n });\n for (let k = 0; k < segJumps.length - 1; k++) {\n const gap = segJumps[k + 1].d - segJumps[k].d;\n if (segJumps[k].r + segJumps[k + 1].r > gap) {\n const half = gap / 2;\n segJumps[k].r = Math.min(segJumps[k].r, half);\n segJumps[k + 1].r = Math.min(segJumps[k + 1].r, half);\n }\n }\n\n for (const j of segJumps) {\n // Checked AGAIN after the adjacency pass, not only before it. That pass\n // can halve a radius to keep two hops off each other, and a hop shrunk\n // that way is just as unreadable as one squeezed by a bend \u2014 same rule,\n // both times. Two crossings too close to carry a hop each carry none.\n if (j.r < minUsefulRadius) {\n continue;\n }\n parts.push(...emitJump(j, ux, uy, sweep, config.jumpStyle));\n }\n\n // End of segment: either a straight L to seg.b (last segment or linear),\n // or a Q-corner into seg.b's neighborhood (rounded, middle).\n if (rounded && upcomingCorner) {\n parts.push(`L${fmt(upcomingCorner.startX)},${fmt(upcomingCorner.startY)}`);\n parts.push(\n `Q${fmt(upcomingCorner.ctrlX)},${fmt(upcomingCorner.ctrlY)} ${fmt(upcomingCorner.endX)},${fmt(upcomingCorner.endY)}`\n );\n } else {\n parts.push(`L${pointToString(seg.b)}`);\n }\n }\n\n return parts.join(' ');\n}\n\nfunction plainPath(points: Point[]): string {\n if (points.length === 0) {\n return '';\n }\n const parts = [`M${pointToString(points[0])}`];\n for (let i = 1; i < points.length; i++) {\n parts.push(`L${pointToString(points[i])}`);\n }\n return parts.join(' ');\n}\n\nexport function processEdgesWithJumps(\n edges: EdgeGeom[],\n config: LineJumpConfig\n): Map<string, string> {\n const result = new Map<string, string>();\n\n if (!config.enabled) {\n for (const edge of edges) {\n result.set(edge.id, plainPath(edge.points));\n }\n return result;\n }\n\n const crossings = findEdgeIntersections(edges);\n const jumpsByEdge = new Map<string, Crossing[]>();\n for (const c of crossings) {\n const list = jumpsByEdge.get(c.jumpEdgeId) ?? [];\n list.push(c);\n jumpsByEdge.set(c.jumpEdgeId, list);\n }\n\n for (const edge of edges) {\n const jumps = jumpsByEdge.get(edge.id);\n if (!jumps || jumps.length === 0) {\n result.set(edge.id, plainPath(edge.points));\n } else {\n result.set(edge.id, rewriteEdgePath(edge, jumps, config));\n }\n }\n\n return result;\n}\n\n/**\n * Returns true iff the SVG path `d` is a straight-line path \u2014 only `M`/`L`/`m`/`l`\n * move/line commands plus their numeric coordinates (digits, sign, decimal point,\n * scientific-notation `e`, and `,`/space separators). Curved paths are skipped by\n * the caller.\n */\nexport function isStraightPath(d: string): boolean {\n return /^[\\d\\s+,.LMelm-]*$/.test(d);\n}\n\n/**\n * Returns true iff the named curve produces orthogonal-friendly segments that\n * can be safely re-emitted with line jumps. Includes `'rounded'` even though\n * its rendered `d` contains `Q` corner-rounding commands \u2014 when an edge with\n * a jump is rewritten the corner rounding is dropped in exchange for visible\n * arc hops at crossings, which is the desired trade-off.\n */\nexport function curveSupportsLineHops(curve: string | undefined): boolean {\n if (!curve) {\n return true;\n }\n return (\n curve === 'linear' ||\n curve === 'rounded' ||\n curve === 'step' ||\n curve === 'stepBefore' ||\n curve === 'stepAfter'\n );\n}\n\n/**\n * Decodes the `data-points` attribute set by edges.js at render time. This\n * gives us the exact point list edges.js used to emit the rendered path \u2014\n * i.e. after node-boundary `intersect()` clipping and any orthogonalization,\n * but BEFORE `applyMarkerOffsetsToPoints`. Using these points guarantees the\n * rewrite's endpoints match the original rendered endpoints.\n */\nfunction decodeDataPoints(raw: string | null): Point[] | null {\n if (!raw) {\n return null;\n }\n try {\n const json = typeof atob === 'function' ? atob(raw) : Buffer.from(raw, 'base64').toString();\n const parsed = JSON.parse(json);\n if (!Array.isArray(parsed)) {\n return null;\n }\n const pts: Point[] = [];\n for (const p of parsed) {\n if (p && typeof p.x === 'number' && typeof p.y === 'number') {\n pts.push({ x: p.x, y: p.y });\n }\n }\n return pts.length >= 2 ? pts : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Patches the rendered SVG paths in `edgePathsGroup` for any edges that\n * cross. The true geometry is read from each path's `data-points` attribute\n * (written by edges.js at render time) so the rewrite's endpoints match\n * exactly what was originally rendered. Edges whose curve is a true\n * smoothing curve (`basis`, `monotoneX`, \u2026) are skipped.\n */\nexport function applyLineJumpsToSvg(\n edgePathsGroup: D3Selection<SVGGElement>,\n edges: EdgeGeom[],\n config: LineJumpConfig\n): void {\n if (!config.enabled) {\n return;\n }\n\n const groupNode = edgePathsGroup.node();\n if (!groupNode) {\n return;\n }\n\n // Build a metadata lookup so per-edge properties (curve, arrow types)\n // survive the DOM round-trip.\n const edgeMeta = new Map<string, EdgeGeom>();\n for (const e of edges) {\n edgeMeta.set(e.id, e);\n }\n\n // Collect geometry from each path's data-points, preferring that over the\n // incoming `edges[].points` which came from pre-render layout state.\n // Index the paths by their own `data-id` instead of building one selector per\n // edge. An id is author-controlled, so interpolating it into a selector needs\n // `CSS.escape`, which is not guaranteed outside a browser \u2014 and the fallback\n // of using the id raw turns a trailing backslash into a `SyntaxError` that\n // aborts the whole render. Reading the attribute avoids the selector entirely.\n const pathByDataId = new Map<string, Element>();\n for (const el of groupNode.querySelectorAll('path[data-id]')) {\n const id = el.getAttribute('data-id');\n if (id !== null && !pathByDataId.has(id)) {\n pathByDataId.set(id, el);\n }\n }\n\n const renderedEdges: EdgeGeom[] = [];\n for (const e of edges) {\n const pathEl = pathByDataId.get(e.id);\n if (!pathEl) {\n continue;\n }\n const decoded = decodeDataPoints(pathEl.getAttribute('data-points'));\n const points = decoded ?? e.points;\n renderedEdges.push({ ...e, points });\n }\n\n const crossings = findEdgeIntersections(renderedEdges);\n if (crossings.length === 0) {\n return;\n }\n\n const jumpsByEdge = new Map<string, Crossing[]>();\n for (const c of crossings) {\n const list = jumpsByEdge.get(c.jumpEdgeId) ?? [];\n list.push(c);\n jumpsByEdge.set(c.jumpEdgeId, list);\n }\n\n for (const renderedEdge of renderedEdges) {\n const jumps = jumpsByEdge.get(renderedEdge.id);\n if (!jumps || jumps.length === 0) {\n continue;\n }\n const meta = edgeMeta.get(renderedEdge.id);\n const curveHint = meta?.curve;\n if (curveHint !== undefined && !curveSupportsLineHops(curveHint)) {\n continue;\n }\n\n const pathEl = pathByDataId.get(renderedEdge.id);\n if (!pathEl) {\n continue;\n }\n\n if (curveHint === undefined) {\n const currentD = pathEl.getAttribute('d') ?? '';\n if (!isStraightPath(currentD)) {\n continue;\n }\n }\n\n // Read the ORIGINAL stroke-dasharray before rewriting so we can\n // recompute it against the new total length. The `neo` look emits:\n // stroke-dasharray: 0 <oValueS> <len - oValueS - oValueE> <oValueE>;\n // which hides the first oValueS and last oValueE pixels of the stroke\n // \u2014 this is what actually prevents the stroke from poking into the arrow\n // marker body. Our rewritten path has a different length, so without\n // updating the \"on\" portion the hidden tail ends up in the wrong place.\n const originalStyle = pathEl.getAttribute('style') ?? '';\n const dasharrayMatch = /stroke-dasharray\\s*:\\s*0\\s+([\\d.]+)\\s+[\\d.]+\\s+([\\d.]+)/.exec(\n originalStyle\n );\n const preservedOValueS = dasharrayMatch ? Number.parseFloat(dasharrayMatch[1]) : null;\n const preservedOValueE = dasharrayMatch ? Number.parseFloat(dasharrayMatch[2]) : null;\n\n const newD = rewriteEdgePath(renderedEdge, jumps, config);\n pathEl.setAttribute('d', newD);\n\n if (\n preservedOValueS !== null &&\n preservedOValueE !== null &&\n typeof (pathEl as SVGPathElement).getTotalLength === 'function'\n ) {\n const newLen = (pathEl as SVGPathElement).getTotalLength();\n const onLen = Math.max(0, newLen - preservedOValueS - preservedOValueE);\n const newDasharray = `0 ${preservedOValueS} ${onLen} ${preservedOValueE}`;\n const cleaned = originalStyle\n .replace(/stroke-dasharray\\s*:[^;]*;?/g, `stroke-dasharray: ${newDasharray};`)\n .replace(/;\\s*;+/g, ';');\n pathEl.setAttribute('style', cleaned);\n }\n }\n}\n"],
"mappings": "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",
"names": []
}