@statelyai/layout 0.0.1 → 0.0.2

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 (42) hide show
  1. package/README.md +1 -3
  2. package/dist/elkjs/index.mjs +10 -7
  3. package/dist/{index-D2RodsZY.d.mts → index-8xYkohbz.d.mts} +2 -0
  4. package/dist/index.d.mts +1 -1
  5. package/dist/index.mjs +2 -2
  6. package/dist/layered/index.d.mts +1 -1
  7. package/dist/layered/index.mjs +1 -1
  8. package/dist/{layered-Dd868WZY.mjs → layered-QwJn2gb2.mjs} +399 -53
  9. package/dist/{spore-fTgSoRLP.mjs → spore-D15xIQKj.mjs} +1 -1
  10. package/package.json +16 -6
  11. package/src/box.ts +135 -0
  12. package/src/elkjs/index.ts +1825 -0
  13. package/src/elkjs/types.ts +103 -0
  14. package/src/errors.ts +16 -0
  15. package/src/fixed.ts +80 -0
  16. package/src/index.ts +92 -0
  17. package/src/java-random.ts +46 -0
  18. package/src/layered/bk-node-placement.ts +715 -0
  19. package/src/layered/elk-enum-values.ts +171 -0
  20. package/src/layered/elk-options.generated.ts +315 -0
  21. package/src/layered/elk-options.ts +98 -0
  22. package/src/layered/flexible-ports.ts +11 -0
  23. package/src/layered/high-degree.ts +127 -0
  24. package/src/layered/index.ts +2138 -0
  25. package/src/layered/layer-unzipping.ts +217 -0
  26. package/src/layered/linear-segments-node-placement.ts +447 -0
  27. package/src/layered/long-edges.ts +405 -0
  28. package/src/layered/min-width.ts +159 -0
  29. package/src/layered/multi-edge-wrapping.ts +460 -0
  30. package/src/layered/network-simplex-node-placement.ts +500 -0
  31. package/src/layered/network-simplex.ts +346 -0
  32. package/src/layered/node-promotion.ts +197 -0
  33. package/src/layered/spacing.ts +37 -0
  34. package/src/layered/spline-bezier.ts +102 -0
  35. package/src/layered/strategies.ts +5343 -0
  36. package/src/layered/stretch-width.ts +136 -0
  37. package/src/layered/types.ts +111 -0
  38. package/src/layout.ts +202 -0
  39. package/src/packing.ts +74 -0
  40. package/src/random.ts +142 -0
  41. package/src/spore.ts +103 -0
  42. package/src/types.ts +84 -0
