@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.
- package/README.md +1 -3
- package/dist/elkjs/index.mjs +10 -7
- package/dist/{index-D2RodsZY.d.mts → index-8xYkohbz.d.mts} +2 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/layered/index.d.mts +1 -1
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-Dd868WZY.mjs → layered-QwJn2gb2.mjs} +399 -53
- package/dist/{spore-fTgSoRLP.mjs → spore-D15xIQKj.mjs} +1 -1
- package/package.json +16 -6
- package/src/box.ts +135 -0
- package/src/elkjs/index.ts +1825 -0
- package/src/elkjs/types.ts +103 -0
- package/src/errors.ts +16 -0
- package/src/fixed.ts +80 -0
- package/src/index.ts +92 -0
- package/src/java-random.ts +46 -0
- package/src/layered/bk-node-placement.ts +715 -0
- package/src/layered/elk-enum-values.ts +171 -0
- package/src/layered/elk-options.generated.ts +315 -0
- package/src/layered/elk-options.ts +98 -0
- package/src/layered/flexible-ports.ts +11 -0
- package/src/layered/high-degree.ts +127 -0
- package/src/layered/index.ts +2138 -0
- package/src/layered/layer-unzipping.ts +217 -0
- package/src/layered/linear-segments-node-placement.ts +447 -0
- package/src/layered/long-edges.ts +405 -0
- package/src/layered/min-width.ts +159 -0
- package/src/layered/multi-edge-wrapping.ts +460 -0
- package/src/layered/network-simplex-node-placement.ts +500 -0
- package/src/layered/network-simplex.ts +346 -0
- package/src/layered/node-promotion.ts +197 -0
- package/src/layered/spacing.ts +37 -0
- package/src/layered/spline-bezier.ts +102 -0
- package/src/layered/strategies.ts +5343 -0
- package/src/layered/stretch-width.ts +136 -0
- package/src/layered/types.ts +111 -0
- package/src/layout.ts +202 -0
- package/src/packing.ts +74 -0
- package/src/random.ts +142 -0
- package/src/spore.ts +103 -0
- package/src/types.ts +84 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Derived from Eclipse Layout Kernel's breaking-point wrapping processors.
|
|
3
|
+
* Copyright (c) 2016 Kiel University and others.
|
|
4
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
5
|
+
*******************************************************************************/
|
|
6
|
+
|
|
7
|
+
import type { GraphEdge, GraphNode, Point } from "@statelyai/graph";
|
|
8
|
+
import { joinLongEdgeRoutes, type LongEdgeExpansion } from "./long-edges";
|
|
9
|
+
import type {
|
|
10
|
+
AcyclicOrientation,
|
|
11
|
+
EdgeRoutes,
|
|
12
|
+
LayerAssignment,
|
|
13
|
+
LayerOrder,
|
|
14
|
+
LayeredPhaseInput,
|
|
15
|
+
NodePlacement,
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
interface BreakingPointInfo {
|
|
19
|
+
originalEdgeId: string;
|
|
20
|
+
cutIndex: number;
|
|
21
|
+
startId: string;
|
|
22
|
+
endId: string;
|
|
23
|
+
nodeStartEdgeId: string;
|
|
24
|
+
startEndEdgeId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function groupBreakingPoints<K>(
|
|
28
|
+
infos: readonly BreakingPointInfo[],
|
|
29
|
+
keyOf: (info: BreakingPointInfo) => K,
|
|
30
|
+
): Map<K, BreakingPointInfo[]> {
|
|
31
|
+
const result = new Map<K, BreakingPointInfo[]>();
|
|
32
|
+
for (const info of infos) {
|
|
33
|
+
const key = keyOf(info);
|
|
34
|
+
const group = result.get(key) ?? [];
|
|
35
|
+
group.push(info);
|
|
36
|
+
result.set(key, group);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface BreakingPointPreparation {
|
|
42
|
+
input: LayeredPhaseInput;
|
|
43
|
+
orientation: AcyclicOrientation;
|
|
44
|
+
assignment: LayerAssignment;
|
|
45
|
+
infos: readonly BreakingPointInfo[];
|
|
46
|
+
infosByOriginalEdgeId: ReadonlyMap<string, readonly BreakingPointInfo[]>;
|
|
47
|
+
piecesByOriginalEdgeId: ReadonlyMap<string, readonly { edgeId: string; reversed: boolean }[]>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface FoldedBreakingPoints {
|
|
51
|
+
expansion: LongEdgeExpansion;
|
|
52
|
+
order: LayerOrder;
|
|
53
|
+
routePiecesByOriginalEdgeId: ReadonlyMap<
|
|
54
|
+
string,
|
|
55
|
+
readonly { edgeIds: readonly string[]; reversed: boolean }[]
|
|
56
|
+
>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function breakingNode(id: string): GraphNode {
|
|
60
|
+
return { type: "node", id, data: undefined, width: 0, height: 0 };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** ELK BreakingPointInserter for an already chosen set of cut indexes. */
|
|
64
|
+
export function insertMultiEdgeBreakingPoints(
|
|
65
|
+
input: LayeredPhaseInput,
|
|
66
|
+
orientation: AcyclicOrientation,
|
|
67
|
+
assignment: LayerAssignment,
|
|
68
|
+
cuts: readonly number[],
|
|
69
|
+
): BreakingPointPreparation {
|
|
70
|
+
const sortedCuts = [...cuts].sort((left, right) => left - right);
|
|
71
|
+
const nodes = [...input.graph.nodes] as GraphNode[];
|
|
72
|
+
const edges: GraphEdge[] = [];
|
|
73
|
+
const sizes = new Map(input.sizes);
|
|
74
|
+
const layerByNodeId = new Map<string, number>();
|
|
75
|
+
const infos: BreakingPointInfo[] = [];
|
|
76
|
+
const piecesByOriginalEdgeId = new Map<
|
|
77
|
+
string,
|
|
78
|
+
readonly { edgeId: string; reversed: boolean }[]
|
|
79
|
+
>();
|
|
80
|
+
const originalEdgeBySegmentId = new Map<string, GraphEdge>();
|
|
81
|
+
|
|
82
|
+
for (const node of input.graph.nodes) {
|
|
83
|
+
const originalLayer = assignment.layerByNodeId.get(node.id) ?? 0;
|
|
84
|
+
const insertedLayers = 2 * sortedCuts.filter((cut) => cut <= originalLayer).length;
|
|
85
|
+
layerByNodeId.set(node.id, originalLayer + insertedLayers);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const [modelOrder, edge] of input.graph.edges.entries()) {
|
|
89
|
+
const sourceLayer = assignment.layerByNodeId.get(edge.sourceId) ?? 0;
|
|
90
|
+
const targetLayer = assignment.layerByNodeId.get(edge.targetId) ?? 0;
|
|
91
|
+
const crossedCuts = sortedCuts.filter(
|
|
92
|
+
(cut) =>
|
|
93
|
+
Math.min(sourceLayer, targetLayer) < cut && cut <= Math.max(sourceLayer, targetLayer),
|
|
94
|
+
);
|
|
95
|
+
if (crossedCuts.length === 0 || sourceLayer >= targetLayer) {
|
|
96
|
+
edges.push(edge);
|
|
97
|
+
originalEdgeBySegmentId.set(edge.id, edge);
|
|
98
|
+
piecesByOriginalEdgeId.set(edge.id, [{ edgeId: edge.id, reversed: false }]);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let currentSourceId = edge.sourceId;
|
|
103
|
+
let currentSourcePort = edge.sourcePort;
|
|
104
|
+
const pieces: Array<{ edgeId: string; reversed: boolean }> = [];
|
|
105
|
+
for (const cut of crossedCuts) {
|
|
106
|
+
const cutIndex = sortedCuts.indexOf(cut);
|
|
107
|
+
const startId = `__layout_breaking:${edge.id}:${cut}:start`;
|
|
108
|
+
const endId = `__layout_breaking:${edge.id}:${cut}:end`;
|
|
109
|
+
nodes.push(breakingNode(startId), breakingNode(endId));
|
|
110
|
+
sizes.set(startId, { width: 0, height: 0 });
|
|
111
|
+
sizes.set(endId, { width: 0, height: 0 });
|
|
112
|
+
layerByNodeId.set(startId, cut + 2 * cutIndex);
|
|
113
|
+
layerByNodeId.set(endId, cut + 2 * cutIndex + 1);
|
|
114
|
+
const nodeStartEdgeId = `${edge.id}::wrap:${cut}:start`;
|
|
115
|
+
const startEndEdgeId = `${edge.id}::wrap:${cut}:return`;
|
|
116
|
+
const nodeStart: GraphEdge = {
|
|
117
|
+
...edge,
|
|
118
|
+
id: nodeStartEdgeId,
|
|
119
|
+
sourceId: currentSourceId,
|
|
120
|
+
targetId: startId,
|
|
121
|
+
sourcePort: currentSourcePort,
|
|
122
|
+
targetPort: undefined,
|
|
123
|
+
width: 0,
|
|
124
|
+
height: 0,
|
|
125
|
+
points: undefined,
|
|
126
|
+
};
|
|
127
|
+
const startEnd: GraphEdge = {
|
|
128
|
+
...edge,
|
|
129
|
+
id: startEndEdgeId,
|
|
130
|
+
sourceId: startId,
|
|
131
|
+
targetId: endId,
|
|
132
|
+
sourcePort: undefined,
|
|
133
|
+
targetPort: undefined,
|
|
134
|
+
width: 0,
|
|
135
|
+
height: 0,
|
|
136
|
+
points: undefined,
|
|
137
|
+
};
|
|
138
|
+
edges.push(nodeStart, startEnd);
|
|
139
|
+
originalEdgeBySegmentId.set(nodeStartEdgeId, edge);
|
|
140
|
+
originalEdgeBySegmentId.set(startEndEdgeId, edge);
|
|
141
|
+
pieces.push(
|
|
142
|
+
{ edgeId: nodeStartEdgeId, reversed: false },
|
|
143
|
+
{ edgeId: startEndEdgeId, reversed: true },
|
|
144
|
+
);
|
|
145
|
+
infos.push({
|
|
146
|
+
originalEdgeId: edge.id,
|
|
147
|
+
cutIndex,
|
|
148
|
+
startId,
|
|
149
|
+
endId,
|
|
150
|
+
nodeStartEdgeId,
|
|
151
|
+
startEndEdgeId,
|
|
152
|
+
});
|
|
153
|
+
currentSourceId = endId;
|
|
154
|
+
currentSourcePort = undefined;
|
|
155
|
+
}
|
|
156
|
+
const remainingEdgeId = `${edge.id}::wrap:remaining`;
|
|
157
|
+
const remaining: GraphEdge = {
|
|
158
|
+
...edge,
|
|
159
|
+
id: remainingEdgeId,
|
|
160
|
+
sourceId: currentSourceId,
|
|
161
|
+
sourcePort: currentSourcePort,
|
|
162
|
+
points: undefined,
|
|
163
|
+
};
|
|
164
|
+
edges.push(remaining);
|
|
165
|
+
originalEdgeBySegmentId.set(remainingEdgeId, edge);
|
|
166
|
+
pieces.push({ edgeId: remainingEdgeId, reversed: false });
|
|
167
|
+
piecesByOriginalEdgeId.set(edge.id, pieces);
|
|
168
|
+
void modelOrder;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const graph = { ...input.graph, nodes, edges } as LayeredPhaseInput["graph"];
|
|
172
|
+
const reversedEdgeIds = new Set<string>();
|
|
173
|
+
for (const edge of edges) {
|
|
174
|
+
const original = originalEdgeBySegmentId.get(edge.id);
|
|
175
|
+
if (original && orientation.reversedEdgeIds.has(original.id)) reversedEdgeIds.add(edge.id);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
input: {
|
|
179
|
+
...input,
|
|
180
|
+
graph,
|
|
181
|
+
sizes,
|
|
182
|
+
edgeSettings: (edge) => input.edgeSettings?.(originalEdgeBySegmentId.get(edge.id) ?? edge),
|
|
183
|
+
},
|
|
184
|
+
orientation: { reversedEdgeIds },
|
|
185
|
+
assignment: { layerByNodeId },
|
|
186
|
+
infos,
|
|
187
|
+
infosByOriginalEdgeId: groupBreakingPoints(infos, (info) => info.originalEdgeId),
|
|
188
|
+
piecesByOriginalEdgeId,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function appendRoute(target: Point[], points: readonly Point[]): void {
|
|
193
|
+
for (const point of points) {
|
|
194
|
+
const previous = target.at(-1);
|
|
195
|
+
if (
|
|
196
|
+
previous &&
|
|
197
|
+
Math.abs(previous.x - point.x) < 1e-9 &&
|
|
198
|
+
Math.abs(previous.y - point.y) < 1e-9
|
|
199
|
+
) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
target.push(point);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function simplifyRoute(points: readonly Point[]): Point[] {
|
|
207
|
+
return points.filter((point, index) => {
|
|
208
|
+
const previous = points[index - 1];
|
|
209
|
+
const next = points[index + 1];
|
|
210
|
+
if (!previous || !next) return true;
|
|
211
|
+
return !(
|
|
212
|
+
(Math.abs(previous.x - point.x) < 1e-9 && Math.abs(point.x - next.x) < 1e-9) ||
|
|
213
|
+
(Math.abs(previous.y - point.y) < 1e-9 && Math.abs(point.y - next.y) < 1e-9)
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** ELK BreakingPointRemover plus orthogonal channel allocation. */
|
|
219
|
+
export function joinFoldedMultiEdgeRoutes(
|
|
220
|
+
originalGraph: LayeredPhaseInput["graph"],
|
|
221
|
+
preparation: BreakingPointPreparation,
|
|
222
|
+
folded: FoldedBreakingPoints,
|
|
223
|
+
placement: NodePlacement,
|
|
224
|
+
internalRoutes: EdgeRoutes,
|
|
225
|
+
routing: string,
|
|
226
|
+
): ReadonlyMap<string, readonly Point[]> {
|
|
227
|
+
const result = new Map<string, readonly Point[]>();
|
|
228
|
+
const cutIndexes = [...new Set(preparation.infos.map((info) => info.cutIndex))].sort(
|
|
229
|
+
(left, right) => left - right,
|
|
230
|
+
);
|
|
231
|
+
const infosByCut = groupBreakingPoints(preparation.infos, (info) => info.cutIndex);
|
|
232
|
+
const wrapDummyRects = [...placement.rectByNodeId]
|
|
233
|
+
.filter(([id]) => id.startsWith("__layout_dummy:wrap:"))
|
|
234
|
+
.map(([, rect]) => rect);
|
|
235
|
+
const leftDummyFlow = Math.min(...wrapDummyRects.map((rect) => rect.x));
|
|
236
|
+
const rightDummyFlow = Math.max(...wrapDummyRects.map((rect) => rect.x));
|
|
237
|
+
const edgeEdgeSpacing = Number(folded.expansion.input.settings["spacing.edgeEdge"] ?? 10);
|
|
238
|
+
const edgeById = new Map(originalGraph.edges.map((edge) => [edge.id, edge]));
|
|
239
|
+
|
|
240
|
+
for (const [edgeId, pieces] of folded.routePiecesByOriginalEdgeId) {
|
|
241
|
+
const points: Point[] = [];
|
|
242
|
+
let lastPoint: Point | undefined;
|
|
243
|
+
for (const piece of pieces) {
|
|
244
|
+
const joined =
|
|
245
|
+
joinLongEdgeRoutes(
|
|
246
|
+
internalRoutes,
|
|
247
|
+
new Map([[piece.edgeIds.join("\0"), piece.edgeIds]]),
|
|
248
|
+
).pointsByEdgeId.get(piece.edgeIds.join("\0")) ?? [];
|
|
249
|
+
const oriented = piece.reversed ? [...joined].reverse() : joined;
|
|
250
|
+
if (points.length === 0 && oriented[0]) appendRoute(points, [oriented[0]]);
|
|
251
|
+
appendRoute(points, oriented.slice(1, -1));
|
|
252
|
+
lastPoint = oriented.at(-1);
|
|
253
|
+
}
|
|
254
|
+
if (lastPoint) appendRoute(points, [lastPoint]);
|
|
255
|
+
const edgeInfos = preparation.infosByOriginalEdgeId.get(edgeId) ?? [];
|
|
256
|
+
let publicPoints = simplifyRoute(points);
|
|
257
|
+
if (routing === "ORTHOGONAL" && edgeInfos.length === 1) {
|
|
258
|
+
const info = edgeInfos[0]!;
|
|
259
|
+
const cutEdges = infosByCut.get(info.cutIndex) ?? [];
|
|
260
|
+
const edgeIndex = Math.max(0, cutEdges.indexOf(info));
|
|
261
|
+
const rowIndex = Math.max(0, cutIndexes.indexOf(info.cutIndex));
|
|
262
|
+
const originalEdge = edgeById.get(edgeId);
|
|
263
|
+
const targetLayer = originalEdge
|
|
264
|
+
? (folded.expansion.assignment.layerByNodeId.get(originalEdge.targetId) ?? 1)
|
|
265
|
+
: 1;
|
|
266
|
+
const startRect = placement.rectByNodeId.get(info.startId);
|
|
267
|
+
const endRect = placement.rectByNodeId.get(info.endId);
|
|
268
|
+
const rightMidpoint = startRect ? (startRect.x + rightDummyFlow) / 2 : rightDummyFlow;
|
|
269
|
+
const leftMidpoint = endRect ? (endRect.x + leftDummyFlow) / 2 : leftDummyFlow;
|
|
270
|
+
const improveWrappedEdges =
|
|
271
|
+
folded.expansion.input.settings["wrapping.multiEdge.improveWrappedEdges"] !== false;
|
|
272
|
+
const edgeNodeSpacing = Number(folded.expansion.input.settings["spacing.edgeNode"] ?? 10);
|
|
273
|
+
const additionalEdgeSpacing = Number(
|
|
274
|
+
folded.expansion.input.settings["wrapping.additionalEdgeSpacing"] ?? 10,
|
|
275
|
+
);
|
|
276
|
+
const singleCut = cutIndexes.length === 1;
|
|
277
|
+
const rightChannel =
|
|
278
|
+
rightDummyFlow -
|
|
279
|
+
(improveWrappedEdges && singleCut ? edgeNodeSpacing + additionalEdgeSpacing : 0) +
|
|
280
|
+
edgeIndex *
|
|
281
|
+
(edgeEdgeSpacing + (improveWrappedEdges && singleCut ? additionalEdgeSpacing : 0));
|
|
282
|
+
const leftChannel = improveWrappedEdges
|
|
283
|
+
? singleCut
|
|
284
|
+
? leftDummyFlow - edgeNodeSpacing + edgeIndex * edgeEdgeSpacing
|
|
285
|
+
: leftDummyFlow + (edgeIndex - rowIndex + Math.max(0, targetLayer - 1)) * edgeEdgeSpacing
|
|
286
|
+
: leftDummyFlow + (edgeIndex - 1) * edgeEdgeSpacing;
|
|
287
|
+
for (const point of publicPoints) {
|
|
288
|
+
if (Math.abs(point.x - rightMidpoint) < 1e-9) point.x = rightChannel;
|
|
289
|
+
else if (Math.abs(point.x - leftMidpoint) < 1e-9) point.x = leftChannel;
|
|
290
|
+
}
|
|
291
|
+
if (improveWrappedEdges && !singleCut && targetLayer > 1 && publicPoints.length >= 3) {
|
|
292
|
+
const thickness = Math.max(
|
|
293
|
+
0,
|
|
294
|
+
Number(
|
|
295
|
+
originalEdge
|
|
296
|
+
? (folded.expansion.input.edgeSettings?.(originalEdge)?.["edge.thickness"] ?? 1)
|
|
297
|
+
: 1,
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
const targetY = publicPoints.at(-1)?.y;
|
|
301
|
+
const approachY = [...publicPoints]
|
|
302
|
+
.reverse()
|
|
303
|
+
.find((point) => targetY === undefined || Math.abs(point.y - targetY) >= 1e-9)?.y;
|
|
304
|
+
if (approachY !== undefined) {
|
|
305
|
+
for (const point of publicPoints.slice(0, -1)) {
|
|
306
|
+
if (Math.abs(point.y - approachY) < 1e-9) point.y += thickness;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
publicPoints = simplifyRoute(publicPoints);
|
|
311
|
+
}
|
|
312
|
+
result.set(edgeId, publicPoints);
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function replaceSegment(
|
|
318
|
+
segmentIdsByEdgeId: Map<string, readonly string[]>,
|
|
319
|
+
edgeId: string,
|
|
320
|
+
replacementIds: readonly string[],
|
|
321
|
+
): void {
|
|
322
|
+
segmentIdsByEdgeId.set(edgeId, replacementIds);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** ELK BreakingPointProcessor.performWrapping plus CuttingUtils.insertDummies. */
|
|
326
|
+
export function foldMultiEdgeBreakingPoints(
|
|
327
|
+
expansion: LongEdgeExpansion,
|
|
328
|
+
order: LayerOrder,
|
|
329
|
+
preparation: BreakingPointPreparation,
|
|
330
|
+
): FoldedBreakingPoints {
|
|
331
|
+
const infoByStartId = new Map(preparation.infos.map((info) => [info.startId, info]));
|
|
332
|
+
const infoByEndId = new Map(preparation.infos.map((info) => [info.endId, info]));
|
|
333
|
+
const physicalLayers: string[][] = [[]];
|
|
334
|
+
const nodes = [...expansion.input.graph.nodes] as GraphNode[];
|
|
335
|
+
let edges = [...expansion.input.graph.edges] as GraphEdge[];
|
|
336
|
+
const sizes = new Map(expansion.input.sizes);
|
|
337
|
+
const segmentIdsByEdgeId = new Map(expansion.segmentIdsByEdgeId);
|
|
338
|
+
const originalEdgeByNewId = new Map<string, GraphEdge>();
|
|
339
|
+
const individualSpacingNodeIds = new Set<string>();
|
|
340
|
+
let dummySerial = 0;
|
|
341
|
+
let edgeSerial = 0;
|
|
342
|
+
const insertReturnChains = (ids: readonly string[], rightColumn: number): void => {
|
|
343
|
+
const offset = ids.length;
|
|
344
|
+
for (const endId of [...ids].reverse()) {
|
|
345
|
+
const info = infoByEndId.get(endId)!;
|
|
346
|
+
const expandedIds = segmentIdsByEdgeId.get(info.startEndEdgeId) ?? [info.startEndEdgeId];
|
|
347
|
+
const startEndId = expandedIds[0]!;
|
|
348
|
+
const startEnd = edges.find((edge) => edge.id === startEndId);
|
|
349
|
+
if (!startEnd) continue;
|
|
350
|
+
edges = edges.filter((edge) => edge.id !== startEndId);
|
|
351
|
+
const chain = [info.endId];
|
|
352
|
+
for (let layerNo = 0; layerNo <= rightColumn; layerNo++) {
|
|
353
|
+
const id = `__layout_dummy:wrap:${dummySerial++}`;
|
|
354
|
+
nodes.push({ type: "node", id, data: undefined, width: 0, height: 1 });
|
|
355
|
+
sizes.set(id, { width: 0, height: 1 });
|
|
356
|
+
individualSpacingNodeIds.add(id);
|
|
357
|
+
if (layerNo === 0) {
|
|
358
|
+
physicalLayers[0]!.splice(Math.max(0, physicalLayers[0]!.length - offset), 0, id);
|
|
359
|
+
} else {
|
|
360
|
+
physicalLayers[layerNo] ??= [];
|
|
361
|
+
physicalLayers[layerNo]!.push(id);
|
|
362
|
+
}
|
|
363
|
+
chain.push(id);
|
|
364
|
+
}
|
|
365
|
+
chain.push(info.startId);
|
|
366
|
+
const replacementIds: string[] = [];
|
|
367
|
+
for (let index = 0; index + 1 < chain.length; index++) {
|
|
368
|
+
const id = `${info.startEndEdgeId}::fold:${edgeSerial++}`;
|
|
369
|
+
const replacement: GraphEdge = {
|
|
370
|
+
...startEnd,
|
|
371
|
+
id,
|
|
372
|
+
sourceId: chain[index]!,
|
|
373
|
+
targetId: chain[index + 1]!,
|
|
374
|
+
sourcePort: undefined,
|
|
375
|
+
targetPort: undefined,
|
|
376
|
+
points: undefined,
|
|
377
|
+
width: 0,
|
|
378
|
+
height: 0,
|
|
379
|
+
};
|
|
380
|
+
edges.push(replacement);
|
|
381
|
+
replacementIds.push(id);
|
|
382
|
+
originalEdgeByNewId.set(id, startEnd);
|
|
383
|
+
}
|
|
384
|
+
replaceSegment(segmentIdsByEdgeId, info.startEndEdgeId, replacementIds);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
let column = 1;
|
|
389
|
+
for (const layer of order.layers) {
|
|
390
|
+
const starts = layer.filter((id) => infoByStartId.has(id));
|
|
391
|
+
const ends = layer.filter((id) => infoByEndId.has(id));
|
|
392
|
+
if (starts.length > 0 && starts.length === layer.length) {
|
|
393
|
+
physicalLayers[column] ??= [];
|
|
394
|
+
physicalLayers[column]!.push(...layer);
|
|
395
|
+
column++;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (ends.length > 0 && ends.length === layer.length) {
|
|
399
|
+
physicalLayers[0]!.push(...layer);
|
|
400
|
+
insertReturnChains(layer, column - 1);
|
|
401
|
+
column = 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
physicalLayers[column] ??= [];
|
|
405
|
+
physicalLayers[column]!.push(...layer);
|
|
406
|
+
column++;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const retainedIds = new Set(physicalLayers.flat());
|
|
410
|
+
const graphNodes = nodes.filter((node) => retainedIds.has(node.id));
|
|
411
|
+
const layerByNodeId = new Map<string, number>();
|
|
412
|
+
physicalLayers.forEach((layer, layerNo) => layer.forEach((id) => layerByNodeId.set(id, layerNo)));
|
|
413
|
+
const graph = {
|
|
414
|
+
...expansion.input.graph,
|
|
415
|
+
nodes: graphNodes,
|
|
416
|
+
edges,
|
|
417
|
+
} as LayeredPhaseInput["graph"];
|
|
418
|
+
const routePiecesByOriginalEdgeId = new Map<
|
|
419
|
+
string,
|
|
420
|
+
readonly { edgeIds: readonly string[]; reversed: boolean }[]
|
|
421
|
+
>();
|
|
422
|
+
for (const [edgeId, pieces] of preparation.piecesByOriginalEdgeId) {
|
|
423
|
+
routePiecesByOriginalEdgeId.set(
|
|
424
|
+
edgeId,
|
|
425
|
+
pieces.map((piece) => ({
|
|
426
|
+
edgeIds: segmentIdsByEdgeId.get(piece.edgeId) ?? [piece.edgeId],
|
|
427
|
+
reversed: piece.reversed,
|
|
428
|
+
})),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
expansion: {
|
|
433
|
+
...expansion,
|
|
434
|
+
input: {
|
|
435
|
+
...expansion.input,
|
|
436
|
+
graph,
|
|
437
|
+
sizes,
|
|
438
|
+
nodeSettings: (node) => ({
|
|
439
|
+
...expansion.input.nodeSettings?.(node),
|
|
440
|
+
...(individualSpacingNodeIds.has(node.id)
|
|
441
|
+
? {
|
|
442
|
+
"spacing.individual": {
|
|
443
|
+
"spacing.edgeNode":
|
|
444
|
+
Number(expansion.input.settings["spacing.edgeNode"] ?? 10) +
|
|
445
|
+
Number(expansion.input.settings["wrapping.additionalEdgeSpacing"] ?? 10),
|
|
446
|
+
},
|
|
447
|
+
}
|
|
448
|
+
: {}),
|
|
449
|
+
}),
|
|
450
|
+
edgeSettings: (edge) =>
|
|
451
|
+
expansion.input.edgeSettings?.(originalEdgeByNewId.get(edge.id) ?? edge),
|
|
452
|
+
},
|
|
453
|
+
orientation: { reversedEdgeIds: new Set() },
|
|
454
|
+
assignment: { layerByNodeId },
|
|
455
|
+
segmentIdsByEdgeId,
|
|
456
|
+
},
|
|
457
|
+
order: { layers: physicalLayers },
|
|
458
|
+
routePiecesByOriginalEdgeId,
|
|
459
|
+
};
|
|
460
|
+
}
|