@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/layered/types.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { NativeRoutingStrategy, RoutingSettings } from "../routing/types";
|
|
1
2
|
import type { EntityRect, Graph, GraphEdge, GraphNode, GraphPort, Point } from "@statelyai/graph";
|
|
2
3
|
import type { LayoutConstraints } from "@statelyai/graph/layout";
|
|
4
|
+
import type { LayoutHint } from "./hints";
|
|
3
5
|
import type { LayoutDirection } from "../types";
|
|
4
6
|
import type { ElkLayeredOptionValueByName, LayeredAdvancedOptions } from "./elk-options";
|
|
5
7
|
|
|
@@ -21,6 +23,8 @@ export interface LayoutPadding {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface LayeredPhaseInput {
|
|
26
|
+
/** Authored edge order retained through label and long-edge expansion. */
|
|
27
|
+
modelOrderByEdgeId?: ReadonlyMap<string, number>;
|
|
24
28
|
graph: Graph<unknown, unknown, unknown, unknown>;
|
|
25
29
|
sizes: ReadonlyMap<string, NodeSize>;
|
|
26
30
|
direction: LayoutDirection;
|
|
@@ -38,12 +42,15 @@ export interface AcyclicOrientation {
|
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export interface LayerAssignment {
|
|
45
|
+
layerCount?: number;
|
|
41
46
|
layerByNodeId: ReadonlyMap<string, number>;
|
|
42
47
|
/** Layer-internal seed order produced by layerers whose insertion order is observable. */
|
|
43
48
|
seedOrder?: readonly string[];
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
export interface LayerOrder {
|
|
52
|
+
/** Canonical clockwise physical port identities, independent of edge reversal. */
|
|
53
|
+
physicalPortOrderByNodeId?: ReadonlyMap<string, readonly string[]>;
|
|
47
54
|
layers: readonly (readonly string[])[];
|
|
48
55
|
/** Internal ELK sweep state retained for exact port-aware placement. */
|
|
49
56
|
inputPortOrderByNodeId?: ReadonlyMap<string, readonly string[]>;
|
|
@@ -57,6 +64,8 @@ export interface NodePlacement {
|
|
|
57
64
|
|
|
58
65
|
export interface EdgeRoutes {
|
|
59
66
|
pointsByEdgeId: ReadonlyMap<string, readonly Point[]>;
|
|
67
|
+
/** Branch points generated on physical orthogonal hypersegments before restoration. */
|
|
68
|
+
junctionPointsByEdgeId?: ReadonlyMap<string, readonly Point[]>;
|
|
60
69
|
/** ELK spline segment NUB controls retained until long-edge joining. */
|
|
61
70
|
splineNubControlsByEdgeId?: ReadonlyMap<string, readonly Point[]>;
|
|
62
71
|
/** Reversed fixed-side routes that must stay outside the node envelope during compaction. */
|
|
@@ -112,12 +121,16 @@ export interface CompoundEdgeAttachment {
|
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
export interface LayeredLayoutOptions {
|
|
124
|
+
/** Replace initial routes after initial placement and routing, without moving geometry. */
|
|
125
|
+
routing?: { strategy: Pick<NativeRoutingStrategy, "route">; settings?: RoutingSettings };
|
|
115
126
|
direction?: LayoutDirection;
|
|
116
127
|
compound?: (node: GraphNode) => CompoundLayoutOptions | undefined;
|
|
117
128
|
edgeAttachment?: (edge: GraphEdge) => CompoundEdgeAttachment | undefined;
|
|
118
129
|
spacing?: Partial<LayeredSpacing>;
|
|
119
130
|
padding?: number | Partial<LayoutPadding>;
|
|
120
131
|
constraints?: LayoutConstraints;
|
|
132
|
+
/** Reader-facing preferences applied during layering, ordering and placement. */
|
|
133
|
+
hints?: readonly LayoutHint[];
|
|
121
134
|
measure?: (node: GraphNode) => NodeSize;
|
|
122
135
|
crossingSweeps?: number;
|
|
123
136
|
strategies?: LayeredStrategies;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Copyright (c) 2017 Kiel University and others.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from ELK v0.11.0 NetworkSimplexCompaction.java.
|
|
5
|
+
* Source commit: 54123e884b1ae743b453260f713b20c9bf5787f2
|
|
6
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
7
|
+
*******************************************************************************/
|
|
8
|
+
import { runNetworkSimplex, type SimplexEdge, type SimplexNode } from "./network-simplex";
|
|
9
|
+
import { InfeasibleCompactionError } from "./compaction-errors";
|
|
10
|
+
|
|
11
|
+
export interface CompactionConstraint {
|
|
12
|
+
source: string;
|
|
13
|
+
target: string;
|
|
14
|
+
delta: number;
|
|
15
|
+
weight: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Minimize weighted separation subject to integral lower-bound constraints. */
|
|
19
|
+
export function solveWeightedCompaction(
|
|
20
|
+
groupIds: readonly string[],
|
|
21
|
+
constraints: readonly CompactionConstraint[],
|
|
22
|
+
): Map<string, number> {
|
|
23
|
+
const nodes = groupIds.map((id, order): SimplexNode => ({
|
|
24
|
+
id,
|
|
25
|
+
order,
|
|
26
|
+
layer: 0,
|
|
27
|
+
incoming: [],
|
|
28
|
+
outgoing: [],
|
|
29
|
+
treeNode: false,
|
|
30
|
+
}));
|
|
31
|
+
if (nodes.length === 0) return new Map();
|
|
32
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
33
|
+
const edges: SimplexEdge[] = [];
|
|
34
|
+
const add = (source: SimplexNode, target: SimplexNode, delta: number, weight: number): void => {
|
|
35
|
+
const edge: SimplexEdge = {
|
|
36
|
+
id: `constraint:${edges.length}`,
|
|
37
|
+
order: edges.length,
|
|
38
|
+
source,
|
|
39
|
+
target,
|
|
40
|
+
delta: Math.max(0, Math.ceil(delta)),
|
|
41
|
+
weight,
|
|
42
|
+
treeEdge: false,
|
|
43
|
+
};
|
|
44
|
+
edges.push(edge);
|
|
45
|
+
source.outgoing.push(edge);
|
|
46
|
+
target.incoming.push(edge);
|
|
47
|
+
};
|
|
48
|
+
for (const constraint of constraints) {
|
|
49
|
+
const source = byId.get(constraint.source);
|
|
50
|
+
const target = byId.get(constraint.target);
|
|
51
|
+
if (!source || !target) throw new Error("Unknown compaction group");
|
|
52
|
+
if (source !== target) add(source, target, constraint.delta, constraint.weight);
|
|
53
|
+
}
|
|
54
|
+
// Fail explicitly on invalid constraint cycles rather than hanging tight-tree growth.
|
|
55
|
+
const remaining = new Map(nodes.map((node) => [node, node.incoming.length]));
|
|
56
|
+
const queue = nodes.filter((node) => node.incoming.length === 0);
|
|
57
|
+
let visited = 0;
|
|
58
|
+
for (let index = 0; index < queue.length; index++) {
|
|
59
|
+
const node = queue[index]!;
|
|
60
|
+
visited++;
|
|
61
|
+
for (const edge of node.outgoing) {
|
|
62
|
+
const count = remaining.get(edge.target)! - 1;
|
|
63
|
+
remaining.set(edge.target, count);
|
|
64
|
+
if (count === 0) queue.push(edge.target);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (visited !== nodes.length)
|
|
68
|
+
throw new InfeasibleCompactionError("Cyclic compaction constraints");
|
|
69
|
+
const sources = nodes.filter((node) => node.incoming.length === 0);
|
|
70
|
+
if (sources.length > 1) {
|
|
71
|
+
const source: SimplexNode = {
|
|
72
|
+
id: "",
|
|
73
|
+
order: nodes.length,
|
|
74
|
+
layer: 0,
|
|
75
|
+
incoming: [],
|
|
76
|
+
outgoing: [],
|
|
77
|
+
treeNode: false,
|
|
78
|
+
};
|
|
79
|
+
nodes.push(source);
|
|
80
|
+
for (const target of sources) add(source, target, 1, 0);
|
|
81
|
+
}
|
|
82
|
+
runNetworkSimplex(nodes, edges, Number.MAX_SAFE_INTEGER, undefined, false);
|
|
83
|
+
return new Map(groupIds.map((id) => [id, byId.get(id)!.layer]));
|
|
84
|
+
}
|
package/src/routing/adapters.ts
CHANGED
|
@@ -88,6 +88,38 @@ export function routeToPolylines(
|
|
|
88
88
|
): readonly (readonly RoutePoint[])[] {
|
|
89
89
|
return route.sections.map((s) => flattenPath(s.path, options));
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* One point list for consumers of `GraphEdge.points`. Sections are joined
|
|
93
|
+
* through their gaps (an edge label, for example); between orthogonal
|
|
94
|
+
* sections the join is orthogonal too, so it never adds a diagonal.
|
|
95
|
+
*/
|
|
96
|
+
export function routeToPoints(
|
|
97
|
+
route: Route,
|
|
98
|
+
options?: { readonly tolerance?: number },
|
|
99
|
+
): RoutePoint[] {
|
|
100
|
+
const points: RoutePoint[] = [];
|
|
101
|
+
const axis = (a: RoutePoint, b: RoutePoint) =>
|
|
102
|
+
Math.abs(a.y - b.y) < 1e-9 ? "horizontal" : Math.abs(a.x - b.x) < 1e-9 ? "vertical" : undefined;
|
|
103
|
+
for (const polyline of routeToPolylines(route, options)) {
|
|
104
|
+
const a = points.at(-1),
|
|
105
|
+
b = polyline[0];
|
|
106
|
+
if (a && b && axis(a, b) === undefined) {
|
|
107
|
+
const before = points.length > 1 ? axis(points.at(-2)!, a) : undefined;
|
|
108
|
+
const after = polyline.length > 1 ? axis(b, polyline[1]!) : undefined;
|
|
109
|
+
if (before && after) {
|
|
110
|
+
if (before === "horizontal" && after === "horizontal") {
|
|
111
|
+
const x = (a.x + b.x) / 2;
|
|
112
|
+
points.push({ x, y: a.y }, { x, y: b.y });
|
|
113
|
+
} else if (before === "vertical" && after === "vertical") {
|
|
114
|
+
const y = (a.y + b.y) / 2;
|
|
115
|
+
points.push({ x: a.x, y }, { x: b.x, y });
|
|
116
|
+
} else points.push(before === "horizontal" ? { x: b.x, y: a.y } : { x: a.x, y: b.y });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
points.push(...polyline.map((point) => ({ ...point })));
|
|
120
|
+
}
|
|
121
|
+
return points;
|
|
122
|
+
}
|
|
91
123
|
/** Compatibility for single-section native consumers. Refuses to erase gaps or topology. */
|
|
92
124
|
export function routeToGraphPatch(
|
|
93
125
|
route: Route,
|