@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
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-BJu8A21s.cjs');
|
|
3
|
+
const require_elkjs = require('../elkjs-CnBvdyBu.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-BsAdm_u8.mjs";
|
|
2
|
+
import { a as scoreStatechartLayout, i as layoutStatechart, n as getElkRoutes, r as compileStatechartLayout, t as ELK } from "../elkjs-CSi7oB5J.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-BJu8A21s.cjs');
|
|
2
|
+
const require_elkjs = require('../elkjs-CnBvdyBu.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-BsAdm_u8.mjs";
|
|
2
|
+
import { t as ELK } from "../elkjs-CSi7oB5J.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-BsAdm_u8.mjs";
|
|
3
|
+
import { n as executeElkjs0111Layout } from "./layout-engine-DnlmGxHL.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],
|
|
@@ -1749,6 +1805,13 @@ var ELK = class {
|
|
|
1749
1805
|
return (sourceConstraint === "FIRST" || sourceConstraint === "FIRST_SEPARATE") && (targetConstraint === "FIRST" || targetConstraint === "FIRST_SEPARATE");
|
|
1750
1806
|
})) throw new Error("org.eclipse.elk.core.UnsupportedConfigurationException: Layer constraints conflict");
|
|
1751
1807
|
const padding = parsePadding(getOption(layoutOptions, "padding"), algorithm === "layered" ? 12 : 0);
|
|
1808
|
+
const parsedSettings = /* @__PURE__ */ new WeakMap();
|
|
1809
|
+
const elementSettings = (elementOptions) => {
|
|
1810
|
+
const key = elementOptions ?? noElementOptions;
|
|
1811
|
+
let settings = parsedSettings.get(key);
|
|
1812
|
+
if (!settings) parsedSettings.set(key, settings = getElementLayeredSettings(key));
|
|
1813
|
+
return settings;
|
|
1814
|
+
};
|
|
1752
1815
|
const options = {
|
|
1753
1816
|
direction: getDirection(layoutOptions),
|
|
1754
1817
|
spacing: {
|
|
@@ -1759,18 +1822,19 @@ var ELK = class {
|
|
|
1759
1822
|
constraints: { layer: () => void 0 },
|
|
1760
1823
|
settings: {
|
|
1761
1824
|
...getLayeredSettings(layoutOptions),
|
|
1825
|
+
...arguments_[crossingModel] ? { [CROSSING_MODEL]: arguments_[crossingModel] } : {},
|
|
1762
1826
|
...compoundLayout || hasHierarchy && !separateHierarchy && !topdownLayout ? { separateConnectedComponents: false } : {},
|
|
1763
1827
|
...hierarchyHandling === "INCLUDE_CHILDREN" ? { "crossingMinimization.greedySwitch.type": String(getOption(layoutOptions, "layered.crossingMinimization.greedySwitchHierarchical.type") ?? "TWO_SIDED") } : {}
|
|
1764
1828
|
},
|
|
1765
1829
|
nodeSettings: (node) => {
|
|
1766
1830
|
const child = findById(graph.children, node.id)?.item;
|
|
1767
|
-
return
|
|
1831
|
+
return elementSettings(child?.layoutOptions);
|
|
1768
1832
|
},
|
|
1769
1833
|
edgeSettings: (edge) => {
|
|
1770
1834
|
const elkEdge = findById(graph.edges, edge.id)?.item;
|
|
1771
1835
|
return {
|
|
1772
|
-
...
|
|
1773
|
-
...
|
|
1836
|
+
...elementSettings(elkEdge?.layoutOptions),
|
|
1837
|
+
...elementSettings(elkEdge?.labels?.[0]?.layoutOptions),
|
|
1774
1838
|
"edge.hierarchyRank": elkEdge && hierarchySegmentRank.get(elkEdge)
|
|
1775
1839
|
};
|
|
1776
1840
|
},
|
|
@@ -1779,7 +1843,7 @@ var ELK = class {
|
|
|
1779
1843
|
const found = findById(child?.ports, port.name);
|
|
1780
1844
|
const elkPort = found?.item;
|
|
1781
1845
|
return {
|
|
1782
|
-
...
|
|
1846
|
+
...elementSettings(elkPort?.layoutOptions),
|
|
1783
1847
|
"port.authoredIndex": found?.position,
|
|
1784
1848
|
"port.labelWidth": Math.max(0, ...(elkPort?.labels ?? []).map((label) => label.width ?? 0)),
|
|
1785
1849
|
"port.labelHeight": Math.max(0, ...(elkPort?.labels ?? []).map((label) => label.height ?? 0))
|
|
@@ -2030,7 +2094,7 @@ function coordinatePreparedScopes(root) {
|
|
|
2030
2094
|
const outputPorts = ports.filter((port) => parent.phase.input.portSettings?.(port, nativeParent)?.["port.side"] === flowSides.after).length;
|
|
2031
2095
|
const childScope = {
|
|
2032
2096
|
...scopes.get(child),
|
|
2033
|
-
useBottomUp: useBottomUpHierarchySweep(childPhase.input,
|
|
2097
|
+
useBottomUp: useBottomUpHierarchySweep(childPhase.input, childSession.crossingGraph(), {
|
|
2034
2098
|
portOrderFixed: parentPortOrderFixed,
|
|
2035
2099
|
inputPorts,
|
|
2036
2100
|
outputPorts,
|
|
@@ -2245,12 +2309,14 @@ function applyNodeMicroLayout(graph, globalOptions) {
|
|
|
2245
2309
|
node.height = height;
|
|
2246
2310
|
}
|
|
2247
2311
|
}
|
|
2312
|
+
const exactKeysBySuffix = /* @__PURE__ */ new Map();
|
|
2248
2313
|
function getOption(options, suffix) {
|
|
2249
|
-
|
|
2314
|
+
let exactKeys = exactKeysBySuffix.get(suffix);
|
|
2315
|
+
if (!exactKeys) exactKeysBySuffix.set(suffix, exactKeys = [
|
|
2250
2316
|
suffix,
|
|
2251
2317
|
`elk.${suffix}`,
|
|
2252
2318
|
`org.eclipse.elk.${suffix}`
|
|
2253
|
-
];
|
|
2319
|
+
]);
|
|
2254
2320
|
for (const key of exactKeys) if (options[key] !== void 0) return options[key];
|
|
2255
2321
|
}
|
|
2256
2322
|
const ergonomicallyMappedLayeredSettings = new Set([
|
|
@@ -2329,6 +2395,7 @@ function findById(items, id) {
|
|
|
2329
2395
|
position
|
|
2330
2396
|
};
|
|
2331
2397
|
}
|
|
2398
|
+
const noElementOptions = {};
|
|
2332
2399
|
function getElementLayeredSettings(options) {
|
|
2333
2400
|
const settings = {};
|
|
2334
2401
|
const chosen = /* @__PURE__ */ new Map();
|
|
@@ -2909,9 +2976,16 @@ function elkBoundaryEdgeOrder(group, scope, originals) {
|
|
|
2909
2976
|
for (const child of node.children ?? []) if (child.children?.length) {
|
|
2910
2977
|
for (const edge of order(child)) if (leaves(edge)) result.add(edge);
|
|
2911
2978
|
}
|
|
2912
|
-
for (const child of node.children ?? [])
|
|
2913
|
-
for (const
|
|
2914
|
-
|
|
2979
|
+
for (const child of node.children ?? []) {
|
|
2980
|
+
for (const port of (child.ports ?? []).map((p) => String(p.id))) {
|
|
2981
|
+
for (const edge of edges) if (endpoints(edge).sourceId === port && leaves(edge)) result.add(edge);
|
|
2982
|
+
for (const edge of edges) if (endpoints(edge).targetId === port && leaves(edge)) result.add(edge);
|
|
2983
|
+
}
|
|
2984
|
+
const id = String(child.id);
|
|
2985
|
+
for (const edge of edges) {
|
|
2986
|
+
const { sourceId, targetId } = endpoints(edge);
|
|
2987
|
+
if ((sourceId === id || targetId === id) && leaves(edge)) result.add(edge);
|
|
2988
|
+
}
|
|
2915
2989
|
}
|
|
2916
2990
|
return [...result];
|
|
2917
2991
|
};
|
|
@@ -2959,6 +3033,44 @@ function crossingVariants(options, graph) {
|
|
|
2959
3033
|
if (!(graph.children ?? []).some((child) => child.children?.length) || getOption(own, "hierarchyHandling") !== "INCLUDE_CHILDREN" || getOption(own, "layered.crossingMinimization.greedySwitchHierarchical.type") !== void 0) return [];
|
|
2960
3034
|
return [{ "elk.randomSeed": "2" }, { "elk.randomSeed": "3" }];
|
|
2961
3035
|
}
|
|
3036
|
+
/**
|
|
3037
|
+
* Whether two edges between different ends can share a port: an explicit port
|
|
3038
|
+
* carrying two or more of them, or implicit ports that merge because some
|
|
3039
|
+
* scope merges edges or a node is a hypernode.
|
|
3040
|
+
*/
|
|
3041
|
+
function mayShareTracks(graph, options) {
|
|
3042
|
+
const merging = (node, inherited = {}) => {
|
|
3043
|
+
const settings = getElementLayeredSettings({
|
|
3044
|
+
...inherited,
|
|
3045
|
+
...node.properties,
|
|
3046
|
+
...node.layoutOptions
|
|
3047
|
+
});
|
|
3048
|
+
return settings.mergeEdges === true || settings.hypernode === true;
|
|
3049
|
+
};
|
|
3050
|
+
const anyMerging = (node) => merging(node) || (node.children ?? []).some(anyMerging);
|
|
3051
|
+
if (merging(graph, options) || (graph.children ?? []).some(anyMerging)) return true;
|
|
3052
|
+
const ports = /* @__PURE__ */ new Set();
|
|
3053
|
+
const collectPorts = (node) => {
|
|
3054
|
+
for (const port of node.ports ?? []) ports.add(String(port.id));
|
|
3055
|
+
for (const child of node.children ?? []) collectPorts(child);
|
|
3056
|
+
};
|
|
3057
|
+
collectPorts(graph);
|
|
3058
|
+
if (!ports.size) return false;
|
|
3059
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3060
|
+
const visit = (node) => {
|
|
3061
|
+
for (const edge of node.edges ?? []) {
|
|
3062
|
+
const source = String(edge.sources?.[0] ?? edge.source), target = String(edge.targets?.[0] ?? edge.target);
|
|
3063
|
+
if (source === target) continue;
|
|
3064
|
+
for (const end of [source, target]) {
|
|
3065
|
+
if (!ports.has(end)) continue;
|
|
3066
|
+
if (seen.has(end)) return true;
|
|
3067
|
+
seen.add(end);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
return (node.children ?? []).some(visit);
|
|
3071
|
+
};
|
|
3072
|
+
return visit(graph);
|
|
3073
|
+
}
|
|
2962
3074
|
/** Whether the graph asks for more than ELK's default layered thoroughness (7). */
|
|
2963
3075
|
function thorough(options, graph) {
|
|
2964
3076
|
const own = {
|