@statelyai/layout 0.4.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.
- 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-BBfKN4Dw.mjs} +165 -63
- package/dist/{elkjs-ggjsBuVP.cjs → elkjs-DNfXDPup.cjs} +165 -63
- 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-5RxQhryb.cjs} +245 -39
- package/dist/{layered-C7ERzZjR.mjs → layered-DSesbctm.mjs} +240 -34
- package/dist/{layout-engine-DAw06RhN.cjs → layout-engine-CLFySz3R.cjs} +1 -1
- package/dist/{layout-engine-DC3IYkIy.mjs → layout-engine-GpBIX6ng.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +112 -28
- package/src/elkjs/layout-quality.ts +81 -33
- package/src/layered/crossing-counter.ts +17 -5
- package/src/layered/hierarchy-sweepiness.ts +37 -41
- package/src/layered/index.ts +0 -11
- package/src/layered/opposing-tracks.ts +108 -8
- package/src/layered/orthogonal-hypersegments.ts +69 -7
- package/src/layered/strategies.ts +115 -18
package/dist/elkjs/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
-
require('../layered-
|
|
3
|
-
const require_elkjs = require('../elkjs-
|
|
2
|
+
require('../layered-5RxQhryb.cjs');
|
|
3
|
+
const require_elkjs = require('../elkjs-DNfXDPup.cjs');
|
|
4
4
|
|
|
5
5
|
exports.compileStatechartLayout = require_elkjs.compileStatechartLayout;
|
|
6
6
|
exports.default = require_elkjs.ELK;
|
package/dist/elkjs/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../layered-
|
|
2
|
-
import { a as scoreStatechartLayout, i as layoutStatechart, n as getElkRoutes, r as compileStatechartLayout, t as ELK } from "../elkjs-
|
|
1
|
+
import "../layered-DSesbctm.mjs";
|
|
2
|
+
import { a as scoreStatechartLayout, i as layoutStatechart, n as getElkRoutes, r as compileStatechartLayout, t as ELK } from "../elkjs-BBfKN4Dw.mjs";
|
|
3
3
|
|
|
4
4
|
export { compileStatechartLayout, ELK as default, getElkRoutes, layoutStatechart, scoreStatechartLayout };
|
package/dist/elkjs/main.cjs
CHANGED
package/dist/elkjs/main.mjs
CHANGED
package/dist/elkjs/worker.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require('../layered-
|
|
2
|
-
const require_elkjs = require('../elkjs-
|
|
1
|
+
require('../layered-5RxQhryb.cjs');
|
|
2
|
+
const require_elkjs = require('../elkjs-DNfXDPup.cjs');
|
|
3
3
|
|
|
4
4
|
//#region src/elkjs/worker.ts
|
|
5
5
|
/*******************************************************************************
|
package/dist/elkjs/worker.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../layered-
|
|
2
|
-
import { t as ELK } from "../elkjs-
|
|
1
|
+
import "../layered-DSesbctm.mjs";
|
|
2
|
+
import { t as ELK } from "../elkjs-BBfKN4Dw.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/elkjs/worker.ts
|
|
5
5
|
/*******************************************************************************
|
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import { T as pathFromPoints, g as pathFromSplinePoints, j as freeze } from "./routing-CR8flv8b.mjs";
|
|
2
|
-
import { $ as
|
|
3
|
-
import { n as executeElkjs0111Layout } from "./layout-engine-
|
|
2
|
+
import { $ as attachExternalPortDummy, R as createLayerSweepSession, S as CROSSING_MODEL, Z as minimizeHierarchyCrossings, b as elkjs0111ResultPolicy, c as elkLayeredOptionDefinitions, et as createExternalPortDummy, h as separateOpposingTracks, nt as createOrthogonalHypersegments, t as createLayeredScopePipeline, tt as externalPortDummyOf, v as isPerpendicularPortRestored, y as hierarchicalPortSides } from "./layered-DSesbctm.mjs";
|
|
3
|
+
import { n as executeElkjs0111Layout } from "./layout-engine-GpBIX6ng.mjs";
|
|
4
4
|
import { createGraph } from "@statelyai/graph";
|
|
5
5
|
|
|
6
6
|
//#region src/layered/hierarchy-sweepiness.ts
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Whether a child graph is swept on its own before its parent. Like ELK, a
|
|
9
|
+
* node starts a random path when no western port carries an edge and ends one
|
|
10
|
+
* when no eastern port does, on the canonical crossing graph's port sides.
|
|
11
|
+
*/
|
|
12
|
+
function useBottomUpHierarchySweep(input, graph, parent) {
|
|
8
13
|
const boundary = input.settings["crossingMinimization.hierarchicalSweepiness"] ?? .1;
|
|
9
14
|
if (boundary < -1 || parent.portOrderFixed || parent.inputPorts < 2 && parent.outputPorts < 2) return true;
|
|
10
15
|
if (parent.deterministic) return false;
|
|
11
16
|
const nodes = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
12
|
-
const
|
|
17
|
+
const owner = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const node of graph.layers.flat()) for (const port of node.ports) owner.set(port.id, node.id);
|
|
19
|
+
const degree = /* @__PURE__ */ new Map(), targets = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const { source, target } of graph.edges) {
|
|
21
|
+
degree.set(source, (degree.get(source) ?? 0) + 1);
|
|
22
|
+
degree.set(target, (degree.get(target) ?? 0) + 1);
|
|
23
|
+
const from = owner.get(source);
|
|
24
|
+
targets.set(from, [...targets.get(from) ?? [], owner.get(target)]);
|
|
25
|
+
}
|
|
26
|
+
const infos = new Map(graph.layers.flat().map((node) => [node.id, {
|
|
13
27
|
random: 0,
|
|
14
28
|
hierarchy: 0,
|
|
15
29
|
edges: 0
|
|
16
30
|
}]));
|
|
17
|
-
const outgoing = /* @__PURE__ */ new Map(), incoming = /* @__PURE__ */ new Map();
|
|
18
|
-
for (const edge of input.graph.edges) {
|
|
19
|
-
const reversed = orientation.reversedEdgeIds.has(edge.id);
|
|
20
|
-
const source = reversed ? edge.targetId : edge.sourceId, target = reversed ? edge.sourceId : edge.targetId;
|
|
21
|
-
outgoing.set(source, [...outgoing.get(source) ?? [], target]);
|
|
22
|
-
incoming.set(target, [...incoming.get(target) ?? [], source]);
|
|
23
|
-
}
|
|
24
|
-
const units = getCrossingUnits(input);
|
|
25
31
|
let randomPaths = 0, hierarchyPaths = 0;
|
|
26
32
|
const transfer = (source, target) => {
|
|
27
33
|
const a = infos.get(source), b = infos.get(target);
|
|
34
|
+
randomPaths += a.random;
|
|
35
|
+
hierarchyPaths += a.hierarchy;
|
|
28
36
|
b.random += a.random;
|
|
29
37
|
b.hierarchy += a.hierarchy;
|
|
30
38
|
b.edges += a.edges + 1;
|
|
@@ -35,36 +43,25 @@ function useBottomUpHierarchySweep(input, orientation, layers, parent) {
|
|
|
35
43
|
down: "SOUTH",
|
|
36
44
|
up: "NORTH"
|
|
37
45
|
}[input.direction];
|
|
38
|
-
const
|
|
39
|
-
const state = infos.get(id);
|
|
40
|
-
for (const target of outgoing.get(id) ?? []) {
|
|
41
|
-
randomPaths += state.random;
|
|
42
|
-
hierarchyPaths += state.hierarchy;
|
|
43
|
-
transfer(id, target);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
for (const layer of layers) {
|
|
46
|
+
for (const layer of graph.layers) {
|
|
47
47
|
const northSouth = [];
|
|
48
|
-
for (const
|
|
49
|
-
if (
|
|
50
|
-
northSouth.push(id);
|
|
48
|
+
for (const node of layer) {
|
|
49
|
+
if (node.type === "NORTH_SOUTH_PORT") {
|
|
50
|
+
northSouth.push(node.id);
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
53
|
-
const
|
|
54
|
-
const
|
|
53
|
+
const state = infos.get(node.id);
|
|
54
|
+
const connected = (side) => node.ports.some((port) => port.side === side && degree.has(port.id));
|
|
55
|
+
const external = externalPortDummyOf(nodes.get(node.id));
|
|
55
56
|
if (external) {
|
|
56
57
|
state.hierarchy = 1;
|
|
57
58
|
if (external.side === outputSide) hierarchyPaths += state.edges;
|
|
58
|
-
} else if (!
|
|
59
|
-
else if (!
|
|
60
|
-
|
|
61
|
-
for (const
|
|
62
|
-
randomPaths += state.random;
|
|
63
|
-
hierarchyPaths += state.hierarchy;
|
|
64
|
-
transfer(id, dummy);
|
|
65
|
-
}
|
|
59
|
+
} else if (!connected("WEST")) state.random = 1;
|
|
60
|
+
else if (!connected("EAST")) randomPaths += state.edges;
|
|
61
|
+
for (const target of targets.get(node.id) ?? []) transfer(node.id, target);
|
|
62
|
+
for (const port of node.ports) if ((port.side === "NORTH" || port.side === "SOUTH") && port.dummy) transfer(node.id, port.dummy);
|
|
66
63
|
}
|
|
67
|
-
for (const id of northSouth)
|
|
64
|
+
for (const id of northSouth) for (const target of targets.get(id) ?? []) transfer(id, target);
|
|
68
65
|
}
|
|
69
66
|
const all = randomPaths + hierarchyPaths;
|
|
70
67
|
return (all === 0 ? Number.POSITIVE_INFINITY : (randomPaths - hierarchyPaths) / all) >= boundary;
|
|
@@ -166,9 +163,13 @@ const vertical = (s) => Math.abs(s.a.x - s.b.x) < EPS;
|
|
|
166
163
|
const span = (s, axis) => [Math.min(s.a[axis], s.b[axis]), Math.max(s.a[axis], s.b[axis])];
|
|
167
164
|
const strictlyInside = (value, low, high) => value > low + EPS && value < high - EPS;
|
|
168
165
|
const overlapLength = (a, b) => Math.max(0, Math.min(a[1], b[1]) - Math.max(a[0], b[0]));
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
/** Within `margin` of the rectangle, its border included. */
|
|
167
|
+
const inRect = (point, rect, margin) => point.x >= rect.x - margin - EPS && point.x <= rect.x + rect.width + margin + EPS && point.y >= rect.y - margin - EPS && point.y <= rect.y + rect.height + margin + EPS;
|
|
168
|
+
/**
|
|
169
|
+
* Measure an ELK JSON result in absolute coordinates. `defectsOnly` skips the
|
|
170
|
+
* pairwise route measures (crossings and overlap stay 0).
|
|
171
|
+
*/
|
|
172
|
+
function measureLayout(root, defectsOnly = false) {
|
|
172
173
|
const frames = new Map([[String(root.id), {
|
|
173
174
|
x: 0,
|
|
174
175
|
y: 0
|
|
@@ -263,6 +264,7 @@ function measureLayout(root) {
|
|
|
263
264
|
routes.push({
|
|
264
265
|
ends,
|
|
265
266
|
segments,
|
|
267
|
+
visible: labels.length && !defectsOnly ? segments.flatMap((s) => outsideAll(s, labels)) : segments,
|
|
266
268
|
labels
|
|
267
269
|
});
|
|
268
270
|
}
|
|
@@ -279,26 +281,31 @@ function measureLayout(root) {
|
|
|
279
281
|
for (const label of other.labels) if (route.segments.some((segment) => horizontal(segment) && strictlyInside(segment.a.y, label.y, label.y + label.height) && overlapLength(span(segment, "x"), [label.x, label.x + label.width]) > EPS || vertical(segment) && strictlyInside(segment.a.x, label.x, label.x + label.width) && overlapLength(span(segment, "y"), [label.y, label.y + label.height]) > EPS)) defects++;
|
|
280
282
|
}
|
|
281
283
|
let crossings = 0, overlap = 0;
|
|
282
|
-
for (const [index, first] of routes.entries()) for (const second of routes.slice(index + 1)) {
|
|
284
|
+
if (!defectsOnly) for (const [index, first] of routes.entries()) for (const second of routes.slice(index + 1)) {
|
|
283
285
|
const shared = first.ends.filter((id) => second.ends.includes(id)).flatMap((id) => {
|
|
284
286
|
const rect = rects.get(id);
|
|
285
287
|
return rect ? [rect] : [];
|
|
286
288
|
});
|
|
287
289
|
const clear = (point) => shared.every((rect) => !inRect(point, rect, TERMINAL_MARGIN));
|
|
288
|
-
|
|
290
|
+
let points;
|
|
291
|
+
const cross$1 = (s, t) => {
|
|
289
292
|
const h = horizontal(s) && vertical(t) ? s : horizontal(t) && vertical(s) ? t : void 0;
|
|
290
|
-
if (h)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
if (!h) return;
|
|
294
|
+
const v = h === s ? t : s;
|
|
295
|
+
const point = {
|
|
296
|
+
x: v.a.x,
|
|
297
|
+
y: h.a.y
|
|
298
|
+
};
|
|
299
|
+
if (strictlyInside(point.x, ...span(h, "x")) && strictlyInside(point.y, ...span(v, "y")) && clear(point)) (points ??= /* @__PURE__ */ new Set()).add(`${Math.round(point.x / EPS)}:${Math.round(point.y / EPS)}`);
|
|
300
|
+
};
|
|
301
|
+
const unlabeled = first.visible === first.segments && second.visible === second.segments;
|
|
302
|
+
for (const s of first.segments) for (const t of second.segments) {
|
|
303
|
+
if (unlabeled) cross$1(s, t);
|
|
299
304
|
const shared$1 = sharedLength(s, t);
|
|
300
305
|
if (shared$1 > EPS && clear(s.a) && clear(s.b)) overlap += shared$1;
|
|
301
306
|
}
|
|
307
|
+
if (!unlabeled) for (const s of first.visible) for (const t of second.visible) cross$1(s, t);
|
|
308
|
+
crossings += points?.size ?? 0;
|
|
302
309
|
}
|
|
303
310
|
const xs = bounds.map((point) => point.x).filter(Number.isFinite), ys = bounds.map((point) => point.y).filter(Number.isFinite);
|
|
304
311
|
const area = xs.length ? (Math.max(...xs) - Math.min(...xs)) * (Math.max(...ys) - Math.min(...ys)) : 0;
|
|
@@ -311,6 +318,29 @@ function measureLayout(root) {
|
|
|
311
318
|
area
|
|
312
319
|
};
|
|
313
320
|
}
|
|
321
|
+
/** The parts of an axis-parallel segment outside the open interiors of `rects`. */
|
|
322
|
+
function outsideAll(segment, rects) {
|
|
323
|
+
let parts = [segment];
|
|
324
|
+
for (const rect of rects) parts = parts.flatMap((part) => {
|
|
325
|
+
const axis = horizontal(part) ? "x" : vertical(part) ? "y" : void 0;
|
|
326
|
+
if (!axis) return [part];
|
|
327
|
+
const cross$1 = axis === "x" ? "y" : "x";
|
|
328
|
+
const [low, high] = axis === "x" ? [rect.x, rect.x + rect.width] : [rect.y, rect.y + rect.height];
|
|
329
|
+
const [crossLow, crossHigh] = cross$1 === "x" ? [rect.x, rect.x + rect.width] : [rect.y, rect.y + rect.height];
|
|
330
|
+
if (!strictlyInside(part.a[cross$1], crossLow, crossHigh)) return [part];
|
|
331
|
+
const [from, to] = span(part, axis);
|
|
332
|
+
if (overlapLength([from, to], [low, high]) <= EPS) return [part];
|
|
333
|
+
const at = (value) => ({
|
|
334
|
+
...part.a,
|
|
335
|
+
[axis]: value
|
|
336
|
+
});
|
|
337
|
+
return [[from, Math.max(from, low)], [Math.min(to, high), to]].filter(([a, b]) => b - a > EPS).map(([a, b]) => ({
|
|
338
|
+
a: at(a),
|
|
339
|
+
b: at(b)
|
|
340
|
+
}));
|
|
341
|
+
});
|
|
342
|
+
return parts;
|
|
343
|
+
}
|
|
314
344
|
function sharedLength(s, t) {
|
|
315
345
|
if (horizontal(s) && horizontal(t) && Math.abs(s.a.y - t.a.y) < EPS) return overlapLength(span(s, "x"), span(t, "x"));
|
|
316
346
|
if (vertical(s) && vertical(t) && Math.abs(s.a.x - t.a.x) < EPS) return overlapLength(span(s, "y"), span(t, "y"));
|
|
@@ -1017,6 +1047,8 @@ function getElkRoutes(graph, options = {}) {
|
|
|
1017
1047
|
function isLayoutEdgeLabel(label) {
|
|
1018
1048
|
return getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true && (Boolean(label.text) || label.width !== void 0 || label.height !== void 0);
|
|
1019
1049
|
}
|
|
1050
|
+
/** The crossing-model setting a layout passes to crossing minimization. */
|
|
1051
|
+
const crossingModel = Symbol("crossingModel");
|
|
1020
1052
|
var ELK = class {
|
|
1021
1053
|
#options;
|
|
1022
1054
|
#algorithmIds;
|
|
@@ -1091,20 +1123,42 @@ var ELK = class {
|
|
|
1091
1123
|
...this.#options.defaultLayoutOptions,
|
|
1092
1124
|
...arguments_.layoutOptions
|
|
1093
1125
|
};
|
|
1094
|
-
const
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1126
|
+
const seeds = crossingVariants(options, graph);
|
|
1127
|
+
const probe = mayShareTracks(graph, options) ? {
|
|
1128
|
+
mixedPorts: false,
|
|
1129
|
+
sharedTracks: false
|
|
1130
|
+
} : void 0;
|
|
1131
|
+
const pristine = seeds.length || probe ? structuredClone(graph) : void 0;
|
|
1132
|
+
let best = separated(await this.#layoutWithoutDefectiveCompaction(graph, probe ? {
|
|
1133
|
+
...arguments_,
|
|
1134
|
+
[crossingModel]: { probe }
|
|
1135
|
+
} : arguments_), options);
|
|
1097
1136
|
if (!pristine) return best;
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1137
|
+
const reseed = seeds.length > 0 && (thorough(options, graph) || measureLayout(best, true).defects > 0);
|
|
1138
|
+
const variants = [
|
|
1139
|
+
...probe?.mixedPorts ? [{
|
|
1140
|
+
...arguments_,
|
|
1141
|
+
[crossingModel]: { mixedPorts: false }
|
|
1142
|
+
}] : [],
|
|
1143
|
+
...probe?.sharedTracks ? [{
|
|
1144
|
+
...arguments_,
|
|
1145
|
+
[crossingModel]: {
|
|
1146
|
+
mixedPorts: false,
|
|
1147
|
+
separateSharedTracks: true
|
|
1148
|
+
}
|
|
1149
|
+
}] : [],
|
|
1150
|
+
...(reseed ? seeds : []).map((variant) => ({
|
|
1102
1151
|
...arguments_,
|
|
1103
1152
|
layoutOptions: {
|
|
1104
1153
|
...arguments_.layoutOptions,
|
|
1105
1154
|
...variant
|
|
1106
1155
|
}
|
|
1107
|
-
})
|
|
1156
|
+
}))
|
|
1157
|
+
];
|
|
1158
|
+
if (!variants.length) return best;
|
|
1159
|
+
let quality = measureLayout(best);
|
|
1160
|
+
for (const variant of variants) {
|
|
1161
|
+
const candidate = separated(await this.#layoutWithoutDefectiveCompaction(structuredClone(pristine), variant), options);
|
|
1108
1162
|
const candidateQuality = measureLayout(candidate);
|
|
1109
1163
|
if (isBetterLayout(candidateQuality, quality)) {
|
|
1110
1164
|
best = candidate;
|
|
@@ -1119,7 +1173,7 @@ var ELK = class {
|
|
|
1119
1173
|
...arguments_.layoutOptions
|
|
1120
1174
|
}, graph) ? structuredClone(graph) : void 0;
|
|
1121
1175
|
const result = await this.#layout(graph, arguments_);
|
|
1122
|
-
const defects = pristine ? measureLayout(result).defects : 0;
|
|
1176
|
+
const defects = pristine ? measureLayout(result, true).defects : 0;
|
|
1123
1177
|
if (!pristine || defects === 0) return result;
|
|
1124
1178
|
const strategy = { "elk.layered.compaction.postCompaction.strategy": "NONE" };
|
|
1125
1179
|
const uncompacted = await this.#layout(disablePostCompaction(pristine), {
|
|
@@ -1129,7 +1183,7 @@ var ELK = class {
|
|
|
1129
1183
|
...strategy
|
|
1130
1184
|
}
|
|
1131
1185
|
});
|
|
1132
|
-
return measureLayout(uncompacted).defects < defects ? uncompacted : result;
|
|
1186
|
+
return measureLayout(uncompacted, true).defects < defects ? uncompacted : result;
|
|
1133
1187
|
}
|
|
1134
1188
|
async #layout(graph, arguments_ = {}, compoundLayout = false) {
|
|
1135
1189
|
const prepared = await this.#prepareLayout(graph, arguments_, compoundLayout);
|
|
@@ -1382,8 +1436,10 @@ var ELK = class {
|
|
|
1382
1436
|
const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
|
|
1383
1437
|
const sourceInside = descendantOwnerByEndpointId.has(sourceId);
|
|
1384
1438
|
const proxy = proxyFor(sourceInside ? "output" : "input", edge);
|
|
1439
|
+
const labels = (graph.edges ?? []).includes(edge) && !ownPortFor(edge) ? edge.labels?.filter((label) => (getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER") !== "CENTER") : edge.labels;
|
|
1385
1440
|
const segment = {
|
|
1386
1441
|
...edge,
|
|
1442
|
+
labels,
|
|
1387
1443
|
id: `__native_hierarchy_edge_${String(child.id)}_${String(edge.id)}`,
|
|
1388
1444
|
sources: [sourceInside ? sourceId : proxy.ports[0].id],
|
|
1389
1445
|
targets: [sourceInside ? proxy.ports[0].id : targetId],
|
|
@@ -1759,6 +1815,7 @@ var ELK = class {
|
|
|
1759
1815
|
constraints: { layer: () => void 0 },
|
|
1760
1816
|
settings: {
|
|
1761
1817
|
...getLayeredSettings(layoutOptions),
|
|
1818
|
+
...arguments_[crossingModel] ? { [CROSSING_MODEL]: arguments_[crossingModel] } : {},
|
|
1762
1819
|
...compoundLayout || hasHierarchy && !separateHierarchy && !topdownLayout ? { separateConnectedComponents: false } : {},
|
|
1763
1820
|
...hierarchyHandling === "INCLUDE_CHILDREN" ? { "crossingMinimization.greedySwitch.type": String(getOption(layoutOptions, "layered.crossingMinimization.greedySwitchHierarchical.type") ?? "TWO_SIDED") } : {}
|
|
1764
1821
|
},
|
|
@@ -2030,7 +2087,7 @@ function coordinatePreparedScopes(root) {
|
|
|
2030
2087
|
const outputPorts = ports.filter((port) => parent.phase.input.portSettings?.(port, nativeParent)?.["port.side"] === flowSides.after).length;
|
|
2031
2088
|
const childScope = {
|
|
2032
2089
|
...scopes.get(child),
|
|
2033
|
-
useBottomUp: useBottomUpHierarchySweep(childPhase.input,
|
|
2090
|
+
useBottomUp: useBottomUpHierarchySweep(childPhase.input, childSession.crossingGraph(), {
|
|
2034
2091
|
portOrderFixed: parentPortOrderFixed,
|
|
2035
2092
|
inputPorts,
|
|
2036
2093
|
outputPorts,
|
|
@@ -2909,9 +2966,16 @@ function elkBoundaryEdgeOrder(group, scope, originals) {
|
|
|
2909
2966
|
for (const child of node.children ?? []) if (child.children?.length) {
|
|
2910
2967
|
for (const edge of order(child)) if (leaves(edge)) result.add(edge);
|
|
2911
2968
|
}
|
|
2912
|
-
for (const child of node.children ?? [])
|
|
2913
|
-
for (const
|
|
2914
|
-
|
|
2969
|
+
for (const child of node.children ?? []) {
|
|
2970
|
+
for (const port of (child.ports ?? []).map((p) => String(p.id))) {
|
|
2971
|
+
for (const edge of edges) if (endpoints(edge).sourceId === port && leaves(edge)) result.add(edge);
|
|
2972
|
+
for (const edge of edges) if (endpoints(edge).targetId === port && leaves(edge)) result.add(edge);
|
|
2973
|
+
}
|
|
2974
|
+
const id = String(child.id);
|
|
2975
|
+
for (const edge of edges) {
|
|
2976
|
+
const { sourceId, targetId } = endpoints(edge);
|
|
2977
|
+
if ((sourceId === id || targetId === id) && leaves(edge)) result.add(edge);
|
|
2978
|
+
}
|
|
2915
2979
|
}
|
|
2916
2980
|
return [...result];
|
|
2917
2981
|
};
|
|
@@ -2959,6 +3023,44 @@ function crossingVariants(options, graph) {
|
|
|
2959
3023
|
if (!(graph.children ?? []).some((child) => child.children?.length) || getOption(own, "hierarchyHandling") !== "INCLUDE_CHILDREN" || getOption(own, "layered.crossingMinimization.greedySwitchHierarchical.type") !== void 0) return [];
|
|
2960
3024
|
return [{ "elk.randomSeed": "2" }, { "elk.randomSeed": "3" }];
|
|
2961
3025
|
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Whether two edges between different ends can share a port: an explicit port
|
|
3028
|
+
* carrying two or more of them, or implicit ports that merge because some
|
|
3029
|
+
* scope merges edges or a node is a hypernode.
|
|
3030
|
+
*/
|
|
3031
|
+
function mayShareTracks(graph, options) {
|
|
3032
|
+
const merging = (node, inherited = {}) => {
|
|
3033
|
+
const settings = getElementLayeredSettings({
|
|
3034
|
+
...inherited,
|
|
3035
|
+
...node.properties,
|
|
3036
|
+
...node.layoutOptions
|
|
3037
|
+
});
|
|
3038
|
+
return settings.mergeEdges === true || settings.hypernode === true;
|
|
3039
|
+
};
|
|
3040
|
+
const anyMerging = (node) => merging(node) || (node.children ?? []).some(anyMerging);
|
|
3041
|
+
if (merging(graph, options) || (graph.children ?? []).some(anyMerging)) return true;
|
|
3042
|
+
const ports = /* @__PURE__ */ new Set();
|
|
3043
|
+
const collectPorts = (node) => {
|
|
3044
|
+
for (const port of node.ports ?? []) ports.add(String(port.id));
|
|
3045
|
+
for (const child of node.children ?? []) collectPorts(child);
|
|
3046
|
+
};
|
|
3047
|
+
collectPorts(graph);
|
|
3048
|
+
if (!ports.size) return false;
|
|
3049
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3050
|
+
const visit = (node) => {
|
|
3051
|
+
for (const edge of node.edges ?? []) {
|
|
3052
|
+
const source = String(edge.sources?.[0] ?? edge.source), target = String(edge.targets?.[0] ?? edge.target);
|
|
3053
|
+
if (source === target) continue;
|
|
3054
|
+
for (const end of [source, target]) {
|
|
3055
|
+
if (!ports.has(end)) continue;
|
|
3056
|
+
if (seen.has(end)) return true;
|
|
3057
|
+
seen.add(end);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
return (node.children ?? []).some(visit);
|
|
3061
|
+
};
|
|
3062
|
+
return visit(graph);
|
|
3063
|
+
}
|
|
2962
3064
|
/** Whether the graph asks for more than ELK's default layered thoroughness (7). */
|
|
2963
3065
|
function thorough(options, graph) {
|
|
2964
3066
|
const own = {
|