@statelyai/layout 0.2.0 → 0.3.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/README.md +71 -2
- package/dist/elkjs/index.cjs +3 -2
- package/dist/elkjs/index.d.cts +11 -2
- package/dist/elkjs/index.d.mts +11 -2
- package/dist/elkjs/index.mjs +3 -3
- package/dist/elkjs/main.cjs +2 -2
- package/dist/elkjs/main.d.cts +2 -2
- package/dist/elkjs/main.d.mts +2 -2
- package/dist/elkjs/main.mjs +2 -2
- package/dist/elkjs/worker-api.cjs +1 -1
- package/dist/elkjs/worker-api.d.cts +2 -2
- package/dist/elkjs/worker-api.d.mts +2 -2
- package/dist/elkjs/worker-api.mjs +1 -1
- package/dist/elkjs/worker.cjs +2 -2
- package/dist/elkjs/worker.mjs +2 -2
- package/dist/{elkjs-CFlfkEBz.mjs → elkjs-DaHMISnQ.mjs} +152 -3
- package/dist/{elkjs-BVnDwnBQ.cjs → elkjs-dMghiiWW.cjs} +157 -2
- package/dist/index-BEA_pzeT.d.cts +72 -0
- package/dist/{index-BSH2bloA.d.mts → index-D9dhxrRl.d.cts} +35 -3
- package/dist/index-DNTLRsDl.d.mts +72 -0
- package/dist/{index-CG712jzV.d.cts → index-DNixtE3h.d.mts} +35 -3
- package/dist/index.cjs +36 -9
- package/dist/index.d.cts +4 -3
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +4 -3
- package/dist/layered/index.cjs +1 -1
- package/dist/layered/index.d.cts +3 -2
- package/dist/layered/index.d.mts +3 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-Cf9Rn8l7.cjs → layered-B2buJqMf.cjs} +417 -469
- package/dist/{layered-Bz2nWmVp.mjs → layered-DGFTeQSl.mjs} +391 -431
- package/dist/{layout-engine-WbC0YjjT.mjs → layout-engine-Cs5tatA6.mjs} +1 -1
- package/dist/{layout-engine-BNex9jxg.cjs → layout-engine-Dn1PiFwP.cjs} +1 -1
- package/dist/routing/index.cjs +28 -0
- package/dist/routing/index.d.cts +3 -0
- package/dist/routing/index.d.mts +3 -0
- package/dist/routing/index.mjs +3 -0
- package/dist/routing-A2hrK9MP.mjs +2396 -0
- package/dist/routing-ujEP7Qh6.cjs +2606 -0
- package/dist/types-CT2akdK0.d.mts +133 -0
- package/dist/types-uxPlGRof.d.cts +133 -0
- package/dist/{worker-client-DAT_z2OR.d.mts → worker-client-BwGGRD5y.d.mts} +1 -1
- package/dist/{worker-client-DVhklF6A.d.cts → worker-client-D4zNyPEI.d.cts} +1 -1
- package/package.json +7 -2
- package/src/authoring/coordinates.ts +10 -2
- package/src/elkjs/index.ts +5 -3
- package/src/elkjs/routes.ts +147 -0
- package/src/index.ts +6 -0
- package/src/layered/compound.ts +417 -0
- package/src/layered/index.ts +27 -102
- package/src/layered/types.ts +21 -0
- package/src/routing/adapters.ts +111 -0
- package/src/routing/algorithms.ts +676 -0
- package/src/routing/coordination.ts +86 -0
- package/src/routing/engine.ts +473 -0
- package/src/routing/index.ts +68 -0
- package/src/routing/layout-cache.ts +65 -0
- package/src/routing/model.ts +218 -0
- package/src/routing/path.ts +324 -0
- package/src/routing/persistent.ts +117 -0
- package/src/routing/search.ts +276 -0
- package/src/routing/spatial.ts +78 -0
- package/src/routing/types.ts +146 -0
- /package/dist/{public-types-B8rAALJq.d.mts → public-types-9NCPoqbp.d.mts} +0 -0
- /package/dist/{public-types-CVB0hXGk.d.cts → public-types-BcRZoJE3.d.cts} +0 -0
- /package/dist/{worker-client-DkwS4sHx.mjs → worker-client-BNjwT1AJ.mjs} +0 -0
- /package/dist/{worker-client-D3HMK33r.cjs → worker-client-D4oEvhAl.cjs} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { GraphPatch, Point, VisualGraph } from "@statelyai/graph";
|
|
2
|
+
import { hasCurrentRouteGeometry } from "./layout-cache";
|
|
3
|
+
import { worldGeometry } from "../authoring/coordinates";
|
|
4
|
+
import { freeze } from "./model";
|
|
5
|
+
import { flattenPath, pathFromPoints } from "./path";
|
|
6
|
+
import { PersistentMap } from "./persistent";
|
|
7
|
+
import type { Route, RoutePath, RoutePoint, RouteSegment } from "./types";
|
|
8
|
+
|
|
9
|
+
/** ELK/native spline output: start, then repeating control1/control2/end triples. */
|
|
10
|
+
export function pathFromSplinePoints(points: readonly RoutePoint[]): RoutePath {
|
|
11
|
+
if (points.length < 4 || (points.length - 1) % 3 !== 0)
|
|
12
|
+
throw new RangeError("Cubic spline points require a start and control1/control2/end triples");
|
|
13
|
+
const segments: RouteSegment[] = [];
|
|
14
|
+
for (let i = 1; i < points.length; i += 3)
|
|
15
|
+
segments.push({
|
|
16
|
+
kind: "cubic",
|
|
17
|
+
control1: { ...points[i]! },
|
|
18
|
+
control2: { ...points[i + 1]! },
|
|
19
|
+
to: { ...points[i + 2]! },
|
|
20
|
+
});
|
|
21
|
+
return { start: { ...points[0]! }, segments };
|
|
22
|
+
}
|
|
23
|
+
/** Normalize routes from any native layout algorithm, without running layout again. */
|
|
24
|
+
export function getLayoutRoutes(graph: VisualGraph): ReadonlyMap<string, Route> {
|
|
25
|
+
const compound = graph as VisualGraph & { compoundRoutes?: ReadonlyMap<string, Route> };
|
|
26
|
+
if (compound.compoundRoutes?.size && hasCurrentRouteGeometry(graph, compound.compoundRoutes))
|
|
27
|
+
return compound.compoundRoutes;
|
|
28
|
+
const world = worldGeometry(graph),
|
|
29
|
+
nodes = new Map(world.nodes.map((n) => [n.id, n]));
|
|
30
|
+
let result = new PersistentMap<Route>();
|
|
31
|
+
for (const edge of world.edges) {
|
|
32
|
+
const points = edge.points;
|
|
33
|
+
let path: RoutePath | undefined;
|
|
34
|
+
if (points?.length && points.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) {
|
|
35
|
+
if (edge.routing === "splines") {
|
|
36
|
+
if (points.length >= 4 && (points.length - 1) % 3 === 0)
|
|
37
|
+
path = pathFromSplinePoints(points);
|
|
38
|
+
} else path = pathFromPoints(points);
|
|
39
|
+
}
|
|
40
|
+
const routed = path !== undefined;
|
|
41
|
+
if (!path) {
|
|
42
|
+
const a = nodes.get(edge.sourceId),
|
|
43
|
+
b = nodes.get(edge.targetId);
|
|
44
|
+
path = pathFromPoints([
|
|
45
|
+
{ x: a ? a.x + a.width : 0, y: a ? a.y + a.height / 2 : 0 },
|
|
46
|
+
{ x: b?.x ?? 40, y: b ? b.y + b.height / 2 : 40 },
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
result = result.set(
|
|
50
|
+
edge.id,
|
|
51
|
+
freeze({
|
|
52
|
+
edgeId: edge.id,
|
|
53
|
+
status: routed ? "routed" : "fallback",
|
|
54
|
+
diagnostics: routed
|
|
55
|
+
? []
|
|
56
|
+
: [
|
|
57
|
+
{
|
|
58
|
+
code: "MISSING_GEOMETRY",
|
|
59
|
+
edgeId: edge.id,
|
|
60
|
+
message: "Layout has no valid route; rendered a fallback connection",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
sections: [
|
|
64
|
+
{
|
|
65
|
+
id: `${edge.id}:0`,
|
|
66
|
+
from: {
|
|
67
|
+
kind: "node",
|
|
68
|
+
nodeId: edge.sourceId,
|
|
69
|
+
...(edge.sourcePort === undefined ? {} : { port: edge.sourcePort }),
|
|
70
|
+
},
|
|
71
|
+
to: {
|
|
72
|
+
kind: "node",
|
|
73
|
+
nodeId: edge.targetId,
|
|
74
|
+
...(edge.targetPort === undefined ? {} : { port: edge.targetPort }),
|
|
75
|
+
},
|
|
76
|
+
path,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/** One polyline per section preserves label gaps and branching for simple renderers. */
|
|
85
|
+
export function routeToPolylines(
|
|
86
|
+
route: Route,
|
|
87
|
+
options?: { readonly tolerance?: number },
|
|
88
|
+
): readonly (readonly RoutePoint[])[] {
|
|
89
|
+
return route.sections.map((s) => flattenPath(s.path, options));
|
|
90
|
+
}
|
|
91
|
+
/** Compatibility for single-section native consumers. Refuses to erase gaps or topology. */
|
|
92
|
+
export function routeToGraphPatch(
|
|
93
|
+
route: Route,
|
|
94
|
+
options: { readonly tolerance?: number; readonly offset?: RoutePoint } = {},
|
|
95
|
+
): GraphPatch {
|
|
96
|
+
if (
|
|
97
|
+
route.sections.length !== 1 ||
|
|
98
|
+
route.sections[0]!.sharedId ||
|
|
99
|
+
route.sections[0]!.from.kind !== "node" ||
|
|
100
|
+
route.sections[0]!.to.kind !== "node"
|
|
101
|
+
)
|
|
102
|
+
throw new Error(
|
|
103
|
+
"This route requires section-aware rendering; a single GraphEdge.points array would lose topology",
|
|
104
|
+
);
|
|
105
|
+
const offset = options.offset ?? { x: 0, y: 0 };
|
|
106
|
+
const points: Point[] = flattenPath(route.sections[0]!.path, options).map((p) => ({
|
|
107
|
+
x: p.x - offset.x,
|
|
108
|
+
y: p.y - offset.y,
|
|
109
|
+
}));
|
|
110
|
+
return { op: "updateEdge", id: route.edgeId, data: { points, routing: "polyline" } };
|
|
111
|
+
}
|