@statelyai/layout 0.5.0 → 0.5.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-DonMqGjf.cjs → elkjs--K57ydUJ.cjs} +2 -2
- package/dist/{elkjs-DBkalvt1.mjs → elkjs-C243vQwg.mjs} +2 -2
- 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-SB2mlAqt.cjs → layered-B1AE7Zpr.cjs} +278 -37
- package/dist/{layered-CmL7oynw.mjs → layered-BtnmL9_b.mjs} +278 -37
- package/dist/{layout-engine-BXoIpYL4.mjs → layout-engine-CHkJkgG6.mjs} +1 -1
- package/dist/{layout-engine-CAyJ-I-u.cjs → layout-engine-Dk5EklIn.cjs} +1 -1
- package/package.json +1 -1
- package/src/layered/index.ts +38 -7
- package/src/layered/loop-envelopes.ts +107 -26
- package/src/layered/self-loop-ports.ts +158 -0
- package/src/layered/strategies.ts +40 -17
|
@@ -8,6 +8,86 @@ import {
|
|
|
8
8
|
selfLoopTracks,
|
|
9
9
|
} from "./fixed-self-loop";
|
|
10
10
|
import { findNode } from "./node-lookup";
|
|
11
|
+
import { hiddenLoopPorts } from "./self-loop-ports";
|
|
12
|
+
|
|
13
|
+
type CanonicalSide = "NORTH" | "SOUTH" | "EAST" | "WEST";
|
|
14
|
+
const crossSide = (side: CanonicalSide) => side === "NORTH" || side === "SOUTH";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extents of a movable loop's hidden ports in the canonical rightward frame:
|
|
18
|
+
* along its side, and protruding from it.
|
|
19
|
+
*/
|
|
20
|
+
function loopPortExtents(input: LayeredPhaseInput, edge: GraphEdge, side: CanonicalSide) {
|
|
21
|
+
const ports = hiddenLoopPorts(input.settings, edge.id);
|
|
22
|
+
const horizontal = input.direction === "right" || input.direction === "left";
|
|
23
|
+
const along = (crossSide(side) === horizontal ? "width" : "height") as "width" | "height";
|
|
24
|
+
const normal = along === "width" ? "height" : "width";
|
|
25
|
+
return {
|
|
26
|
+
along: ports ? ports.source[along] + ports.target[along] : 0,
|
|
27
|
+
normal: ports ? Math.max(ports.source[normal], ports.target[normal]) : 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A movable loop's label extent away from its side, in the canonical frame. */
|
|
32
|
+
export function movableLoopLabelExtent(
|
|
33
|
+
input: LayeredPhaseInput,
|
|
34
|
+
edge: GraphEdge,
|
|
35
|
+
side: CanonicalSide,
|
|
36
|
+
): number {
|
|
37
|
+
const horizontal = input.direction === "right" || input.direction === "left";
|
|
38
|
+
const size = (crossSide(side) === horizontal ? edge.height : edge.width) ?? 0;
|
|
39
|
+
return size > 0 ? size + Number(input.settings["spacing.edgeLabel"] ?? 2) : 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* ELK's OrthogonalSelfLoopRouter routing slot of a movable loop, measured
|
|
44
|
+
* from its node side in the canonical frame: past the protruding ports and
|
|
45
|
+
* the self-loop spacing, then one edge spacing plus any label per inner slot.
|
|
46
|
+
*/
|
|
47
|
+
export function movableLoopTrack(
|
|
48
|
+
input: LayeredPhaseInput,
|
|
49
|
+
sideLoops: readonly GraphEdge[],
|
|
50
|
+
edge: GraphEdge,
|
|
51
|
+
side: CanonicalSide,
|
|
52
|
+
ordering: string,
|
|
53
|
+
): number {
|
|
54
|
+
const ordered = ordering === "REVERSE_STACKED" ? [...sideLoops].reverse() : sideLoops;
|
|
55
|
+
const inner = ordering === "SEQUENCED" ? [] : ordered.slice(0, ordered.indexOf(edge));
|
|
56
|
+
const edgeSpacing = Number(input.settings["spacing.edgeEdge"] ?? 10);
|
|
57
|
+
return (
|
|
58
|
+
Math.max(0, ...sideLoops.map((loop) => loopPortExtents(input, loop, side).normal)) +
|
|
59
|
+
Number(input.settings["spacing.nodeSelfLoop"] ?? 10) +
|
|
60
|
+
inner.reduce(
|
|
61
|
+
(sum, loop) =>
|
|
62
|
+
sum + edgeSpacing + (crossSide(side) ? movableLoopLabelExtent(input, loop, side) : 0),
|
|
63
|
+
0,
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Canonical position of a movable loop port along its side, given its
|
|
70
|
+
* DISTRIBUTED ratio among the side's implicit ports (ELK's port placement
|
|
71
|
+
* with equal gaps between hidden ports of their own size).
|
|
72
|
+
*/
|
|
73
|
+
export function movableLoopPortOffset(
|
|
74
|
+
input: LayeredPhaseInput,
|
|
75
|
+
sideLoops: readonly GraphEdge[],
|
|
76
|
+
side: CanonicalSide,
|
|
77
|
+
length: number,
|
|
78
|
+
ratio: number,
|
|
79
|
+
): { along: number; normal: number } {
|
|
80
|
+
const count = sideLoops.length * 2;
|
|
81
|
+
const extents = sideLoops.map((loop) => loopPortExtents(input, loop, side));
|
|
82
|
+
const total = extents.reduce((sum, extent) => sum + extent.along, 0);
|
|
83
|
+
const size = total / count;
|
|
84
|
+
const index = Math.round(ratio * (count + 1)) - 1;
|
|
85
|
+
const gap = (length - total) / (count + 1);
|
|
86
|
+
return {
|
|
87
|
+
along: gap * (index + 1) + size * index + size / 2,
|
|
88
|
+
normal: Math.max(0, ...extents.map((extent) => extent.normal)),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
11
91
|
|
|
12
92
|
export interface LoopEnvelope {
|
|
13
93
|
before: number;
|
|
@@ -125,18 +205,10 @@ export function loopEnvelopes(input: LayeredPhaseInput): ReadonlyMap<string, Loo
|
|
|
125
205
|
}
|
|
126
206
|
}
|
|
127
207
|
const loops = allLoops.filter((edge) => hasMovableLoopPorts(input, edge));
|
|
128
|
-
|
|
129
|
-
// the entire node together when that phase is ported, rather than mixing
|
|
130
|
-
// two different loop distributions and reservations on the same node.
|
|
131
|
-
if (
|
|
132
|
-
loops.length === 0 ||
|
|
133
|
-
loops.some((edge) => input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true)
|
|
134
|
-
)
|
|
135
|
-
continue;
|
|
208
|
+
if (loops.length === 0) continue;
|
|
136
209
|
const settings = input.nodeSettings?.(node);
|
|
137
210
|
const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
138
211
|
const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
139
|
-
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
140
212
|
const envelope = { before: 0, after: 0, flowBefore: 0, flowAfter: 0 };
|
|
141
213
|
const sides =
|
|
142
214
|
distribution === "EQUALLY"
|
|
@@ -144,25 +216,34 @@ export function loopEnvelopes(input: LayeredPhaseInput): ReadonlyMap<string, Loo
|
|
|
144
216
|
: distribution === "NORTH_SOUTH"
|
|
145
217
|
? (["before", "after"] as const)
|
|
146
218
|
: (["before"] as const);
|
|
219
|
+
const canonicalSides = {
|
|
220
|
+
before: "NORTH",
|
|
221
|
+
after: "SOUTH",
|
|
222
|
+
flowAfter: "EAST",
|
|
223
|
+
flowBefore: "WEST",
|
|
224
|
+
} as const;
|
|
225
|
+
const horizontal = input.direction === "right" || input.direction === "left";
|
|
226
|
+
const size = input.sizes.get(node.id) ?? { width: 0, height: 0 };
|
|
227
|
+
const flowSize = horizontal ? size.width : size.height;
|
|
147
228
|
for (const side of sides) {
|
|
148
229
|
const selected = loops.filter((_, index) => sides[index % sides.length] === side);
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
side
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
230
|
+
const canonical = canonicalSides[side];
|
|
231
|
+
envelope[side] = Math.max(
|
|
232
|
+
envelope[side],
|
|
233
|
+
...selected.map(
|
|
234
|
+
(edge) =>
|
|
235
|
+
movableLoopTrack(input, selected, edge, canonical, ordering) +
|
|
236
|
+
movableLoopLabelExtent(input, edge, canonical),
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
if (!crossSide(canonical)) continue;
|
|
240
|
+
// ELK centers cross-side loop labels on their node along the flow, and
|
|
241
|
+
// a label longer than its node widens the node's layer by the overhang.
|
|
242
|
+
for (const edge of selected) {
|
|
243
|
+
const overhang = ((horizontal ? edge.width : edge.height) ?? 0) - flowSize;
|
|
244
|
+
envelope.flowBefore = Math.max(envelope.flowBefore, overhang / 2);
|
|
245
|
+
envelope.flowAfter = Math.max(envelope.flowAfter, overhang / 2);
|
|
246
|
+
}
|
|
166
247
|
}
|
|
167
248
|
envelopes.set(node.id, envelope);
|
|
168
249
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* ELK self-loop port hiding and restoration. SPDX-License-Identifier: EPL-2.0 */
|
|
2
|
+
import type { Graph, GraphEdge, GraphPort, Point, VisualGraph, VisualPort } from "@statelyai/graph";
|
|
3
|
+
import type { LayeredLayoutOptions } from "./index";
|
|
4
|
+
|
|
5
|
+
interface PortSize {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
export interface HiddenLoopPorts {
|
|
10
|
+
source: PortSize;
|
|
11
|
+
target: PortSize;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const hiddenBySettings = new WeakMap<object, ReadonlyMap<string, HiddenLoopPorts>>();
|
|
15
|
+
|
|
16
|
+
/** Sizes of the ports SelfLoopPreProcessor hid from a loop, if any. */
|
|
17
|
+
export function hiddenLoopPorts(settings: object, edgeId: string): HiddenLoopPorts | undefined {
|
|
18
|
+
return hiddenBySettings.get(settings)?.get(edgeId);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ELK's SelfLoopPreProcessor removes ports that carry only self loops from
|
|
23
|
+
* nodes whose port order is not fixed, and SelfLoopPortRestorer later puts
|
|
24
|
+
* them on the loop's side. The layered phases then see implicit loop ports;
|
|
25
|
+
* {@link restoreSelfLoopPorts} places the hidden ports at the loop ends.
|
|
26
|
+
*/
|
|
27
|
+
export function hideSelfLoopPorts<N, E, G, P>(
|
|
28
|
+
graph: Graph<N, E, G, P>,
|
|
29
|
+
options: LayeredLayoutOptions,
|
|
30
|
+
):
|
|
31
|
+
| {
|
|
32
|
+
graph: Graph<N, E, G, P>;
|
|
33
|
+
options: LayeredLayoutOptions;
|
|
34
|
+
edges: ReadonlyMap<string, GraphEdge<E>>;
|
|
35
|
+
}
|
|
36
|
+
| undefined {
|
|
37
|
+
const uses = new Map<string, number>();
|
|
38
|
+
const nonLoop = new Set<string>();
|
|
39
|
+
for (const edge of graph.edges) {
|
|
40
|
+
for (const [node, port] of [
|
|
41
|
+
[edge.sourceId, edge.sourcePort],
|
|
42
|
+
[edge.targetId, edge.targetPort],
|
|
43
|
+
] as const) {
|
|
44
|
+
if (port === undefined) continue;
|
|
45
|
+
const key = `${node}\0${port}`;
|
|
46
|
+
uses.set(key, (uses.get(key) ?? 0) + 1);
|
|
47
|
+
if (edge.sourceId !== edge.targetId) nonLoop.add(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
51
|
+
const hidden = new Map<string, HiddenLoopPorts>();
|
|
52
|
+
const edges = new Map<string, GraphEdge<E>>();
|
|
53
|
+
const hiddenPorts = new Set<string>();
|
|
54
|
+
for (const edge of graph.edges) {
|
|
55
|
+
if (
|
|
56
|
+
edge.sourceId !== edge.targetId ||
|
|
57
|
+
edge.sourcePort === undefined ||
|
|
58
|
+
edge.targetPort === undefined ||
|
|
59
|
+
edge.sourcePort === edge.targetPort
|
|
60
|
+
)
|
|
61
|
+
continue;
|
|
62
|
+
const node = nodeById.get(edge.sourceId);
|
|
63
|
+
const constraints = node && options.nodeSettings?.(node)?.portConstraints;
|
|
64
|
+
if (
|
|
65
|
+
!node ||
|
|
66
|
+
(constraints !== undefined && constraints !== "FREE" && constraints !== "UNDEFINED")
|
|
67
|
+
)
|
|
68
|
+
continue;
|
|
69
|
+
// One loop per port: shared ports would make a self hyperloop.
|
|
70
|
+
const ports = [edge.sourcePort, edge.targetPort].map((name) =>
|
|
71
|
+
node.ports?.find((port) => port.name === name),
|
|
72
|
+
);
|
|
73
|
+
if (
|
|
74
|
+
ports.some(
|
|
75
|
+
(port) =>
|
|
76
|
+
!port ||
|
|
77
|
+
uses.get(`${node.id}\0${port.name}`) !== 1 ||
|
|
78
|
+
nonLoop.has(`${node.id}\0${port.name}`),
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
continue;
|
|
82
|
+
const size = (port: GraphPort<P>) => ({ width: port.width ?? 0, height: port.height ?? 0 });
|
|
83
|
+
hidden.set(edge.id, { source: size(ports[0]!), target: size(ports[1]!) });
|
|
84
|
+
edges.set(edge.id, edge);
|
|
85
|
+
for (const port of ports) hiddenPorts.add(`${node.id}\0${port!.name}`);
|
|
86
|
+
}
|
|
87
|
+
if (hidden.size === 0) return undefined;
|
|
88
|
+
const settings = { ...options.settings };
|
|
89
|
+
hiddenBySettings.set(settings, hidden);
|
|
90
|
+
return {
|
|
91
|
+
graph: {
|
|
92
|
+
...graph,
|
|
93
|
+
nodes: graph.nodes.map((node) =>
|
|
94
|
+
node.ports?.some((port) => hiddenPorts.has(`${node.id}\0${port.name}`))
|
|
95
|
+
? {
|
|
96
|
+
...node,
|
|
97
|
+
ports: node.ports.filter((port) => !hiddenPorts.has(`${node.id}\0${port.name}`)),
|
|
98
|
+
}
|
|
99
|
+
: node,
|
|
100
|
+
),
|
|
101
|
+
edges: graph.edges.map((edge) =>
|
|
102
|
+
hidden.has(edge.id) ? { ...edge, sourcePort: undefined, targetPort: undefined } : edge,
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
options: { ...options, settings },
|
|
106
|
+
edges,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Put hidden loop ports back, anchored on the side their loop leaves from. */
|
|
111
|
+
export function restoreSelfLoopPorts<N, E, G, P>(
|
|
112
|
+
graph: Graph<N, E, G, P>,
|
|
113
|
+
result: VisualGraph<N, E, G, P>,
|
|
114
|
+
edges: ReadonlyMap<string, GraphEdge<E>>,
|
|
115
|
+
): VisualGraph<N, E, G, P> {
|
|
116
|
+
const anchors = new Map<string, Point>();
|
|
117
|
+
for (const edge of result.edges) {
|
|
118
|
+
const original = edges.get(edge.id);
|
|
119
|
+
const points = edge.points;
|
|
120
|
+
if (!original || !points?.length) continue;
|
|
121
|
+
anchors.set(`${edge.sourceId}\0${original.sourcePort}`, points[0]!);
|
|
122
|
+
anchors.set(`${edge.targetId}\0${original.targetPort}`, points.at(-1)!);
|
|
123
|
+
}
|
|
124
|
+
const originalById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
125
|
+
return {
|
|
126
|
+
...result,
|
|
127
|
+
nodes: result.nodes.map((node) => {
|
|
128
|
+
const original = originalById.get(node.id)?.ports;
|
|
129
|
+
if (!original?.some((port) => anchors.has(`${node.id}\0${port.name}`))) return node;
|
|
130
|
+
const placed = new Map(node.ports?.map((port) => [port.name, port]));
|
|
131
|
+
return {
|
|
132
|
+
...node,
|
|
133
|
+
ports: original.map((port): VisualPort<P> => {
|
|
134
|
+
const anchor = anchors.get(`${node.id}\0${port.name}`);
|
|
135
|
+
const width = port.width ?? 0,
|
|
136
|
+
height = port.height ?? 0;
|
|
137
|
+
if (!anchor) return placed.get(port.name) ?? { ...port, width, height, x: 0, y: 0 };
|
|
138
|
+
const x = anchor.x - node.x,
|
|
139
|
+
y = anchor.y - node.y;
|
|
140
|
+
// The loop leaves from the port's outer border, centered on it.
|
|
141
|
+
return {
|
|
142
|
+
...port,
|
|
143
|
+
width,
|
|
144
|
+
height,
|
|
145
|
+
x: x <= 0 ? x : x >= node.width ? x - width : x - width / 2,
|
|
146
|
+
y: x <= 0 || x >= node.width ? y - height / 2 : y <= 0 ? y : y - height,
|
|
147
|
+
};
|
|
148
|
+
}),
|
|
149
|
+
};
|
|
150
|
+
}),
|
|
151
|
+
edges: result.edges.map((edge) => {
|
|
152
|
+
const original = edges.get(edge.id);
|
|
153
|
+
return original
|
|
154
|
+
? { ...edge, sourcePort: original.sourcePort, targetPort: original.targetPort }
|
|
155
|
+
: edge;
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -19,6 +19,8 @@ import { routingCoordinates } from "./routing-coordinates";
|
|
|
19
19
|
import {
|
|
20
20
|
hasMovableLoopPorts,
|
|
21
21
|
hasPlacementLoopEnvelope,
|
|
22
|
+
movableLoopPortOffset,
|
|
23
|
+
movableLoopTrack,
|
|
22
24
|
loopEnvelopes,
|
|
23
25
|
preparedLoopEnvelopes,
|
|
24
26
|
} from "./loop-envelopes";
|
|
@@ -4337,16 +4339,14 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
|
|
|
4337
4339
|
edge: GraphEdge,
|
|
4338
4340
|
side: string,
|
|
4339
4341
|
ordering: string,
|
|
4340
|
-
canonical = false,
|
|
4341
4342
|
): number => {
|
|
4342
|
-
const
|
|
4343
|
-
const axis = crossSide === (!canonical || horizontal) ? "height" : "width";
|
|
4343
|
+
const axis = side === "NORTH" || side === "SOUTH" ? "height" : "width";
|
|
4344
4344
|
const ordered = ordering === "REVERSE_STACKED" ? [...loops].reverse() : loops;
|
|
4345
4345
|
const index = ordered.indexOf(edge);
|
|
4346
4346
|
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
4347
4347
|
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
4348
4348
|
const size = (candidate: GraphEdge) =>
|
|
4349
|
-
|
|
4349
|
+
input.edgeSettings?.(candidate)?.["edgeLabels.inline"] === true
|
|
4350
4350
|
? (candidate[axis] ?? 0)
|
|
4351
4351
|
: 0;
|
|
4352
4352
|
return (
|
|
@@ -4442,7 +4442,7 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
|
|
|
4442
4442
|
for (const [id, loops] of selfLoopsByNodeId) {
|
|
4443
4443
|
const rect = mutableRects.get(id);
|
|
4444
4444
|
const node = nodeById.get(id);
|
|
4445
|
-
if (!rect || !node) continue;
|
|
4445
|
+
if (!rect || !node || hasPlacementLoopEnvelope(placement, id)) continue;
|
|
4446
4446
|
const settings = input.nodeSettings?.(node);
|
|
4447
4447
|
const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
4448
4448
|
const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
@@ -6146,9 +6146,7 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
|
|
|
6146
6146
|
continue;
|
|
6147
6147
|
}
|
|
6148
6148
|
const movable =
|
|
6149
|
-
hasMovableLoopPorts(input, edge) &&
|
|
6150
|
-
hasPlacementLoopEnvelope(placement, source.id) &&
|
|
6151
|
-
input.edgeSettings?.(edge)?.["edgeLabels.inline"] !== true;
|
|
6149
|
+
hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id);
|
|
6152
6150
|
// Route unconstrained loops in the canonical rightward system, then
|
|
6153
6151
|
// apply the same direction transform as the layer pipeline.
|
|
6154
6152
|
const loopRect = movable
|
|
@@ -6200,7 +6198,21 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
|
|
|
6200
6198
|
const sideLoops = loops.filter(
|
|
6201
6199
|
(candidate, i) => loopSide(distribution, i, candidate) === side,
|
|
6202
6200
|
);
|
|
6203
|
-
|
|
6201
|
+
if (movable) {
|
|
6202
|
+
const track = movableLoopTrack(input, sideLoops, edge, side, ordering);
|
|
6203
|
+
const sign = side === "NORTH" ? -1 : 1;
|
|
6204
|
+
const base = side === "NORTH" ? 0 : loopRect.height;
|
|
6205
|
+
const [first, last] = [startRatio, endRatio].map((ratio) =>
|
|
6206
|
+
movableLoopPortOffset(input, sideLoops, side, loopRect.width, ratio),
|
|
6207
|
+
);
|
|
6208
|
+
return [
|
|
6209
|
+
{ x: first!.along, y: base + sign * first!.normal },
|
|
6210
|
+
{ x: first!.along, y: base + sign * track },
|
|
6211
|
+
{ x: last!.along, y: base + sign * track },
|
|
6212
|
+
{ x: last!.along, y: base + sign * last!.normal },
|
|
6213
|
+
];
|
|
6214
|
+
}
|
|
6215
|
+
const distance = loopDistance(sideLoops, edge, side, ordering);
|
|
6204
6216
|
const y =
|
|
6205
6217
|
side === "NORTH" ? loopRect.y - distance : loopRect.y + loopRect.height + distance;
|
|
6206
6218
|
const start = {
|
|
@@ -6224,15 +6236,26 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
|
|
|
6224
6236
|
const secondRatio = sequenced ? 1 / denominator : 2 / denominator;
|
|
6225
6237
|
const startRatio = side === "EAST" ? firstRatio : secondRatio;
|
|
6226
6238
|
const endRatio = side === "EAST" ? secondRatio : firstRatio;
|
|
6239
|
+
const sideLoops = loops.filter(
|
|
6240
|
+
(candidate, i) => loopSide(distribution, i, candidate) === side,
|
|
6241
|
+
);
|
|
6242
|
+
if (movable) {
|
|
6243
|
+
const track = movableLoopTrack(input, sideLoops, edge, side, ordering);
|
|
6244
|
+
const sign = side === "WEST" ? -1 : 1;
|
|
6245
|
+
const base = side === "WEST" ? 0 : loopRect.width;
|
|
6246
|
+
const [first, last] = [startRatio, endRatio].map((ratio) =>
|
|
6247
|
+
movableLoopPortOffset(input, sideLoops, side, loopRect.height, ratio),
|
|
6248
|
+
);
|
|
6249
|
+
return [
|
|
6250
|
+
{ x: base + sign * first!.normal, y: first!.along },
|
|
6251
|
+
{ x: base + sign * track, y: first!.along },
|
|
6252
|
+
{ x: base + sign * track, y: last!.along },
|
|
6253
|
+
{ x: base + sign * last!.normal, y: last!.along },
|
|
6254
|
+
];
|
|
6255
|
+
}
|
|
6227
6256
|
const distance =
|
|
6228
|
-
|
|
6229
|
-
? loopDistance(
|
|
6230
|
-
loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side),
|
|
6231
|
-
edge,
|
|
6232
|
-
side,
|
|
6233
|
-
ordering,
|
|
6234
|
-
movable,
|
|
6235
|
-
)
|
|
6257
|
+
input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true
|
|
6258
|
+
? loopDistance(sideLoops, edge, side, ordering)
|
|
6236
6259
|
: spacing * (indexOnSide + 1);
|
|
6237
6260
|
const x =
|
|
6238
6261
|
side === "EAST" ? loopRect.x + loopRect.width + distance : loopRect.x - distance;
|