@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.
Files changed (33) hide show
  1. package/dist/elkjs/index.cjs +2 -2
  2. package/dist/elkjs/index.mjs +2 -2
  3. package/dist/elkjs/main.cjs +1 -1
  4. package/dist/elkjs/main.mjs +1 -1
  5. package/dist/elkjs/worker.cjs +2 -2
  6. package/dist/elkjs/worker.mjs +2 -2
  7. package/dist/{elkjs-UciYx7lD.mjs → elkjs-CSi7oB5J.mjs} +181 -69
  8. package/dist/{elkjs-ggjsBuVP.cjs → elkjs-CnBvdyBu.cjs} +181 -69
  9. package/dist/index.cjs +2 -2
  10. package/dist/index.mjs +2 -2
  11. package/dist/layered/index.cjs +1 -1
  12. package/dist/layered/index.mjs +1 -1
  13. package/dist/{layered-CBPg6_6_.cjs → layered-BJu8A21s.cjs} +474 -158
  14. package/dist/{layered-C7ERzZjR.mjs → layered-BsAdm_u8.mjs} +469 -153
  15. package/dist/{layout-engine-DAw06RhN.cjs → layout-engine-BFP1TZbF.cjs} +1 -1
  16. package/dist/{layout-engine-DC3IYkIy.mjs → layout-engine-DnlmGxHL.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/src/elkjs/index.ts +135 -33
  19. package/src/elkjs/layout-quality.ts +81 -33
  20. package/src/layered/bk-node-placement.ts +10 -7
  21. package/src/layered/crossing-counter.ts +27 -9
  22. package/src/layered/crossing-graph.ts +2 -2
  23. package/src/layered/greedy-switch-decider.ts +44 -17
  24. package/src/layered/hierarchy-sweepiness.ts +37 -41
  25. package/src/layered/index.ts +15 -21
  26. package/src/layered/loop-envelopes.ts +10 -4
  27. package/src/layered/node-lookup.ts +29 -0
  28. package/src/layered/opposing-tracks.ts +108 -8
  29. package/src/layered/orthogonal-hypersegments.ts +69 -7
  30. package/src/layered/port-distributor.ts +26 -14
  31. package/src/layered/port-edge-order.ts +2 -1
  32. package/src/layered/spacing.ts +4 -3
  33. package/src/layered/strategies.ts +288 -111
@@ -1,30 +1,38 @@
1
1
  const require_routing = require('./routing-QIdGGFBe.cjs');
2
- const require_layered = require('./layered-CBPg6_6_.cjs');
3
- const require_layout_engine = require('./layout-engine-DAw06RhN.cjs');
2
+ const require_layered = require('./layered-BJu8A21s.cjs');
3
+ const require_layout_engine = require('./layout-engine-BFP1TZbF.cjs');
4
4
  let _statelyai_graph = require("@statelyai/graph");
5
5
 
6
6
  //#region src/layered/hierarchy-sweepiness.ts
7
- function useBottomUpHierarchySweep(input, orientation, layers, parent) {
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 infos = new Map(input.graph.nodes.map((node) => [node.id, {
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 = require_layered.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 passEdges = (id) => {
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 id of layer) {
49
- if (id.startsWith("__layout_dummy:north-south:")) {
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 node = nodes.get(id), state = infos.get(id);
54
- const external = require_layered.externalPortDummyOf(node);
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 = require_layered.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 (!incoming.get(id)?.length) state.random = 1;
59
- else if (!outgoing.get(id)?.length) randomPaths += state.edges;
60
- passEdges(id);
61
- for (const dummy of units?.associates.get(id) ?? []) {
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) passEdges(id);
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
- const inRect = (point, rect, margin) => point.x > rect.x - margin && point.x < rect.x + rect.width + margin && point.y > rect.y - margin && point.y < rect.y + rect.height + margin;
170
- /** Measure an ELK JSON result in absolute coordinates. */
171
- function measureLayout(root) {
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
- for (const s of first.segments) for (const t of second.segments) {
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
- const v = h === s ? t : s;
292
- const point = {
293
- x: v.a.x,
294
- y: h.a.y
295
- };
296
- if (strictlyInside(point.x, ...span(h, "x")) && strictlyInside(point.y, ...span(v, "y")) && clear(point)) crossings++;
297
- continue;
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 variants = crossingVariants(options, graph);
1095
- const pristine = variants.length ? structuredClone(graph) : void 0;
1096
- let best = separated(await this.#layoutWithoutDefectiveCompaction(graph, arguments_), options);
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
- let quality = measureLayout(best);
1099
- if (quality.defects === 0 && !thorough(options, graph)) return best;
1100
- for (const variant of variants) {
1101
- const candidate = separated(await this.#layoutWithoutDefectiveCompaction(structuredClone(pristine), {
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
- }), options);
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] ? { [require_layered.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 getElementLayeredSettings(child?.layoutOptions ?? {});
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
- ...getElementLayeredSettings(elkEdge?.layoutOptions ?? {}),
1773
- ...getElementLayeredSettings(elkEdge?.labels?.[0]?.layoutOptions ?? {}),
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
- ...getElementLayeredSettings(elkPort?.layoutOptions ?? {}),
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, childPhase.orientation, childSession.snapshot().layers, {
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
- const exactKeys = [
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 ?? []) for (const port of [...(child.ports ?? []).map((p) => String(p.id)), String(child.id)]) {
2913
- for (const edge of edges) if (endpoints(edge).sourceId === port && leaves(edge)) result.add(edge);
2914
- for (const edge of edges) if (endpoints(edge).targetId === port && leaves(edge)) result.add(edge);
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 = {
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  const require_routing = require('./routing-QIdGGFBe.cjs');
2
- const require_layered = require('./layered-CBPg6_6_.cjs');
3
- const require_layout_engine = require('./layout-engine-DAw06RhN.cjs');
2
+ const require_layered = require('./layered-BJu8A21s.cjs');
3
+ const require_layout_engine = require('./layout-engine-BFP1TZbF.cjs');
4
4
  let _statelyai_graph = require("@statelyai/graph");
5
5
 
6
6
  //#region src/layout.ts
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { C as getPointAtLength, D as toSvgPath, E as roundCorners, F as LayoutError, I as UnsupportedLayoutError, S as getPathLength, T as pathFromPoints, _ as routeToGraphPatch, a as fanRouting, b as flattenPath, c as orthogonalRouting, d as routingStrategies, f as selfLoopRouting, g as pathFromSplinePoints, h as getLayoutRoutes, i as curvedRouting, l as parallelRouting, m as applyRoutePatches, n as bundleRouting, o as octilinearRouting, p as straightRouting, r as busRouting, s as organicRouting, t as bezierRouting, u as polylineRouting, v as routeToPoints, w as getTangentAtLength, x as getPathBounds, y as routeToPolylines } from "./routing-CR8flv8b.mjs";
2
- import { A as breakCyclesByStronglyConnectedNodeType, B as minimizeCrossingsWithBarycenter, C as assignLayersByDepthFirstModelOrder, D as assignLayersWithCoffmanGraham, E as assignLayersInteractively, F as breakCyclesWithModelOrderBreadthFirstSearch, H as minimizeCrossingsWithModelOrder, I as breakCyclesWithModelOrderDepthFirstSearch, J as routeEdgesWithSplines, K as routeEdgesOrthogonally, M as breakCyclesGreedilyByModelOrder, N as breakCyclesInteractively, O as breakCyclesByModelOrder, P as breakCyclesWithDepthFirstSearch, S as assignLayersByBreadthFirstModelOrder, T as assignLayersByLongestPathToSink, U as placeNodesInLayers, V as minimizeCrossingsWithMedian, W as placeNodesInteractively, Z as assignLayersWithNetworkSimplex, _ as statechartHints, a as fromElkLayeredOptionId, c as elkLayeredOptionDefinitions, f as assignLayersWithStretchWidth, g as hint, i as layeredAlgorithm, j as breakCyclesGreedily, k as breakCyclesByStronglyConnectedConnectivity, m as c, o as toElkLayeredOptions, p as assignLayersWithMinWidth, q as routeEdgesWithPolylines, r as getLayeredLayout, s as elkLayeredEnumValues, w as assignLayersByLongestPath, z as minimizeCrossingsInteractively } from "./layered-C7ERzZjR.mjs";
3
- import { a as getSporeCompactionLayout, c as sporeOverlapRemovalAlgorithm, d as getRectanglePackingLayout, f as rectanglePackingAlgorithm, g as getFixedLayout, h as fixedAlgorithm, i as getBuiltInLayoutAlgorithm, l as getRandomLayout, m as getBoxLayout, o as getSporeOverlapRemovalLayout, p as boxAlgorithm, r as executeLayoutAlgorithm, s as sporeCompactionAlgorithm, t as builtInLayoutAlgorithms, u as randomAlgorithm } from "./layout-engine-DC3IYkIy.mjs";
2
+ import { A as breakCyclesByStronglyConnectedConnectivity, B as minimizeCrossingsInteractively, C as assignLayersByBreadthFirstModelOrder, D as assignLayersInteractively, E as assignLayersByLongestPathToSink, F as breakCyclesWithDepthFirstSearch, G as placeNodesInteractively, H as minimizeCrossingsWithMedian, I as breakCyclesWithModelOrderBreadthFirstSearch, J as routeEdgesWithPolylines, L as breakCyclesWithModelOrderDepthFirstSearch, M as breakCyclesGreedily, N as breakCyclesGreedilyByModelOrder, O as assignLayersWithCoffmanGraham, P as breakCyclesInteractively, Q as assignLayersWithNetworkSimplex, T as assignLayersByLongestPath, U as minimizeCrossingsWithModelOrder, V as minimizeCrossingsWithBarycenter, W as placeNodesInLayers, Y as routeEdgesWithSplines, _ as statechartHints, a as fromElkLayeredOptionId, c as elkLayeredOptionDefinitions, f as assignLayersWithStretchWidth, g as hint, i as layeredAlgorithm, j as breakCyclesByStronglyConnectedNodeType, k as breakCyclesByModelOrder, m as c, o as toElkLayeredOptions, p as assignLayersWithMinWidth, q as routeEdgesOrthogonally, r as getLayeredLayout, s as elkLayeredEnumValues, w as assignLayersByDepthFirstModelOrder } from "./layered-BsAdm_u8.mjs";
3
+ import { a as getSporeCompactionLayout, c as sporeOverlapRemovalAlgorithm, d as getRectanglePackingLayout, f as rectanglePackingAlgorithm, g as getFixedLayout, h as fixedAlgorithm, i as getBuiltInLayoutAlgorithm, l as getRandomLayout, m as getBoxLayout, o as getSporeOverlapRemovalLayout, p as boxAlgorithm, r as executeLayoutAlgorithm, s as sporeCompactionAlgorithm, t as builtInLayoutAlgorithms, u as randomAlgorithm } from "./layout-engine-DnlmGxHL.mjs";
4
4
  import { getGraphIssues } from "@statelyai/graph";
5
5
 
6
6
  //#region src/layout.ts
@@ -1,4 +1,4 @@
1
- const require_layered = require('../layered-CBPg6_6_.cjs');
1
+ const require_layered = require('../layered-BJu8A21s.cjs');
2
2
 
3
3
  exports.assignLayersByBreadthFirstModelOrder = require_layered.assignLayersByBreadthFirstModelOrder;
4
4
  exports.assignLayersByDepthFirstModelOrder = require_layered.assignLayersByDepthFirstModelOrder;
@@ -1,3 +1,3 @@
1
- import { A as breakCyclesByStronglyConnectedNodeType, B as minimizeCrossingsWithBarycenter, C as assignLayersByDepthFirstModelOrder, D as assignLayersWithCoffmanGraham, E as assignLayersInteractively, F as breakCyclesWithModelOrderBreadthFirstSearch, H as minimizeCrossingsWithModelOrder, I as breakCyclesWithModelOrderDepthFirstSearch, J as routeEdgesWithSplines, K as routeEdgesOrthogonally, M as breakCyclesGreedilyByModelOrder, N as breakCyclesInteractively, O as breakCyclesByModelOrder, P as breakCyclesWithDepthFirstSearch, S as assignLayersByBreadthFirstModelOrder, T as assignLayersByLongestPathToSink, U as placeNodesInLayers, V as minimizeCrossingsWithMedian, W as placeNodesInteractively, Z as assignLayersWithNetworkSimplex, a as fromElkLayeredOptionId, c as elkLayeredOptionDefinitions, d as placeNodesWithBrandesKoepf, f as assignLayersWithStretchWidth, i as layeredAlgorithm, j as breakCyclesGreedily, k as breakCyclesByStronglyConnectedConnectivity, l as placeNodesWithNetworkSimplex, n as elkjs0111LayeredAlgorithm, o as toElkLayeredOptions, p as assignLayersWithMinWidth, q as routeEdgesWithPolylines, r as getLayeredLayout, s as elkLayeredEnumValues, t as createLayeredScopePipeline, u as placeNodesWithLinearSegments, w as assignLayersByLongestPath, z as minimizeCrossingsInteractively } from "../layered-C7ERzZjR.mjs";
1
+ import { A as breakCyclesByStronglyConnectedConnectivity, B as minimizeCrossingsInteractively, C as assignLayersByBreadthFirstModelOrder, D as assignLayersInteractively, E as assignLayersByLongestPathToSink, F as breakCyclesWithDepthFirstSearch, G as placeNodesInteractively, H as minimizeCrossingsWithMedian, I as breakCyclesWithModelOrderBreadthFirstSearch, J as routeEdgesWithPolylines, L as breakCyclesWithModelOrderDepthFirstSearch, M as breakCyclesGreedily, N as breakCyclesGreedilyByModelOrder, O as assignLayersWithCoffmanGraham, P as breakCyclesInteractively, Q as assignLayersWithNetworkSimplex, T as assignLayersByLongestPath, U as minimizeCrossingsWithModelOrder, V as minimizeCrossingsWithBarycenter, W as placeNodesInLayers, Y as routeEdgesWithSplines, a as fromElkLayeredOptionId, c as elkLayeredOptionDefinitions, d as placeNodesWithBrandesKoepf, f as assignLayersWithStretchWidth, i as layeredAlgorithm, j as breakCyclesByStronglyConnectedNodeType, k as breakCyclesByModelOrder, l as placeNodesWithNetworkSimplex, n as elkjs0111LayeredAlgorithm, o as toElkLayeredOptions, p as assignLayersWithMinWidth, q as routeEdgesOrthogonally, r as getLayeredLayout, s as elkLayeredEnumValues, t as createLayeredScopePipeline, u as placeNodesWithLinearSegments, w as assignLayersByDepthFirstModelOrder } from "../layered-BsAdm_u8.mjs";
2
2
 
3
3
  export { assignLayersByBreadthFirstModelOrder, assignLayersByDepthFirstModelOrder, assignLayersByLongestPath, assignLayersByLongestPathToSink, assignLayersInteractively, assignLayersWithCoffmanGraham, assignLayersWithMinWidth, assignLayersWithNetworkSimplex, assignLayersWithStretchWidth, breakCyclesByModelOrder, breakCyclesByStronglyConnectedConnectivity, breakCyclesByStronglyConnectedNodeType, breakCyclesGreedily, breakCyclesGreedilyByModelOrder, breakCyclesInteractively, breakCyclesWithDepthFirstSearch, breakCyclesWithModelOrderBreadthFirstSearch, breakCyclesWithModelOrderDepthFirstSearch, createLayeredScopePipeline, elkLayeredEnumValues, elkLayeredOptionDefinitions, elkjs0111LayeredAlgorithm, fromElkLayeredOptionId, getLayeredLayout, layeredAlgorithm, minimizeCrossingsInteractively, minimizeCrossingsWithBarycenter, minimizeCrossingsWithMedian, minimizeCrossingsWithModelOrder, placeNodesInLayers, placeNodesInteractively, placeNodesWithBrandesKoepf, placeNodesWithLinearSegments, placeNodesWithNetworkSimplex, routeEdgesOrthogonally, routeEdgesWithPolylines, routeEdgesWithSplines, toElkLayeredOptions };