@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.
Files changed (110) hide show
  1. package/NOTICE.md +4 -2
  2. package/README.md +150 -11
  3. package/dist/elkjs/index.cjs +2 -2
  4. package/dist/elkjs/index.d.cts +2 -0
  5. package/dist/elkjs/index.d.mts +2 -0
  6. package/dist/elkjs/index.mjs +2 -2
  7. package/dist/elkjs/main.cjs +1 -1
  8. package/dist/elkjs/main.mjs +1 -1
  9. package/dist/elkjs/worker.cjs +2 -2
  10. package/dist/elkjs/worker.mjs +2 -2
  11. package/dist/elkjs-UciYx7lD.mjs +2973 -0
  12. package/dist/elkjs-ggjsBuVP.cjs +3002 -0
  13. package/dist/{index-BEA_pzeT.d.cts → index-B6cWNrMw.d.cts} +9 -1
  14. package/dist/{index-DNTLRsDl.d.mts → index-CJQLUKD9.d.mts} +9 -1
  15. package/dist/{index-DNixtE3h.d.mts → index-DT6lPMfS.d.cts} +85 -5
  16. package/dist/{index-D9dhxrRl.d.cts → index-F1U-fCTu.d.mts} +85 -5
  17. package/dist/index.cjs +6 -3
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.mts +3 -3
  20. package/dist/index.mjs +4 -4
  21. package/dist/layered/index.cjs +3 -1
  22. package/dist/layered/index.d.cts +2 -2
  23. package/dist/layered/index.d.mts +2 -2
  24. package/dist/layered/index.mjs +2 -2
  25. package/dist/{layered-DGFTeQSl.mjs → layered-C7ERzZjR.mjs} +9605 -1655
  26. package/dist/{layered-B2buJqMf.cjs → layered-CBPg6_6_.cjs} +12481 -4435
  27. package/dist/{layout-engine-Dn1PiFwP.cjs → layout-engine-DAw06RhN.cjs} +5 -18
  28. package/dist/{layout-engine-Cs5tatA6.mjs → layout-engine-DC3IYkIy.mjs} +3 -10
  29. package/dist/routing/index.cjs +2 -1
  30. package/dist/routing/index.d.cts +2 -2
  31. package/dist/routing/index.d.mts +2 -2
  32. package/dist/routing/index.mjs +2 -2
  33. package/dist/{routing-A2hrK9MP.mjs → routing-CR8flv8b.mjs} +977 -593
  34. package/dist/{routing-ujEP7Qh6.cjs → routing-QIdGGFBe.cjs} +986 -596
  35. package/package.json +8 -2
  36. package/src/authoring/coordinates.ts +28 -8
  37. package/src/authoring/partial.ts +7 -2
  38. package/src/authoring/routing.ts +4 -1
  39. package/src/constraints.ts +174 -6
  40. package/src/elkjs/index.ts +1392 -449
  41. package/src/elkjs/layout-quality.ts +219 -0
  42. package/src/elkjs/opposing-tracks.ts +161 -0
  43. package/src/elkjs/orthogonal-junctions.ts +174 -0
  44. package/src/elkjs/statechart.ts +9 -3
  45. package/src/elkjs/types.ts +2 -0
  46. package/src/index.ts +2 -0
  47. package/src/internal/elkjs-compatibility.ts +4 -1
  48. package/src/internal/layered-trace.ts +48 -0
  49. package/src/internal/layout-engine.ts +2 -1
  50. package/src/java-random.ts +6 -0
  51. package/src/layered/bk-node-placement.ts +403 -92
  52. package/src/layered/center-labels.ts +398 -0
  53. package/src/layered/compaction-bounds.ts +26 -0
  54. package/src/layered/compaction-errors.ts +2 -0
  55. package/src/layered/compaction-scanline.ts +63 -0
  56. package/src/layered/compound-boundaries.ts +99 -0
  57. package/src/layered/compound.ts +35 -7
  58. package/src/layered/constraint-orientation.ts +141 -0
  59. package/src/layered/crossing-constraints.ts +243 -0
  60. package/src/layered/crossing-counter.ts +221 -0
  61. package/src/layered/crossing-graph.ts +401 -0
  62. package/src/layered/cycle-random.ts +22 -0
  63. package/src/layered/elk-port-lists.ts +243 -0
  64. package/src/layered/end-labels.ts +124 -0
  65. package/src/layered/external-port-dummy.ts +137 -0
  66. package/src/layered/feedback-chains.ts +114 -0
  67. package/src/layered/fixed-self-loop.ts +150 -0
  68. package/src/layered/greedy-switch-decider.ts +356 -0
  69. package/src/layered/grouped-compaction.ts +787 -0
  70. package/src/layered/gwt-sort.ts +53 -0
  71. package/src/layered/hierarchical-port-phases.ts +242 -0
  72. package/src/layered/hierarchical-port-restoration.ts +233 -0
  73. package/src/layered/hierarchical-port-routing.ts +179 -0
  74. package/src/layered/hierarchy-crossing.ts +121 -0
  75. package/src/layered/hierarchy-sweepiness.ts +83 -0
  76. package/src/layered/hints.ts +371 -0
  77. package/src/layered/hyperedge-dummy-merger.ts +195 -0
  78. package/src/layered/index.ts +886 -161
  79. package/src/layered/initial-model-order.ts +404 -0
  80. package/src/layered/inverted-ports.ts +207 -0
  81. package/src/layered/label-dummy-switcher.ts +200 -0
  82. package/src/layered/long-edges.ts +135 -44
  83. package/src/layered/loop-envelopes.ts +184 -0
  84. package/src/layered/native-routing.ts +275 -0
  85. package/src/layered/network-simplex.ts +11 -15
  86. package/src/layered/node-margins.ts +59 -0
  87. package/src/layered/north-south-ports.ts +271 -0
  88. package/src/layered/opposing-tracks.ts +944 -0
  89. package/src/layered/orthogonal-cycle-order.ts +106 -0
  90. package/src/layered/orthogonal-hypersegments.ts +92 -0
  91. package/src/layered/orthogonal-junctions.ts +202 -0
  92. package/src/layered/orthogonal-segments.ts +243 -0
  93. package/src/layered/placement-orientation.ts +22 -0
  94. package/src/layered/port-distributor.ts +192 -0
  95. package/src/layered/port-edge-order.ts +20 -0
  96. package/src/layered/replace-routing.ts +45 -0
  97. package/src/layered/routing-coordinates.ts +32 -0
  98. package/src/layered/separate-exterior-labels.ts +24 -6
  99. package/src/layered/spacing.ts +34 -12
  100. package/src/layered/splitter-order.ts +106 -0
  101. package/src/layered/strategies.ts +2024 -883
  102. package/src/layered/types.ts +13 -0
  103. package/src/layered/weighted-compaction.ts +84 -0
  104. package/src/routing/adapters.ts +32 -0
  105. package/src/routing/algorithms.ts +433 -21
  106. package/src/routing/coordination.ts +8 -3
  107. package/src/routing/index.ts +1 -0
  108. package/src/routing/search.ts +93 -11
  109. package/dist/elkjs-DaHMISnQ.mjs +0 -1677
  110. package/dist/elkjs-dMghiiWW.cjs +0 -1706
@@ -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
+ }
@@ -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,