@statelyai/layout 0.3.0 → 0.4.1

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-BBfKN4Dw.mjs +3075 -0
  12. package/dist/elkjs-DNfXDPup.cjs +3104 -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-B2buJqMf.cjs → layered-5RxQhryb.cjs} +11184 -2932
  26. package/dist/{layered-DGFTeQSl.mjs → layered-DSesbctm.mjs} +12059 -3903
  27. package/dist/{layout-engine-Dn1PiFwP.cjs → layout-engine-CLFySz3R.cjs} +5 -18
  28. package/dist/{layout-engine-Cs5tatA6.mjs → layout-engine-GpBIX6ng.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 +1476 -449
  41. package/src/elkjs/layout-quality.ts +267 -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 +233 -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 +79 -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 +875 -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 +1044 -0
  89. package/src/layered/orthogonal-cycle-order.ts +106 -0
  90. package/src/layered/orthogonal-hypersegments.ts +154 -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 +2124 -886
  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
@@ -0,0 +1,106 @@
1
+ import type { GraphEdge } from "@statelyai/graph";
2
+ import type { PortEdgeList } from "./elk-port-lists";
3
+ import type { LongEdgeExpansion } from "./long-edges";
4
+ import type { LayeredPhaseInput } from "./types";
5
+
6
+ /**
7
+ * ELK's LongEdgeSplitter appends each dummy to the next layer while visiting
8
+ * the current layer's nodes, their ports and each port's outgoing edges. That
9
+ * insertion order is the initial crossing-minimization order, so seed it here.
10
+ */
11
+ export function withLongEdgeSplitterOrder(
12
+ unsplit: LayeredPhaseInput,
13
+ expansion: LongEdgeExpansion,
14
+ baseOrder: readonly string[],
15
+ portLists: ReadonlyMap<string, readonly PortEdgeList[]>,
16
+ ): LongEdgeExpansion {
17
+ const { input, orientation, assignment } = expansion;
18
+ const authoredIds = new Set(unsplit.graph.nodes.map((node) => node.id));
19
+ const dummyIds = new Set(
20
+ input.graph.nodes.map((node) => node.id).filter((id) => !authoredIds.has(id)),
21
+ );
22
+ if (dummyIds.size === 0) return expansion;
23
+ const layerOf = (id: string) => assignment.layerByNodeId.get(id) ?? 0;
24
+ const placed = new Set<string>();
25
+ const layers: string[][] = [];
26
+ for (const id of [...baseOrder, ...authoredIds]) {
27
+ if (placed.has(id) || !authoredIds.has(id)) continue;
28
+ placed.add(id);
29
+ (layers[layerOf(id)] ??= []).push(id);
30
+ }
31
+ const reversed = (edge: GraphEdge) => orientation.reversedEdgeIds.has(edge.id);
32
+ const outgoing = new Map<string, GraphEdge[]>();
33
+ for (const edge of input.graph.edges) {
34
+ const source = reversed(edge) ? edge.targetId : edge.sourceId;
35
+ if (edge.sourceId === edge.targetId) continue;
36
+ if (!outgoing.has(source)) outgoing.set(source, []);
37
+ outgoing.get(source)!.push(edge);
38
+ }
39
+ // Visit ports and their outgoing edges in ELK's simulated list order, which
40
+ // records reversal history. Expanded segment ids keep the original edge id.
41
+ const original = (id: string) => id.split("::")[0]!;
42
+ const targetsInPortOrder = (id: string): string[] => {
43
+ const edges = outgoing.get(id) ?? [];
44
+ const lists = portLists.get(id);
45
+ if (!lists) return edges.map((edge) => (reversed(edge) ? edge.sourceId : edge.targetId));
46
+ const rank = new Map<string, number>();
47
+ for (const port of lists) for (const edgeId of port.outgoing) rank.set(edgeId, rank.size);
48
+ return edges
49
+ .map((edge, index) => ({
50
+ edge,
51
+ index,
52
+ rank: rank.get(original(edge.id)) ?? rank.size + index,
53
+ }))
54
+ .sort((a, b) => a.rank - b.rank || a.index - b.index)
55
+ .map(({ edge }) => (reversed(edge) ? edge.sourceId : edge.targetId));
56
+ };
57
+ for (let index = 0; index < layers.length; index++) {
58
+ const layer = layers[index] ?? [];
59
+ for (let position = 0; position < layer.length; position++) {
60
+ for (const target of targetsInPortOrder(layer[position]!)) {
61
+ if (!dummyIds.has(target) || placed.has(target) || layerOf(target) !== index + 1) continue;
62
+ placed.add(target);
63
+ (layers[index + 1] ??= []).push(target);
64
+ }
65
+ }
66
+ }
67
+ const seedOrder = layers.flatMap((layer) => layer ?? []);
68
+ for (const id of dummyIds) if (!placed.has(id)) seedOrder.push(id);
69
+ return { ...expansion, assignment: { ...assignment, seedOrder } };
70
+ }
71
+
72
+ /**
73
+ * Each split re-targets a long edge through a new segment appended to the
74
+ * target port's incoming edges. Long edges therefore follow retained short
75
+ * edges, in the order of their final split.
76
+ */
77
+ export function incomingAfterLongEdgeSplitting(
78
+ lists: ReadonlyMap<string, readonly PortEdgeList[]>,
79
+ expansion: LongEdgeExpansion,
80
+ ): Map<string, PortEdgeList[]> {
81
+ const { input, orientation, assignment } = expansion;
82
+ const seed = new Map((assignment.seedOrder ?? []).map((id, index) => [id, index]));
83
+ const listed = (id: string) => id.replace(/::segment:\d+$/, "");
84
+ const finalSplit = new Map<string, number>();
85
+ for (const edge of input.graph.edges) {
86
+ const reversed = orientation.reversedEdgeIds.has(edge.id);
87
+ const [source, target] = reversed
88
+ ? [edge.targetId, edge.sourceId]
89
+ : [edge.sourceId, edge.targetId];
90
+ const at = seed.get(source);
91
+ if (source.startsWith("__layout_dummy:") && at !== undefined)
92
+ finalSplit.set(`${target}\0${listed(edge.id)}`, at);
93
+ }
94
+ return new Map(
95
+ [...lists].map(([id, ports]) => [
96
+ id,
97
+ ports.map((port) => ({
98
+ ...port,
99
+ incoming: port.incoming
100
+ .map((edgeId, index) => ({ edgeId, index, split: finalSplit.get(`${id}\0${edgeId}`) }))
101
+ .sort((a, b) => (a.split ?? -1) - (b.split ?? -1) || a.index - b.index)
102
+ .map(({ edgeId }) => edgeId),
103
+ })),
104
+ ]),
105
+ );
106
+ }