@@ -0,0 +1,127 @@
1
+ import type { GraphEdge } from "@statelyai/graph";
2
+ import type { AcyclicOrientation, LayerAssignment, LayeredPhaseInput } from "./types";
3
+
4
+ interface Tree {
5
+ nodesAtDepth: Map<number, Set<string>>;
6
+ height: number;
7
+ }
8
+
9
+ function endpoints(edge: GraphEdge, orientation: AcyclicOrientation): [string, string] {
10
+ return orientation.reversedEdgeIds.has(edge.id)
11
+ ? [edge.targetId, edge.sourceId]
12
+ : [edge.sourceId, edge.targetId];
13
+ }
14
+
15
+ /** ELK's post-layering treatment for leaf trees around high-degree nodes. */
16
+ export function applyHighDegreeNodeTreatment(
17
+ input: LayeredPhaseInput,
18
+ orientation: AcyclicOrientation,
19
+ assignment: LayerAssignment,
20
+ ): LayerAssignment {
21
+ if (input.settings["highDegreeNodes.treatment"] !== true) return assignment;
22
+ const threshold = Math.max(0, Number(input.settings["highDegreeNodes.threshold"] ?? 16));
23
+ const configuredHeight = Math.max(0, Number(input.settings["highDegreeNodes.treeHeight"] ?? 5));
24
+ const maximumHeight = configuredHeight === 0 ? Number.POSITIVE_INFINITY : configuredHeight;
25
+ const incoming = new Map<string, GraphEdge[]>();
26
+ const outgoing = new Map<string, GraphEdge[]>();
27
+ for (const node of input.graph.nodes) {
28
+ incoming.set(node.id, []);
29
+ outgoing.set(node.id, []);
30
+ }
31
+ for (const edge of input.graph.edges) {
32
+ if (edge.sourceId === edge.targetId) continue;
33
+ const [source, target] = endpoints(edge, orientation);
34
+ outgoing.get(source)?.push(edge);
35
+ incoming.get(target)?.push(edge);
36
+ }
37
+ const degree = (id: string) => (incoming.get(id)?.length ?? 0) + (outgoing.get(id)?.length ?? 0);
38
+ const isHighDegree = (id: string) => degree(id) >= threshold;
39
+ const other = (edge: GraphEdge, id: string) => {
40
+ const [source, target] = endpoints(edge, orientation);
41
+ return source === id ? target : source;
42
+ };
43
+ const hasSingleConnection = (id: string, edges: readonly GraphEdge[]) => {
44
+ let connection: string | undefined;
45
+ for (const edge of edges) {
46
+ const candidate = other(edge, id);
47
+ if (connection === undefined) connection = candidate;
48
+ else if (connection !== candidate) return false;
49
+ }
50
+ return true;
51
+ };
52
+ const collectTree = (
53
+ root: string,
54
+ ancestors: ReadonlyMap<string, GraphEdge[]>,
55
+ descendants: ReadonlyMap<string, GraphEdge[]>,
56
+ ): Tree | undefined => {
57
+ const nodesAtDepth = new Map<number, Set<string>>();
58
+ const visiting = new Set<string>();
59
+ const visit = (id: string, depth: number): number | undefined => {
60
+ if (isHighDegree(id) || visiting.has(id) || depth > maximumHeight) return undefined;
61
+ if (!hasSingleConnection(id, ancestors.get(id) ?? [])) return undefined;
62
+ visiting.add(id);
63
+ let height = 1;
64
+ for (const edge of descendants.get(id) ?? []) {
65
+ const childHeight = visit(other(edge, id), depth + 1);
66
+ if (childHeight === undefined) {
67
+ visiting.delete(id);
68
+ return undefined;
69
+ }
70
+ height = Math.max(height, childHeight + 1);
71
+ }
72
+ visiting.delete(id);
73
+ if (height > maximumHeight) return undefined;
74
+ const bucket = nodesAtDepth.get(depth) ?? new Set<string>();
75
+ bucket.add(id);
76
+ nodesAtDepth.set(depth, bucket);
77
+ return height;
78
+ };
79
+ const height = visit(root, 1);
80
+ return height === undefined ? undefined : { nodesAtDepth, height };
81
+ };
82
+
83
+ const maximumLayer = Math.max(0, ...assignment.layerByNodeId.values());
84
+ const originalLayers = Array.from({ length: maximumLayer + 1 }, () => [] as string[]);
85
+ for (const node of input.graph.nodes) {
86
+ originalLayers[assignment.layerByNodeId.get(node.id) ?? 0]?.push(node.id);
87
+ }
88
+ const beforeByLayer = new Map<number, Tree[]>();
89
+ const afterByLayer = new Map<number, Tree[]>();
90
+ const moved = new Set<string>();
91
+ const remember = (target: Map<number, Tree[]>, layer: number, tree: Tree) => {
92
+ const trees = target.get(layer) ?? [];
93
+ trees.push(tree);
94
+ target.set(layer, trees);
95
+ for (const nodes of tree.nodesAtDepth.values()) for (const node of nodes) moved.add(node);
96
+ };
97
+ for (const [layer, ids] of originalLayers.entries()) {
98
+ for (const id of ids) {
99
+ if (!isHighDegree(id)) continue;
100
+ for (const edge of incoming.get(id) ?? []) {
101
+ const tree = collectTree(other(edge, id), outgoing, incoming);
102
+ if (tree) remember(beforeByLayer, layer, tree);
103
+ }
104
+ for (const edge of outgoing.get(id) ?? []) {
105
+ const tree = collectTree(other(edge, id), incoming, outgoing);
106
+ if (tree) remember(afterByLayer, layer, tree);
107
+ }
108
+ }
109
+ }
110
+
111
+ const rebuilt: string[][] = [];
112
+ for (const [layer, ids] of originalLayers.entries()) {
113
+ const before = beforeByLayer.get(layer) ?? [];
114
+ for (let depth = Math.max(0, ...before.map((tree) => tree.height)); depth >= 1; depth--) {
115
+ rebuilt.push(before.flatMap((tree) => [...(tree.nodesAtDepth.get(depth) ?? [])]));
116
+ }
117
+ rebuilt.push(ids.filter((id) => !moved.has(id)));
118
+ const after = afterByLayer.get(layer) ?? [];
119
+ for (let depth = 1; depth <= Math.max(0, ...after.map((tree) => tree.height)); depth++) {
120
+ rebuilt.push(after.flatMap((tree) => [...(tree.nodesAtDepth.get(depth) ?? [])]));
121
+ }
122
+ }
123
+ const nonempty = rebuilt.filter((layer) => layer.length > 0);
124
+ return {
125
+ layerByNodeId: new Map(nonempty.flatMap((ids, layer) => ids.map((id) => [id, layer] as const))),
126
+ };
127
+ }