@statelyai/layout 0.4.0 → 0.4.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.
- package/dist/elkjs/index.cjs +2 -2
- 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 → elkjs-CSi7oB5J.mjs} +181 -69
- package/dist/{elkjs-ggjsBuVP.cjs → elkjs-CnBvdyBu.cjs} +181 -69
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/layered/index.cjs +1 -1
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-CBPg6_6_.cjs → layered-BJu8A21s.cjs} +474 -158
- package/dist/{layered-C7ERzZjR.mjs → layered-BsAdm_u8.mjs} +469 -153
- package/dist/{layout-engine-DAw06RhN.cjs → layout-engine-BFP1TZbF.cjs} +1 -1
- package/dist/{layout-engine-DC3IYkIy.mjs → layout-engine-DnlmGxHL.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +135 -33
- package/src/elkjs/layout-quality.ts +81 -33
- package/src/layered/bk-node-placement.ts +10 -7
- package/src/layered/crossing-counter.ts +27 -9
- package/src/layered/crossing-graph.ts +2 -2
- package/src/layered/greedy-switch-decider.ts +44 -17
- package/src/layered/hierarchy-sweepiness.ts +37 -41
- package/src/layered/index.ts +15 -21
- package/src/layered/loop-envelopes.ts +10 -4
- package/src/layered/node-lookup.ts +29 -0
- package/src/layered/opposing-tracks.ts +108 -8
- package/src/layered/orthogonal-hypersegments.ts +69 -7
- package/src/layered/port-distributor.ts +26 -14
- package/src/layered/port-edge-order.ts +2 -1
- package/src/layered/spacing.ts +4 -3
- package/src/layered/strategies.ts +288 -111
|
@@ -18,6 +18,8 @@ export interface PortDistributionOptions {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** Stateful clockwise port distribution across successive layer sweeps. */
|
|
21
|
+
const sideRank = { NORTH: 0, EAST: 1, SOUTH: 2, WEST: 3 };
|
|
22
|
+
|
|
21
23
|
export class CanonicalPortDistributor {
|
|
22
24
|
readonly state: PortDistributionState;
|
|
23
25
|
private topologyGraph?: CrossingGraph;
|
|
@@ -73,27 +75,36 @@ export class CanonicalPortDistributor {
|
|
|
73
75
|
): void {
|
|
74
76
|
this.prepareTopology(graph);
|
|
75
77
|
let consumed = 0;
|
|
78
|
+
// Only ports with edges toward the free layer take ranks.
|
|
79
|
+
const ends = input ? this.incoming : this.outgoing;
|
|
80
|
+
const ranked = (port: CrossingPort) => ends.get(port.id)!.length > 0;
|
|
76
81
|
for (const node of layer) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
|
|
82
|
+
let count = 0,
|
|
83
|
+
northCount = 0;
|
|
84
|
+
for (const port of node.ports)
|
|
85
|
+
if (ranked(port)) {
|
|
86
|
+
count++;
|
|
87
|
+
if (port.side === "NORTH") northCount++;
|
|
88
|
+
}
|
|
89
|
+
const increment = nodeRelative ? 1 / (count + 1) : 1;
|
|
81
90
|
if (input) {
|
|
82
|
-
let north = consumed +
|
|
83
|
-
let rest = nodeRelative ? consumed + 1 - increment : consumed +
|
|
84
|
-
for (const port of ports) {
|
|
91
|
+
let north = consumed + northCount * increment;
|
|
92
|
+
let rest = nodeRelative ? consumed + 1 - increment : consumed + count;
|
|
93
|
+
for (const port of node.ports) {
|
|
94
|
+
if (!ranked(port)) continue;
|
|
85
95
|
this.state.ranks[port.id] = port.side === "NORTH" ? north : rest;
|
|
86
96
|
if (port.side === "NORTH") north -= increment;
|
|
87
97
|
else rest -= increment;
|
|
88
98
|
}
|
|
89
99
|
} else {
|
|
90
100
|
let position = consumed + increment;
|
|
91
|
-
for (const port of ports) {
|
|
101
|
+
for (const port of node.ports) {
|
|
102
|
+
if (!ranked(port)) continue;
|
|
92
103
|
this.state.ranks[port.id] = position;
|
|
93
104
|
position += increment;
|
|
94
105
|
}
|
|
95
106
|
}
|
|
96
|
-
consumed += nodeRelative ? 1 :
|
|
107
|
+
consumed += nodeRelative ? 1 : count;
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
110
|
|
|
@@ -109,14 +120,15 @@ export class CanonicalPortDistributor {
|
|
|
109
120
|
const ranks = (layer: CrossingNode[], input: boolean) =>
|
|
110
121
|
this.calculatePortRanks(graph, layer, input, options.nodeRelative);
|
|
111
122
|
const distributeNode = (node: CrossingNode, side: "EAST" | "WEST") => {
|
|
112
|
-
if (options.fixedOrder.has(node.id)) return;
|
|
123
|
+
if (options.fixedOrder.has(node.id) || !node.ports.length) return;
|
|
113
124
|
const layerIndex = node.ports.length ? layerByPort.get(node.ports[0]!.id)! : index;
|
|
114
125
|
const size = graph.layers[layerIndex]!.length;
|
|
115
126
|
for (const face of [side, "SOUTH", "NORTH"] as const) {
|
|
116
127
|
let minimum = 0,
|
|
117
128
|
maximum = 0;
|
|
118
129
|
const inLayer: CrossingPort[] = [];
|
|
119
|
-
for (const port of node.ports
|
|
130
|
+
for (const port of node.ports) {
|
|
131
|
+
if (port.side !== face) continue;
|
|
120
132
|
let sum = 0;
|
|
121
133
|
const cross = port.side === "NORTH" || port.side === "SOUTH";
|
|
122
134
|
if (cross) {
|
|
@@ -135,8 +147,8 @@ export class CanonicalPortDistributor {
|
|
|
135
147
|
else if (output && !input) sum += position + 1;
|
|
136
148
|
else if (input && output) sum += port.side === "NORTH" ? 0 : large / 2;
|
|
137
149
|
} else {
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
150
|
+
const sameLayer = (peer: string) => layerByPort.get(peer) === layerIndex;
|
|
151
|
+
if (outgoing.get(port.id)!.some(sameLayer) || incoming.get(port.id)!.some(sameLayer)) {
|
|
140
152
|
inLayer.push(port);
|
|
141
153
|
continue;
|
|
142
154
|
}
|
|
@@ -168,7 +180,7 @@ export class CanonicalPortDistributor {
|
|
|
168
180
|
: minimum - (size + 1 - value);
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
|
-
|
|
183
|
+
if (node.ports.length < 2) return;
|
|
172
184
|
node.ports.sort((a, b) => {
|
|
173
185
|
const side = sideRank[a.side] - sideRank[b.side];
|
|
174
186
|
if (side) return side;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { GraphEdge } from "@statelyai/graph";
|
|
2
2
|
import type { LayeredPhaseInput } from "./types";
|
|
3
|
+
import { findNode } from "./node-lookup";
|
|
3
4
|
|
|
4
5
|
/** ELK traverses connected edges through the node's port list, preserving each port's edge order. */
|
|
5
6
|
export function connectedEdgesInPortOrder(input: LayeredPhaseInput, nodeId: string): GraphEdge[] {
|
|
6
|
-
const node = input.graph.nodes
|
|
7
|
+
const node = findNode(input.graph.nodes, nodeId);
|
|
7
8
|
const portRank = new Map(node?.ports?.map((port, index) => [port.name, index]));
|
|
8
9
|
const explicitCount = portRank.size;
|
|
9
10
|
return input.graph.edges
|
package/src/layered/spacing.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { externalPortDummyOf } from "./external-port-dummy";
|
|
2
2
|
import type { LayeredPhaseInput } from "./types";
|
|
3
|
+
import { findNode } from "./node-lookup";
|
|
3
4
|
|
|
4
5
|
export type IndividualSpacing = Readonly<Record<string, number>>;
|
|
5
6
|
|
|
@@ -13,8 +14,8 @@ export function nodeNodeSpacing(
|
|
|
13
14
|
const secondBreakingPoint = secondId.startsWith("__layout_breaking:");
|
|
14
15
|
const firstLabel = firstId.startsWith("__layout_dummy:label:");
|
|
15
16
|
const secondLabel = secondId.startsWith("__layout_dummy:label:");
|
|
16
|
-
const firstNode = input.graph.nodes
|
|
17
|
-
const secondNode = input.graph.nodes
|
|
17
|
+
const firstNode = findNode(input.graph.nodes, firstId);
|
|
18
|
+
const secondNode = findNode(input.graph.nodes, secondId);
|
|
18
19
|
const firstExternal = firstNode !== undefined && externalPortDummyOf(firstNode) !== undefined;
|
|
19
20
|
const secondExternal = secondNode !== undefined && externalPortDummyOf(secondNode) !== undefined;
|
|
20
21
|
const firstDummy = firstId.startsWith("__layout_dummy:") || firstBreakingPoint || firstExternal;
|
|
@@ -47,7 +48,7 @@ export function nodeNodeSpacing(
|
|
|
47
48
|
? Number(input.settings["spacing.edgeNode"] ?? 10)
|
|
48
49
|
: input.spacing.node;
|
|
49
50
|
for (const id of [firstId, secondId]) {
|
|
50
|
-
const node = input.graph.nodes
|
|
51
|
+
const node = findNode(input.graph.nodes, id);
|
|
51
52
|
if (!node) continue;
|
|
52
53
|
const individual = input.nodeSettings?.(node)?.["spacing.individual"];
|
|
53
54
|
if (individual && typeof individual === "object") {
|