@statelyai/layout 0.3.0 → 0.4.0
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/NOTICE.md +4 -2
- package/README.md +150 -11
- package/dist/elkjs/index.cjs +2 -2
- package/dist/elkjs/index.d.cts +2 -0
- package/dist/elkjs/index.d.mts +2 -0
- package/dist/elkjs/index.mjs +2 -2
- package/dist/elkjs/main.cjs +1 -1
- package/dist/elkjs/main.mjs +1 -1
- package/dist/elkjs/worker.cjs +2 -2
- package/dist/elkjs/worker.mjs +2 -2
- package/dist/elkjs-UciYx7lD.mjs +2973 -0
- package/dist/elkjs-ggjsBuVP.cjs +3002 -0
- package/dist/{index-BEA_pzeT.d.cts → index-B6cWNrMw.d.cts} +9 -1
- package/dist/{index-DNTLRsDl.d.mts → index-CJQLUKD9.d.mts} +9 -1
- package/dist/{index-DNixtE3h.d.mts → index-DT6lPMfS.d.cts} +85 -5
- package/dist/{index-D9dhxrRl.d.cts → index-F1U-fCTu.d.mts} +85 -5
- package/dist/index.cjs +6 -3
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +4 -4
- package/dist/layered/index.cjs +3 -1
- package/dist/layered/index.d.cts +2 -2
- package/dist/layered/index.d.mts +2 -2
- package/dist/layered/index.mjs +2 -2
- package/dist/{layered-DGFTeQSl.mjs → layered-C7ERzZjR.mjs} +9605 -1655
- package/dist/{layered-B2buJqMf.cjs → layered-CBPg6_6_.cjs} +12481 -4435
- package/dist/{layout-engine-Dn1PiFwP.cjs → layout-engine-DAw06RhN.cjs} +5 -18
- package/dist/{layout-engine-Cs5tatA6.mjs → layout-engine-DC3IYkIy.mjs} +3 -10
- package/dist/routing/index.cjs +2 -1
- package/dist/routing/index.d.cts +2 -2
- package/dist/routing/index.d.mts +2 -2
- package/dist/routing/index.mjs +2 -2
- package/dist/{routing-A2hrK9MP.mjs → routing-CR8flv8b.mjs} +977 -593
- package/dist/{routing-ujEP7Qh6.cjs → routing-QIdGGFBe.cjs} +986 -596
- package/package.json +8 -2
- package/src/authoring/coordinates.ts +28 -8
- package/src/authoring/partial.ts +7 -2
- package/src/authoring/routing.ts +4 -1
- package/src/constraints.ts +174 -6
- package/src/elkjs/index.ts +1392 -449
- package/src/elkjs/layout-quality.ts +219 -0
- package/src/elkjs/opposing-tracks.ts +161 -0
- package/src/elkjs/orthogonal-junctions.ts +174 -0
- package/src/elkjs/statechart.ts +9 -3
- package/src/elkjs/types.ts +2 -0
- package/src/index.ts +2 -0
- package/src/internal/elkjs-compatibility.ts +4 -1
- package/src/internal/layered-trace.ts +48 -0
- package/src/internal/layout-engine.ts +2 -1
- package/src/java-random.ts +6 -0
- package/src/layered/bk-node-placement.ts +403 -92
- package/src/layered/center-labels.ts +398 -0
- package/src/layered/compaction-bounds.ts +26 -0
- package/src/layered/compaction-errors.ts +2 -0
- package/src/layered/compaction-scanline.ts +63 -0
- package/src/layered/compound-boundaries.ts +99 -0
- package/src/layered/compound.ts +35 -7
- package/src/layered/constraint-orientation.ts +141 -0
- package/src/layered/crossing-constraints.ts +243 -0
- package/src/layered/crossing-counter.ts +221 -0
- package/src/layered/crossing-graph.ts +401 -0
- package/src/layered/cycle-random.ts +22 -0
- package/src/layered/elk-port-lists.ts +243 -0
- package/src/layered/end-labels.ts +124 -0
- package/src/layered/external-port-dummy.ts +137 -0
- package/src/layered/feedback-chains.ts +114 -0
- package/src/layered/fixed-self-loop.ts +150 -0
- package/src/layered/greedy-switch-decider.ts +356 -0
- package/src/layered/grouped-compaction.ts +787 -0
- package/src/layered/gwt-sort.ts +53 -0
- package/src/layered/hierarchical-port-phases.ts +242 -0
- package/src/layered/hierarchical-port-restoration.ts +233 -0
- package/src/layered/hierarchical-port-routing.ts +179 -0
- package/src/layered/hierarchy-crossing.ts +121 -0
- package/src/layered/hierarchy-sweepiness.ts +83 -0
- package/src/layered/hints.ts +371 -0
- package/src/layered/hyperedge-dummy-merger.ts +195 -0
- package/src/layered/index.ts +886 -161
- package/src/layered/initial-model-order.ts +404 -0
- package/src/layered/inverted-ports.ts +207 -0
- package/src/layered/label-dummy-switcher.ts +200 -0
- package/src/layered/long-edges.ts +135 -44
- package/src/layered/loop-envelopes.ts +184 -0
- package/src/layered/native-routing.ts +275 -0
- package/src/layered/network-simplex.ts +11 -15
- package/src/layered/node-margins.ts +59 -0
- package/src/layered/north-south-ports.ts +271 -0
- package/src/layered/opposing-tracks.ts +944 -0
- package/src/layered/orthogonal-cycle-order.ts +106 -0
- package/src/layered/orthogonal-hypersegments.ts +92 -0
- package/src/layered/orthogonal-junctions.ts +202 -0
- package/src/layered/orthogonal-segments.ts +243 -0
- package/src/layered/placement-orientation.ts +22 -0
- package/src/layered/port-distributor.ts +192 -0
- package/src/layered/port-edge-order.ts +20 -0
- package/src/layered/replace-routing.ts +45 -0
- package/src/layered/routing-coordinates.ts +32 -0
- package/src/layered/separate-exterior-labels.ts +24 -6
- package/src/layered/spacing.ts +34 -12
- package/src/layered/splitter-order.ts +106 -0
- package/src/layered/strategies.ts +2024 -883
- package/src/layered/types.ts +13 -0
- package/src/layered/weighted-compaction.ts +84 -0
- package/src/routing/adapters.ts +32 -0
- package/src/routing/algorithms.ts +433 -21
- package/src/routing/coordination.ts +8 -3
- package/src/routing/index.ts +1 -0
- package/src/routing/search.ts +93 -11
- package/dist/elkjs-DaHMISnQ.mjs +0 -1677
- package/dist/elkjs-dMghiiWW.cjs +0 -1706
package/src/routing/search.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface SearchContext {
|
|
|
8
8
|
edgeCost?(a: RoutePoint, b: RoutePoint): number;
|
|
9
9
|
guides?(bounds: RouteBounds): readonly RouteBounds[];
|
|
10
10
|
readonly maxSearchNodes: number;
|
|
11
|
+
readonly maxGridNodes?: number;
|
|
11
12
|
readonly bendPenalty: number;
|
|
12
13
|
visited: number;
|
|
13
14
|
budgetExceeded: boolean;
|
|
@@ -82,6 +83,18 @@ export function findPath(
|
|
|
82
83
|
context: SearchContext,
|
|
83
84
|
terminals: { incoming?: RoutePoint; outgoing?: RoutePoint } = {},
|
|
84
85
|
): RoutePoint[] | undefined {
|
|
86
|
+
const maxGridNodes = context.maxGridNodes ?? context.maxSearchNodes;
|
|
87
|
+
// Match the admissible lower bound to the route geometry. Euclidean distance
|
|
88
|
+
// badly underestimates long orthogonal paths and exhausts the search budget.
|
|
89
|
+
const remainingDistance = (point: RoutePoint) => {
|
|
90
|
+
const dx = Math.abs(point.x - end.x),
|
|
91
|
+
dy = Math.abs(point.y - end.y);
|
|
92
|
+
return style === "orthogonal"
|
|
93
|
+
? dx + dy
|
|
94
|
+
: style === "octilinear"
|
|
95
|
+
? Math.max(dx, dy) + (Math.SQRT2 - 1) * Math.min(dx, dy)
|
|
96
|
+
: distance(point, end);
|
|
97
|
+
};
|
|
85
98
|
const turnCost = (a: RoutePoint | undefined, b: RoutePoint | undefined) => {
|
|
86
99
|
if (!a || !b || !Math.hypot(a.x, a.y) || !Math.hypot(b.x, b.y)) return 0;
|
|
87
100
|
const dot = a.x * b.x + a.y * b.y,
|
|
@@ -134,13 +147,34 @@ export function findPath(
|
|
|
134
147
|
points = [start, end, ...corners];
|
|
135
148
|
neighbors = () => points.map((_, i) => i);
|
|
136
149
|
} else {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
// Fractional compound offsets can produce almost-identical grid tracks.
|
|
151
|
+
// Zero-length steps between them bypass terminal direction constraints.
|
|
152
|
+
const coordinates = (values: number[]) => {
|
|
153
|
+
const endpoints = values.slice(0, 2);
|
|
154
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
155
|
+
const unique: number[] = [];
|
|
156
|
+
for (const value of sorted) {
|
|
157
|
+
if (unique.length && Math.abs(value - unique.at(-1)!) < 1e-8) continue;
|
|
158
|
+
unique.push(value);
|
|
159
|
+
}
|
|
160
|
+
// Preserve exact terminal values so reconstructed leads stay aligned.
|
|
161
|
+
return unique.map((value) => endpoints.find((p) => Math.abs(p - value) < 1e-8) ?? value);
|
|
162
|
+
};
|
|
163
|
+
const xs = coordinates([
|
|
164
|
+
start.x,
|
|
165
|
+
end.x,
|
|
166
|
+
area.x,
|
|
167
|
+
area.x + area.width,
|
|
168
|
+
...corners.map((p) => p.x),
|
|
169
|
+
]);
|
|
170
|
+
const ys = coordinates([
|
|
171
|
+
start.y,
|
|
172
|
+
end.y,
|
|
173
|
+
area.y,
|
|
174
|
+
area.y + area.height,
|
|
175
|
+
...corners.map((p) => p.y),
|
|
176
|
+
]);
|
|
177
|
+
if (xs.length * ys.length > maxGridNodes) {
|
|
144
178
|
context.budgetExceeded = true;
|
|
145
179
|
return undefined;
|
|
146
180
|
}
|
|
@@ -164,7 +198,7 @@ export function findPath(
|
|
|
164
198
|
const unique = new Map(points.map((p) => [`${p.x}:${p.y}`, p]));
|
|
165
199
|
for (const p of extra) unique.set(`${p.x}:${p.y}`, p);
|
|
166
200
|
points = [...unique.values()];
|
|
167
|
-
if (points.length >
|
|
201
|
+
if (points.length > maxGridNodes) {
|
|
168
202
|
context.budgetExceeded = true;
|
|
169
203
|
return undefined;
|
|
170
204
|
}
|
|
@@ -200,7 +234,7 @@ export function findPath(
|
|
|
200
234
|
i + width < points.length ? i + width : -1,
|
|
201
235
|
].filter((v) => v >= 0);
|
|
202
236
|
}
|
|
203
|
-
if (points.length >
|
|
237
|
+
if (points.length > maxGridNodes) {
|
|
204
238
|
context.budgetExceeded = true;
|
|
205
239
|
return undefined;
|
|
206
240
|
}
|
|
@@ -224,7 +258,7 @@ export function findPath(
|
|
|
224
258
|
const first = `${from}:-1`;
|
|
225
259
|
costs.set(first, 0);
|
|
226
260
|
vertices.set(first, from);
|
|
227
|
-
queue.push({ index: from, previous: -1, key: first, cost: 0 },
|
|
261
|
+
queue.push({ index: from, previous: -1, key: first, cost: 0 }, remainingDistance(start));
|
|
228
262
|
while (queue.items.length && context.visited < context.maxSearchNodes) {
|
|
229
263
|
const current = queue.pop();
|
|
230
264
|
if (costs.get(current.key) !== current.cost) continue;
|
|
@@ -246,6 +280,14 @@ export function findPath(
|
|
|
246
280
|
const before =
|
|
247
281
|
current.previous < 0 ? terminals.incoming : delta(points[current.previous]!, a);
|
|
248
282
|
const step = delta(a, b);
|
|
283
|
+
const reverses = (incoming: RoutePoint | undefined, outgoing: RoutePoint | undefined) =>
|
|
284
|
+
incoming &&
|
|
285
|
+
outgoing &&
|
|
286
|
+
Math.abs(incoming.x * outgoing.y - incoming.y * outgoing.x) < 1e-8 &&
|
|
287
|
+
incoming.x * outgoing.x + incoming.y * outgoing.y < -1e-8;
|
|
288
|
+
// Immediate reversal retraces the preceding segment. It is never a
|
|
289
|
+
// valid bend, including inside the grid or at a terminal lead.
|
|
290
|
+
if (reverses(before, step) || (next === to && reverses(step, terminals.outgoing))) continue;
|
|
249
291
|
const bend =
|
|
250
292
|
turnCost(before, step) + (next === to ? turnCost(step, terminals.outgoing) : 0);
|
|
251
293
|
const pair = Math.min(current.index, next) * points.length + Math.max(current.index, next);
|
|
@@ -260,7 +302,10 @@ export function findPath(
|
|
|
260
302
|
costs.set(key, cost);
|
|
261
303
|
parents.set(key, current.key);
|
|
262
304
|
vertices.set(key, next);
|
|
263
|
-
queue.push(
|
|
305
|
+
queue.push(
|
|
306
|
+
{ index: next, previous: current.index, key, cost },
|
|
307
|
+
cost + remainingDistance(b),
|
|
308
|
+
);
|
|
264
309
|
}
|
|
265
310
|
}
|
|
266
311
|
}
|
|
@@ -274,3 +319,40 @@ export function pointsBounds(points: readonly RoutePoint[]): RouteBounds {
|
|
|
274
319
|
height: 0,
|
|
275
320
|
});
|
|
276
321
|
}
|
|
322
|
+
|
|
323
|
+
// Search must not consume the terminal leads that are appended afterwards.
|
|
324
|
+
// A distant excursion can retrace a lead even without an immediate reversal.
|
|
325
|
+
export const findPathBetweenLeads = (
|
|
326
|
+
start: RoutePoint,
|
|
327
|
+
end: RoutePoint,
|
|
328
|
+
style: "orthogonal" | "polyline" | "octilinear",
|
|
329
|
+
ctx: SearchContext,
|
|
330
|
+
leads: readonly [RoutePoint, RoutePoint][],
|
|
331
|
+
directions: { incoming?: RoutePoint; outgoing?: RoutePoint } = {},
|
|
332
|
+
) => {
|
|
333
|
+
const protectedContext = {
|
|
334
|
+
...ctx,
|
|
335
|
+
edgeCost: (p: RoutePoint, q: RoutePoint) => {
|
|
336
|
+
for (const [from, to] of leads) {
|
|
337
|
+
const dx = q.x - p.x,
|
|
338
|
+
dy = q.y - p.y;
|
|
339
|
+
const length = Math.hypot(dx, dy);
|
|
340
|
+
if (length < 1e-8) continue;
|
|
341
|
+
if (
|
|
342
|
+
Math.abs(dx * (to.y - from.y) - dy * (to.x - from.x)) > 1e-8 * length ||
|
|
343
|
+
Math.abs(dx * (from.y - p.y) - dy * (from.x - p.x)) > 1e-8 * length
|
|
344
|
+
)
|
|
345
|
+
continue;
|
|
346
|
+
const lo = ((from.x - p.x) * dx + (from.y - p.y) * dy) / length;
|
|
347
|
+
const hi = ((to.x - p.x) * dx + (to.y - p.y) * dy) / length;
|
|
348
|
+
if (Math.min(length, Math.max(lo, hi)) - Math.max(0, Math.min(lo, hi)) > 1e-8)
|
|
349
|
+
return Infinity;
|
|
350
|
+
}
|
|
351
|
+
return ctx.edgeCost?.(p, q) ?? 0;
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
const result = findPath(start, end, style, protectedContext, directions);
|
|
355
|
+
ctx.visited = protectedContext.visited;
|
|
356
|
+
ctx.budgetExceeded = protectedContext.budgetExceeded;
|
|
357
|
+
return result;
|
|
358
|
+
};
|