@kekonic/diagrams-routing 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kekonic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @kekonic/diagrams-routing
2
+
3
+ Post-ELK edge finishing for KDiagram: crossing treatment, endpoint trim, and label placement types.
4
+
5
+ ## Scope
6
+
7
+ - Crossing modes: `none` / `gaps` / `jumps` / `smart`
8
+ - Arrowhead / crow's-foot endpoint insets
9
+ - Shared `RoutedEdge` / `TreatedEdge` / `EdgeLabelPlacement` types
10
+
11
+ Orthogonal corridors normally come from ELK (or region-arrange stubs). This package does **not** search routes.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add @kekonic/diagrams-routing
17
+ ```
18
+
19
+ Prefer the meta package `@kekonic/diagrams` for the full pipeline.
20
+
21
+ ## Related
22
+
23
+ - Layout: `@kekonic/diagrams-layout`
24
+ - Pipeline boundaries: [`docs/architecture/pipeline.md`](../../docs/architecture/pipeline.md)
@@ -0,0 +1,75 @@
1
+ import { PerimeterAttachInput, attachPointOnPerimeter } from "@kekonic/diagrams-geometry";
2
+ import { CrossingMode, Diagnostic, Point, Rect } from "@kekonic/diagrams-core";
3
+
4
+ //#region src/routing/types.d.ts
5
+ type RoutedEdge = {
6
+ edgeId: string;
7
+ points: Point[];
8
+ segments: Array<{
9
+ from: Point;
10
+ to: Point;
11
+ }>;
12
+ };
13
+ /** Post-ELK edge geometry after label placement and endpoint trim. */
14
+ type RoutingResult = {
15
+ edges: RoutedEdge[];
16
+ algorithmVersion: string;
17
+ routeMs: number;
18
+ diagnostics: Diagnostic[];
19
+ };
20
+ //#endregion
21
+ //#region src/crossings/treat.d.ts
22
+ /** Target trim: tip lands just shy of the node stroke; dash/solid stop at the arrow base. */
23
+ declare const ARROW_ENDPOINT_INSET: number;
24
+ /**
25
+ * Crow's-foot tip sits on the path endpoint (marker refX), unlike arrows whose tip
26
+ * overhangs past refX. Keep only a hairline gap so the tip kisses the table stroke.
27
+ */
28
+ declare const CARDINALITY_ENDPOINT_INSET = 1.5;
29
+ /** Small inset for edges without arrowheads — clears node stroke at connection. */
30
+ declare const EDGE_ENDPOINT_INSET = 1;
31
+ type RenderedEdgeSegment = {
32
+ type: "line";
33
+ from: Point;
34
+ to: Point;
35
+ } | {
36
+ type: "gap";
37
+ from: Point;
38
+ to: Point;
39
+ } | {
40
+ type: "jump";
41
+ from: Point;
42
+ to: Point;
43
+ radius: number;
44
+ };
45
+ type TreatedEdge = {
46
+ edgeId: string;
47
+ segments: RenderedEdgeSegment[];
48
+ };
49
+ declare function applyCrossingTreatment(edges: RoutedEdge[], mode: CrossingMode): TreatedEdge[];
50
+ /** Crossing points between distinct edge segments (for label scoring). */
51
+ declare function detectCrossingPoints(edges: RoutedEdge[]): Point[];
52
+ type TrimEdgeEndpointsOptions = {
53
+ /** Uniform source inset, or per-edge via callback. */sourceInset?: number | ((edgeId: string) => number); /** Uniform target inset, or per-edge via callback. */
54
+ targetInset?: number | ((edgeId: string) => number);
55
+ };
56
+ /** Shorten first/last line segments so strokes and arrowheads stop before node fills. */
57
+ declare function trimEdgeEndpoints(edges: TreatedEdge[], options?: TrimEdgeEndpointsOptions): TreatedEdge[];
58
+ //#endregion
59
+ //#region src/labels/types.d.ts
60
+ /** Edge label bounds produced by ELK and painted by the SVG renderer. */
61
+ type EdgeLabelPlacement = {
62
+ edgeId: string;
63
+ text: string;
64
+ bounds: Rect;
65
+ anchor: Point;
66
+ };
67
+ /** Icon size inside an edge-label pill (logical units). Keep in sync with SVG paint. */
68
+ declare const EDGE_LABEL_ICON = 12;
69
+ /** Gap between edge-label icon and text. */
70
+ declare const EDGE_LABEL_ICON_GAP = 4;
71
+ /** Horizontal padding inside the edge-label pill. */
72
+ declare const EDGE_LABEL_PAD_X = 8;
73
+ //#endregion
74
+ export { ARROW_ENDPOINT_INSET, CARDINALITY_ENDPOINT_INSET, EDGE_ENDPOINT_INSET, EDGE_LABEL_ICON, EDGE_LABEL_ICON_GAP, EDGE_LABEL_PAD_X, type EdgeLabelPlacement, type PerimeterAttachInput, type RenderedEdgeSegment, type RoutedEdge, type RoutingResult, type TreatedEdge, applyCrossingTreatment, attachPointOnPerimeter, detectCrossingPoints, trimEdgeEndpoints };
75
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,288 @@
1
+ import { attachPointOnPerimeter } from "@kekonic/diagrams-geometry";
2
+ /**
3
+ * How far to pull the path endpoint back from the node border.
4
+ * Path end = arrow base; tip overhangs to (approximately) the attach point.
5
+ */
6
+ const ARROW_NODE_GAP = 1.5;
7
+ /** Target trim: tip lands just shy of the node stroke; dash/solid stop at the arrow base. */
8
+ const ARROW_ENDPOINT_INSET = 9;
9
+ /**
10
+ * Crow's-foot tip sits on the path endpoint (marker refX), unlike arrows whose tip
11
+ * overhangs past refX. Keep only a hairline gap so the tip kisses the table stroke.
12
+ */
13
+ const CARDINALITY_ENDPOINT_INSET = ARROW_NODE_GAP;
14
+ /** Small inset for edges without arrowheads — clears node stroke at connection. */
15
+ const EDGE_ENDPOINT_INSET = 1;
16
+ /** Ignore crossings this close to a segment end — gaps there orphan markers on stubs. */
17
+ const CROSSING_ENDPOINT_CLEARANCE = 28;
18
+ /** Crossings within this distance form a dense cluster (prefer gaps in smart mode). */
19
+ const SMART_CLUSTER_PX = 48;
20
+ /** Prefer gaps when a crossing sits this close to a polyline bend. */
21
+ const SMART_CORNER_CLEARANCE = 24;
22
+ function dist(a, b) {
23
+ return Math.hypot(a.x - b.x, a.y - b.y);
24
+ }
25
+ /** Mark crossings that sit in a dense local cluster (≥2 within SMART_CLUSTER_PX). */
26
+ function denseCrossingIndexes(crossings) {
27
+ const dense = /* @__PURE__ */ new Set();
28
+ for (let i = 0; i < crossings.length; i++) {
29
+ let neighbors = 0;
30
+ for (let j = 0; j < crossings.length; j++) {
31
+ if (i === j) continue;
32
+ if (dist(crossings[i].point, crossings[j].point) <= SMART_CLUSTER_PX) neighbors++;
33
+ }
34
+ if (neighbors >= 1) dense.add(i);
35
+ }
36
+ return dense;
37
+ }
38
+ function nearPolylineCorner(edges, edgeId, point) {
39
+ const edge = edges.find((e) => e.edgeId === edgeId);
40
+ if (!edge || edge.points.length < 3) return false;
41
+ for (let i = 1; i < edge.points.length - 1; i++) if (dist(edge.points[i], point) <= SMART_CORNER_CLEARANCE) return true;
42
+ return false;
43
+ }
44
+ /**
45
+ * Decide gap vs jump for smart mode (§14.3):
46
+ * - isolated orthogonal crossings → jumps
47
+ * - dense clusters → gaps
48
+ * - crossings near corners → gaps (jumps look noisy on bends)
49
+ */
50
+ function smartUsesJump(crossing, index, dense, edges) {
51
+ if (dense.has(index)) return false;
52
+ if (nearPolylineCorner(edges, crossing.hop.edgeId, crossing.point)) return false;
53
+ if (nearPolylineCorner(edges, crossing.under.edgeId, crossing.point)) return false;
54
+ return true;
55
+ }
56
+ function applyCrossingTreatment(edges, mode) {
57
+ if (mode === "none") return edges.map((e) => ({
58
+ edgeId: e.edgeId,
59
+ segments: e.segments.map((s) => ({
60
+ type: "line",
61
+ from: s.from,
62
+ to: s.to
63
+ }))
64
+ }));
65
+ const allSegments = [];
66
+ for (const edge of edges) edge.segments.forEach((s, idx) => allSegments.push({
67
+ edgeId: edge.edgeId,
68
+ idx,
69
+ from: s.from,
70
+ to: s.to
71
+ }));
72
+ const crossings = [];
73
+ for (let i = 0; i < allSegments.length; i++) for (let j = i + 1; j < allSegments.length; j++) {
74
+ const a = allSegments[i];
75
+ const b = allSegments[j];
76
+ if (a.edgeId === b.edgeId) continue;
77
+ const pt = segmentIntersection(a.from, a.to, b.from, b.to);
78
+ if (!pt) continue;
79
+ const hop = Math.abs(a.from.y - a.to.y) <= Math.abs(a.from.x - a.to.x) ? a : b;
80
+ const under = hop === a ? b : a;
81
+ crossings.push({
82
+ hop,
83
+ under,
84
+ point: pt
85
+ });
86
+ }
87
+ const dense = mode === "smart" ? denseCrossingIndexes(crossings) : /* @__PURE__ */ new Set();
88
+ /** Per hop-segment key → list of {point, useJump} */
89
+ const treatmentsAt = /* @__PURE__ */ new Map();
90
+ crossings.forEach((c, index) => {
91
+ const key = `${c.hop.edgeId}:${c.hop.idx}`;
92
+ if (!treatmentsAt.has(key)) treatmentsAt.set(key, []);
93
+ const useJump = mode === "jumps" ? true : mode === "gaps" ? false : smartUsesJump(c, index, dense, edges);
94
+ treatmentsAt.get(key).push({
95
+ point: c.point,
96
+ useJump
97
+ });
98
+ });
99
+ const jumpRadius = 10;
100
+ return edges.map((edge) => {
101
+ const segments = [];
102
+ edge.segments.forEach((s, idx) => {
103
+ const key = `${edge.edgeId}:${idx}`;
104
+ const points = treatmentsAt.get(key) ?? [];
105
+ if (!points.length) {
106
+ segments.push({
107
+ type: "line",
108
+ from: s.from,
109
+ to: s.to
110
+ });
111
+ return;
112
+ }
113
+ const segLen = Math.hypot(s.to.x - s.from.x, s.to.y - s.from.y) || 1;
114
+ const sorted = points.map((p) => ({
115
+ ...p,
116
+ t: paramOnSegment(s.from, s.to, p.point)
117
+ })).filter((x) => {
118
+ const fromStart = x.t * segLen;
119
+ const fromEnd = (1 - x.t) * segLen;
120
+ return fromStart > CROSSING_ENDPOINT_CLEARANCE && fromEnd > CROSSING_ENDPOINT_CLEARANCE;
121
+ }).sort((a, b) => a.t - b.t);
122
+ let prev = s.from;
123
+ for (const { point: p, useJump } of sorted) {
124
+ const t = paramOnSegment(s.from, s.to, p);
125
+ const half = Math.min(.04, Math.max(.015, 8 / (Math.hypot(s.to.x - s.from.x, s.to.y - s.from.y) || 1)));
126
+ const gapStart = lerp(s.from, s.to, Math.max(0, t - half));
127
+ const gapEnd = lerp(s.from, s.to, Math.min(1, t + half));
128
+ segments.push({
129
+ type: "line",
130
+ from: prev,
131
+ to: gapStart
132
+ });
133
+ if (useJump) segments.push({
134
+ type: "jump",
135
+ from: gapStart,
136
+ to: gapEnd,
137
+ radius: jumpRadius
138
+ });
139
+ else segments.push({
140
+ type: "gap",
141
+ from: gapStart,
142
+ to: gapEnd
143
+ });
144
+ prev = gapEnd;
145
+ }
146
+ segments.push({
147
+ type: "line",
148
+ from: prev,
149
+ to: s.to
150
+ });
151
+ });
152
+ return {
153
+ edgeId: edge.edgeId,
154
+ segments
155
+ };
156
+ });
157
+ }
158
+ function segmentIntersection(a1, a2, b1, b2) {
159
+ const eps = .75;
160
+ const aH = Math.abs(a1.y - a2.y) < eps;
161
+ const aV = Math.abs(a1.x - a2.x) < eps;
162
+ const bH = Math.abs(b1.y - b2.y) < eps;
163
+ const bV = Math.abs(b1.x - b2.x) < eps;
164
+ if (aH && bV) {
165
+ const y = (a1.y + a2.y) / 2;
166
+ const x = (b1.x + b2.x) / 2;
167
+ if (between(x, a1.x, a2.x) && between(y, b1.y, b2.y)) return {
168
+ x,
169
+ y
170
+ };
171
+ return null;
172
+ }
173
+ if (aV && bH) {
174
+ const x = (a1.x + a2.x) / 2;
175
+ const y = (b1.y + b2.y) / 2;
176
+ if (between(y, a1.y, a2.y) && between(x, b1.x, b2.x)) return {
177
+ x,
178
+ y
179
+ };
180
+ return null;
181
+ }
182
+ const d = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x);
183
+ if (Math.abs(d) < 1e-6) return null;
184
+ const t = ((b1.x - a1.x) * (b2.y - b1.y) - (b1.y - a1.y) * (b2.x - b1.x)) / d;
185
+ const u = ((b1.x - a1.x) * (a2.y - a1.y) - (b1.y - a1.y) * (a2.x - a1.x)) / d;
186
+ if (t > .02 && t < .98 && u > .02 && u < .98) return {
187
+ x: a1.x + t * (a2.x - a1.x),
188
+ y: a1.y + t * (a2.y - a1.y)
189
+ };
190
+ return null;
191
+ }
192
+ /** Crossing points between distinct edge segments (for label scoring). */
193
+ function detectCrossingPoints(edges) {
194
+ const allSegments = [];
195
+ for (const edge of edges) for (const s of edge.segments) allSegments.push({
196
+ edgeId: edge.edgeId,
197
+ from: s.from,
198
+ to: s.to
199
+ });
200
+ const points = [];
201
+ for (let i = 0; i < allSegments.length; i++) for (let j = i + 1; j < allSegments.length; j++) {
202
+ const a = allSegments[i];
203
+ const b = allSegments[j];
204
+ if (a.edgeId === b.edgeId) continue;
205
+ const pt = segmentIntersection(a.from, a.to, b.from, b.to);
206
+ if (pt) points.push(pt);
207
+ }
208
+ return points;
209
+ }
210
+ function between(v, a, b, pad = 1) {
211
+ const lo = Math.min(a, b) + pad;
212
+ const hi = Math.max(a, b) - pad;
213
+ return v > lo && v < hi;
214
+ }
215
+ function paramOnSegment(a, b, p) {
216
+ const dx = b.x - a.x;
217
+ const dy = b.y - a.y;
218
+ const len2 = dx * dx + dy * dy;
219
+ if (len2 < 1e-6) return 0;
220
+ return ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
221
+ }
222
+ function lerp(a, b, t) {
223
+ return {
224
+ x: a.x + (b.x - a.x) * t,
225
+ y: a.y + (b.y - a.y) * t
226
+ };
227
+ }
228
+ /** Shorten first/last line segments so strokes and arrowheads stop before node fills. */
229
+ function trimEdgeEndpoints(edges, options = {}) {
230
+ const sourceInsetOpt = options.sourceInset ?? 1;
231
+ const targetInsetOpt = options.targetInset ?? 1;
232
+ return edges.map((edge) => {
233
+ const sourceInset = typeof sourceInsetOpt === "function" ? sourceInsetOpt(edge.edgeId) : sourceInsetOpt;
234
+ const targetInset = typeof targetInsetOpt === "function" ? targetInsetOpt(edge.edgeId) : targetInsetOpt;
235
+ if (sourceInset <= 0 && targetInset <= 0) return edge;
236
+ const segments = edge.segments.map((s) => s.type === "line" ? {
237
+ ...s,
238
+ from: { ...s.from },
239
+ to: { ...s.to }
240
+ } : s);
241
+ const firstLine = segments.findIndex((s) => s.type === "line");
242
+ let lastLine = -1;
243
+ for (let i = segments.length - 1; i >= 0; i--) if (segments[i].type === "line") {
244
+ lastLine = i;
245
+ break;
246
+ }
247
+ if (firstLine < 0) return {
248
+ edgeId: edge.edgeId,
249
+ segments
250
+ };
251
+ if (sourceInset > 0) {
252
+ const seg = segments[firstLine];
253
+ if (seg.type === "line") seg.from = insetFromEnd(seg.to, seg.from, sourceInset);
254
+ }
255
+ if (targetInset > 0 && lastLine >= 0) {
256
+ const seg = segments[lastLine];
257
+ if (seg.type === "line") seg.to = insetFromEnd(seg.from, seg.to, targetInset);
258
+ }
259
+ return {
260
+ edgeId: edge.edgeId,
261
+ segments
262
+ };
263
+ });
264
+ }
265
+ /** Move `end` toward `start` by up to `inset` px, capped to keep a visible stub. */
266
+ function insetFromEnd(start, end, inset) {
267
+ const dx = end.x - start.x;
268
+ const dy = end.y - start.y;
269
+ const len = Math.hypot(dx, dy);
270
+ if (len < 1e-6) return end;
271
+ const effectiveInset = Math.min(inset, len * .45);
272
+ return {
273
+ x: end.x - dx / len * effectiveInset,
274
+ y: end.y - dy / len * effectiveInset
275
+ };
276
+ }
277
+ //#endregion
278
+ //#region src/labels/types.ts
279
+ /** Icon size inside an edge-label pill (logical units). Keep in sync with SVG paint. */
280
+ const EDGE_LABEL_ICON = 12;
281
+ /** Gap between edge-label icon and text. */
282
+ const EDGE_LABEL_ICON_GAP = 4;
283
+ /** Horizontal padding inside the edge-label pill. */
284
+ const EDGE_LABEL_PAD_X = 8;
285
+ //#endregion
286
+ export { ARROW_ENDPOINT_INSET, CARDINALITY_ENDPOINT_INSET, EDGE_ENDPOINT_INSET, EDGE_LABEL_ICON, EDGE_LABEL_ICON_GAP, EDGE_LABEL_PAD_X, applyCrossingTreatment, attachPointOnPerimeter, detectCrossingPoints, trimEdgeEndpoints };
287
+
288
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/crossings/treat.ts","../src/labels/types.ts"],"sourcesContent":["import type { Point } from \"@kekonic/diagrams-core\";\nimport type { CrossingMode } from \"@kekonic/diagrams-core\";\nimport type { RoutedEdge } from \"../routing/types.ts\";\n\n/** Arrow triangle in marker space: base at x=1.5, tip at x=9. */\nexport const ARROW_MARKER_BASE_X = 1.5;\nexport const ARROW_MARKER_TIP_X = 9;\n/** SVG marker refX — path end sits on the arrow *base* so the stroke does not run under the fill. */\nexport const ARROW_MARKER_REF_X = ARROW_MARKER_BASE_X;\n/** Tip extends past the path end (toward the node) by this much. */\nexport const ARROW_MARKER_TIP_OVERHANG = ARROW_MARKER_TIP_X - ARROW_MARKER_BASE_X;\n/**\n * How far to pull the path endpoint back from the node border.\n * Path end = arrow base; tip overhangs to (approximately) the attach point.\n */\nexport const ARROW_NODE_GAP = 1.5;\n/** Target trim: tip lands just shy of the node stroke; dash/solid stop at the arrow base. */\nexport const ARROW_ENDPOINT_INSET = ARROW_MARKER_TIP_OVERHANG + ARROW_NODE_GAP;\n/**\n * Crow's-foot tip sits on the path endpoint (marker refX), unlike arrows whose tip\n * overhangs past refX. Keep only a hairline gap so the tip kisses the table stroke.\n */\nexport const CARDINALITY_ENDPOINT_INSET = ARROW_NODE_GAP;\n/** Small inset for edges without arrowheads — clears node stroke at connection. */\nexport const EDGE_ENDPOINT_INSET = 1;\n/** Ignore crossings this close to a segment end — gaps there orphan markers on stubs. */\nconst CROSSING_ENDPOINT_CLEARANCE = 28;\n/** Crossings within this distance form a dense cluster (prefer gaps in smart mode). */\nconst SMART_CLUSTER_PX = 48;\n/** Prefer gaps when a crossing sits this close to a polyline bend. */\nconst SMART_CORNER_CLEARANCE = 24;\n\nexport type RenderedEdgeSegment =\n | { type: \"line\"; from: Point; to: Point }\n | { type: \"gap\"; from: Point; to: Point }\n | { type: \"jump\"; from: Point; to: Point; radius: number };\n\nexport type TreatedEdge = {\n edgeId: string;\n segments: RenderedEdgeSegment[];\n};\n\ntype CrossingHit = {\n hop: { edgeId: string; idx: number; from: Point; to: Point };\n under: { edgeId: string; idx: number; from: Point; to: Point };\n point: Point;\n};\n\nfunction dist(a: Point, b: Point): number {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n\n/** Mark crossings that sit in a dense local cluster (≥2 within SMART_CLUSTER_PX). */\nfunction denseCrossingIndexes(crossings: CrossingHit[]): Set<number> {\n const dense = new Set<number>();\n for (let i = 0; i < crossings.length; i++) {\n let neighbors = 0;\n for (let j = 0; j < crossings.length; j++) {\n if (i === j) continue;\n if (dist(crossings[i]!.point, crossings[j]!.point) <= SMART_CLUSTER_PX) neighbors++;\n }\n if (neighbors >= 1) dense.add(i);\n }\n return dense;\n}\n\nfunction nearPolylineCorner(edges: RoutedEdge[], edgeId: string, point: Point): boolean {\n const edge = edges.find((e) => e.edgeId === edgeId);\n if (!edge || edge.points.length < 3) return false;\n // Intermediate points are bends; endpoints are already cleared separately.\n for (let i = 1; i < edge.points.length - 1; i++) {\n if (dist(edge.points[i]!, point) <= SMART_CORNER_CLEARANCE) return true;\n }\n return false;\n}\n\n/**\n * Decide gap vs jump for smart mode (§14.3):\n * - isolated orthogonal crossings → jumps\n * - dense clusters → gaps\n * - crossings near corners → gaps (jumps look noisy on bends)\n */\nfunction smartUsesJump(\n crossing: CrossingHit,\n index: number,\n dense: Set<number>,\n edges: RoutedEdge[],\n): boolean {\n if (dense.has(index)) return false;\n if (nearPolylineCorner(edges, crossing.hop.edgeId, crossing.point)) return false;\n if (nearPolylineCorner(edges, crossing.under.edgeId, crossing.point)) return false;\n return true;\n}\n\nexport function applyCrossingTreatment(edges: RoutedEdge[], mode: CrossingMode): TreatedEdge[] {\n if (mode === \"none\") {\n return edges.map((e) => ({\n edgeId: e.edgeId,\n segments: e.segments.map((s) => ({ type: \"line\" as const, from: s.from, to: s.to })),\n }));\n }\n\n const allSegments: Array<{ edgeId: string; idx: number; from: Point; to: Point }> = [];\n for (const edge of edges) {\n edge.segments.forEach((s, idx) =>\n allSegments.push({ edgeId: edge.edgeId, idx, from: s.from, to: s.to }),\n );\n }\n\n const crossings: CrossingHit[] = [];\n for (let i = 0; i < allSegments.length; i++) {\n for (let j = i + 1; j < allSegments.length; j++) {\n const a = allSegments[i]!;\n const b = allSegments[j]!;\n if (a.edgeId === b.edgeId) continue;\n const pt = segmentIntersection(a.from, a.to, b.from, b.to);\n if (!pt) continue;\n // Hop the more horizontal segment (classic metro/schematic bridge look).\n const aHoriz = Math.abs(a.from.y - a.to.y) <= Math.abs(a.from.x - a.to.x);\n const hop = aHoriz ? a : b;\n const under = hop === a ? b : a;\n crossings.push({ hop, under, point: pt });\n }\n }\n\n const dense = mode === \"smart\" ? denseCrossingIndexes(crossings) : new Set<number>();\n\n /** Per hop-segment key → list of {point, useJump} */\n const treatmentsAt = new Map<string, Array<{ point: Point; useJump: boolean }>>();\n crossings.forEach((c, index) => {\n const key = `${c.hop.edgeId}:${c.hop.idx}`;\n if (!treatmentsAt.has(key)) treatmentsAt.set(key, []);\n const useJump =\n mode === \"jumps\" ? true : mode === \"gaps\" ? false : smartUsesJump(c, index, dense, edges);\n treatmentsAt.get(key)!.push({ point: c.point, useJump });\n });\n\n const jumpRadius = 10;\n\n return edges.map((edge) => {\n const segments: RenderedEdgeSegment[] = [];\n edge.segments.forEach((s, idx) => {\n const key = `${edge.edgeId}:${idx}`;\n const points = treatmentsAt.get(key) ?? [];\n if (!points.length) {\n segments.push({ type: \"line\", from: s.from, to: s.to });\n return;\n }\n const segLen = Math.hypot(s.to.x - s.from.x, s.to.y - s.from.y) || 1;\n const sorted = points\n .map((p) => ({ ...p, t: paramOnSegment(s.from, s.to, p.point) }))\n .filter((x) => {\n const fromStart = x.t * segLen;\n const fromEnd = (1 - x.t) * segLen;\n return fromStart > CROSSING_ENDPOINT_CLEARANCE && fromEnd > CROSSING_ENDPOINT_CLEARANCE;\n })\n .sort((a, b) => a.t - b.t);\n\n let prev = s.from;\n for (const { point: p, useJump } of sorted) {\n const t = paramOnSegment(s.from, s.to, p);\n const half = Math.min(\n 0.04,\n Math.max(0.015, 8 / (Math.hypot(s.to.x - s.from.x, s.to.y - s.from.y) || 1)),\n );\n const gapStart = lerp(s.from, s.to, Math.max(0, t - half));\n const gapEnd = lerp(s.from, s.to, Math.min(1, t + half));\n segments.push({ type: \"line\", from: prev, to: gapStart });\n if (useJump) {\n segments.push({ type: \"jump\", from: gapStart, to: gapEnd, radius: jumpRadius });\n } else {\n segments.push({ type: \"gap\", from: gapStart, to: gapEnd });\n }\n prev = gapEnd;\n }\n segments.push({ type: \"line\", from: prev, to: s.to });\n });\n return { edgeId: edge.edgeId, segments };\n });\n}\n\nfunction segmentIntersection(a1: Point, a2: Point, b1: Point, b2: Point): Point | null {\n // Prefer orthogonal H∩V detection — common for ELK routes and tolerant of float noise.\n const eps = 0.75;\n const aH = Math.abs(a1.y - a2.y) < eps;\n const aV = Math.abs(a1.x - a2.x) < eps;\n const bH = Math.abs(b1.y - b2.y) < eps;\n const bV = Math.abs(b1.x - b2.x) < eps;\n\n if (aH && bV) {\n const y = (a1.y + a2.y) / 2;\n const x = (b1.x + b2.x) / 2;\n if (between(x, a1.x, a2.x) && between(y, b1.y, b2.y)) return { x, y };\n return null;\n }\n if (aV && bH) {\n const x = (a1.x + a2.x) / 2;\n const y = (b1.y + b2.y) / 2;\n if (between(y, a1.y, a2.y) && between(x, b1.x, b2.x)) return { x, y };\n return null;\n }\n\n const d = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x);\n if (Math.abs(d) < 1e-6) return null;\n const t = ((b1.x - a1.x) * (b2.y - b1.y) - (b1.y - a1.y) * (b2.x - b1.x)) / d;\n const u = ((b1.x - a1.x) * (a2.y - a1.y) - (b1.y - a1.y) * (a2.x - a1.x)) / d;\n if (t > 0.02 && t < 0.98 && u > 0.02 && u < 0.98) {\n return { x: a1.x + t * (a2.x - a1.x), y: a1.y + t * (a2.y - a1.y) };\n }\n return null;\n}\n\n/** Crossing points between distinct edge segments (for label scoring). */\nexport function detectCrossingPoints(edges: RoutedEdge[]): Point[] {\n const allSegments: Array<{ edgeId: string; from: Point; to: Point }> = [];\n for (const edge of edges) {\n for (const s of edge.segments) {\n allSegments.push({ edgeId: edge.edgeId, from: s.from, to: s.to });\n }\n }\n const points: Point[] = [];\n for (let i = 0; i < allSegments.length; i++) {\n for (let j = i + 1; j < allSegments.length; j++) {\n const a = allSegments[i]!;\n const b = allSegments[j]!;\n if (a.edgeId === b.edgeId) continue;\n const pt = segmentIntersection(a.from, a.to, b.from, b.to);\n if (pt) points.push(pt);\n }\n }\n return points;\n}\n\nfunction between(v: number, a: number, b: number, pad = 1): boolean {\n const lo = Math.min(a, b) + pad;\n const hi = Math.max(a, b) - pad;\n return v > lo && v < hi;\n}\n\nfunction paramOnSegment(a: Point, b: Point, p: Point): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len2 = dx * dx + dy * dy;\n if (len2 < 1e-6) return 0;\n return ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;\n}\n\nfunction lerp(a: Point, b: Point, t: number): Point {\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\nexport type TrimEdgeEndpointsOptions = {\n /** Uniform source inset, or per-edge via callback. */\n sourceInset?: number | ((edgeId: string) => number);\n /** Uniform target inset, or per-edge via callback. */\n targetInset?: number | ((edgeId: string) => number);\n};\n\n/** Shorten first/last line segments so strokes and arrowheads stop before node fills. */\nexport function trimEdgeEndpoints(\n edges: TreatedEdge[],\n options: TrimEdgeEndpointsOptions = {},\n): TreatedEdge[] {\n const sourceInsetOpt = options.sourceInset ?? EDGE_ENDPOINT_INSET;\n const targetInsetOpt = options.targetInset ?? EDGE_ENDPOINT_INSET;\n\n return edges.map((edge) => {\n const sourceInset =\n typeof sourceInsetOpt === \"function\" ? sourceInsetOpt(edge.edgeId) : sourceInsetOpt;\n const targetInset =\n typeof targetInsetOpt === \"function\" ? targetInsetOpt(edge.edgeId) : targetInsetOpt;\n if (sourceInset <= 0 && targetInset <= 0) return edge;\n\n const segments = edge.segments.map((s) =>\n s.type === \"line\" ? { ...s, from: { ...s.from }, to: { ...s.to } } : s,\n );\n\n const firstLine = segments.findIndex((s) => s.type === \"line\");\n let lastLine = -1;\n for (let i = segments.length - 1; i >= 0; i--) {\n if (segments[i]!.type === \"line\") {\n lastLine = i;\n break;\n }\n }\n if (firstLine < 0) return { edgeId: edge.edgeId, segments };\n\n if (sourceInset > 0) {\n const seg = segments[firstLine]!;\n if (seg.type === \"line\") {\n seg.from = insetFromEnd(seg.to, seg.from, sourceInset);\n }\n }\n\n if (targetInset > 0 && lastLine >= 0) {\n const seg = segments[lastLine]!;\n if (seg.type === \"line\") {\n seg.to = insetFromEnd(seg.from, seg.to, targetInset);\n }\n }\n\n return { edgeId: edge.edgeId, segments };\n });\n}\n\n/** Move `end` toward `start` by up to `inset` px, capped to keep a visible stub. */\nfunction insetFromEnd(start: Point, end: Point, inset: number): Point {\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) return end;\n const effectiveInset = Math.min(inset, len * 0.45);\n return {\n x: end.x - (dx / len) * effectiveInset,\n y: end.y - (dy / len) * effectiveInset,\n };\n}\n","import type { Point, Rect } from \"@kekonic/diagrams-core\";\n\n/** Edge label bounds produced by ELK and painted by the SVG renderer. */\nexport type EdgeLabelPlacement = {\n edgeId: string;\n text: string;\n bounds: Rect;\n anchor: Point;\n};\n\n/** Icon size inside an edge-label pill (logical units). Keep in sync with SVG paint. */\nexport const EDGE_LABEL_ICON = 12;\n/** Gap between edge-label icon and text. */\nexport const EDGE_LABEL_ICON_GAP = 4;\n/** Horizontal padding inside the edge-label pill. */\nexport const EDGE_LABEL_PAD_X = 8;\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@kekonic/diagrams-routing",
3
+ "version": "1.0.0-rc.4",
4
+ "description": "KDiagram edge finalization, crossing treatment, and perimeter attachment.",
5
+ "homepage": "https://github.com/kekonic/diagrams#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/kekonic/diagrams/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/kekonic/diagrams.git",
13
+ "directory": "packages/diagrams-routing"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.mts",
22
+ "import": "./dist/index.mjs"
23
+ }
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@kekonic/diagrams-core": "1.0.0-rc.4",
30
+ "@kekonic/diagrams-geometry": "1.0.0-rc.4",
31
+ "@kekonic/diagrams-layout": "1.0.0-rc.4"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^26",
35
+ "typescript": "^5",
36
+ "vitest": "^4"
37
+ },
38
+ "engines": {
39
+ "node": ">=22.18.0"
40
+ },
41
+ "scripts": {
42
+ "build": "vp pack",
43
+ "test": "vp test"
44
+ }
45
+ }