@statelyai/layout 0.2.0 → 0.3.0

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 (67) hide show
  1. package/README.md +71 -2
  2. package/dist/elkjs/index.cjs +3 -2
  3. package/dist/elkjs/index.d.cts +11 -2
  4. package/dist/elkjs/index.d.mts +11 -2
  5. package/dist/elkjs/index.mjs +3 -3
  6. package/dist/elkjs/main.cjs +2 -2
  7. package/dist/elkjs/main.d.cts +2 -2
  8. package/dist/elkjs/main.d.mts +2 -2
  9. package/dist/elkjs/main.mjs +2 -2
  10. package/dist/elkjs/worker-api.cjs +1 -1
  11. package/dist/elkjs/worker-api.d.cts +2 -2
  12. package/dist/elkjs/worker-api.d.mts +2 -2
  13. package/dist/elkjs/worker-api.mjs +1 -1
  14. package/dist/elkjs/worker.cjs +2 -2
  15. package/dist/elkjs/worker.mjs +2 -2
  16. package/dist/{elkjs-CFlfkEBz.mjs → elkjs-DaHMISnQ.mjs} +152 -3
  17. package/dist/{elkjs-BVnDwnBQ.cjs → elkjs-dMghiiWW.cjs} +157 -2
  18. package/dist/index-BEA_pzeT.d.cts +72 -0
  19. package/dist/{index-BSH2bloA.d.mts → index-D9dhxrRl.d.cts} +35 -3
  20. package/dist/index-DNTLRsDl.d.mts +72 -0
  21. package/dist/{index-CG712jzV.d.cts → index-DNixtE3h.d.mts} +35 -3
  22. package/dist/index.cjs +36 -9
  23. package/dist/index.d.cts +4 -3
  24. package/dist/index.d.mts +4 -3
  25. package/dist/index.mjs +4 -3
  26. package/dist/layered/index.cjs +1 -1
  27. package/dist/layered/index.d.cts +3 -2
  28. package/dist/layered/index.d.mts +3 -2
  29. package/dist/layered/index.mjs +1 -1
  30. package/dist/{layered-Cf9Rn8l7.cjs → layered-B2buJqMf.cjs} +417 -469
  31. package/dist/{layered-Bz2nWmVp.mjs → layered-DGFTeQSl.mjs} +391 -431
  32. package/dist/{layout-engine-WbC0YjjT.mjs → layout-engine-Cs5tatA6.mjs} +1 -1
  33. package/dist/{layout-engine-BNex9jxg.cjs → layout-engine-Dn1PiFwP.cjs} +1 -1
  34. package/dist/routing/index.cjs +28 -0
  35. package/dist/routing/index.d.cts +3 -0
  36. package/dist/routing/index.d.mts +3 -0
  37. package/dist/routing/index.mjs +3 -0
  38. package/dist/routing-A2hrK9MP.mjs +2396 -0
  39. package/dist/routing-ujEP7Qh6.cjs +2606 -0
  40. package/dist/types-CT2akdK0.d.mts +133 -0
  41. package/dist/types-uxPlGRof.d.cts +133 -0
  42. package/dist/{worker-client-DAT_z2OR.d.mts → worker-client-BwGGRD5y.d.mts} +1 -1
  43. package/dist/{worker-client-DVhklF6A.d.cts → worker-client-D4zNyPEI.d.cts} +1 -1
  44. package/package.json +7 -2
  45. package/src/authoring/coordinates.ts +10 -2
  46. package/src/elkjs/index.ts +5 -3
  47. package/src/elkjs/routes.ts +147 -0
  48. package/src/index.ts +6 -0
  49. package/src/layered/compound.ts +417 -0
  50. package/src/layered/index.ts +27 -102
  51. package/src/layered/types.ts +21 -0
  52. package/src/routing/adapters.ts +111 -0
  53. package/src/routing/algorithms.ts +676 -0
  54. package/src/routing/coordination.ts +86 -0
  55. package/src/routing/engine.ts +473 -0
  56. package/src/routing/index.ts +68 -0
  57. package/src/routing/layout-cache.ts +65 -0
  58. package/src/routing/model.ts +218 -0
  59. package/src/routing/path.ts +324 -0
  60. package/src/routing/persistent.ts +117 -0
  61. package/src/routing/search.ts +276 -0
  62. package/src/routing/spatial.ts +78 -0
  63. package/src/routing/types.ts +146 -0
  64. /package/dist/{public-types-B8rAALJq.d.mts → public-types-9NCPoqbp.d.mts} +0 -0
  65. /package/dist/{public-types-CVB0hXGk.d.cts → public-types-BcRZoJE3.d.cts} +0 -0
  66. /package/dist/{worker-client-DkwS4sHx.mjs → worker-client-BNjwT1AJ.mjs} +0 -0
  67. /package/dist/{worker-client-D3HMK33r.cjs → worker-client-D4oEvhAl.cjs} +0 -0
@@ -1,21 +1,6 @@
1
+ import { F as UnsupportedLayoutError, M as routeCrosses, N as routeOrthogonal, P as LayoutError, _ as routeToPolylines, b as recordRouteGeometry, c as orthogonalRouting, j as overlaps, v as nativeGeometry, y as worldGeometry } from "./routing-A2hrK9MP.mjs";
1
2
  import { getNodeSize } from "@statelyai/graph/layout";
2
3
 
3
- //#region src/errors.ts
4
- var LayoutError = class extends Error {
5
- constructor(message, code) {
6
- super(message);
7
- this.code = code;
8
- this.name = "LayoutError";
9
- }
10
- };
11
- var UnsupportedLayoutError = class extends LayoutError {
12
- constructor(message) {
13
- super(message, "UNSUPPORTED_LAYOUT");
14
- this.name = "UnsupportedLayoutError";
15
- }
16
- };
17
-
18
- //#endregion
19
4
  //#region src/java-random.ts
20
5
  /** Java's 48-bit `java.util.Random`, used by ELK for seeded tie-breaking. */
21
6
  var JavaRandom = class JavaRandom {
@@ -2229,15 +2214,15 @@ function implicitEdgeEndpoints(input, placement, orientation) {
2229
2214
  const hypernode = nodeSettings?.hypernode;
2230
2215
  const unzipping = (input.settings["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING";
2231
2216
  const crossingStrategy = input.settings["crossingMinimization.strategy"] ?? "LAYER_SWEEP";
2232
- const interactiveTargetOrder = crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "target") && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2233
- const verticalNoneTargetOrder = !horizontal && crossingStrategy === "NONE" && input.settings["layering.strategy"] === "STRETCH_WIDTH" && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "target") && !input.graph.edges.some(({ sourceId }) => sourceId === nodeId) && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2234
- const interactiveLeftSourceOrder = crossingStrategy === "INTERACTIVE" && input.direction === "left" && entries.length >= 3 && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "source") ? [...entries].sort((left, right) => {
2217
+ const interactiveTargetOrder = crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint }) => endpoint === "target") && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2218
+ const verticalNoneTargetOrder = !horizontal && crossingStrategy === "NONE" && input.settings["layering.strategy"] === "STRETCH_WIDTH" && entries.every(({ endpoint }) => endpoint === "target") && !input.graph.edges.some(({ sourceId }) => sourceId === nodeId) && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2219
+ const interactiveLeftSourceOrder = crossingStrategy === "INTERACTIVE" && input.direction === "left" && entries.length >= 3 && entries.every(({ endpoint }) => endpoint === "source") ? [...entries].sort((left, right) => {
2235
2220
  const leftRect = placement.rectByNodeId.get(left.edge.targetId);
2236
2221
  const rightRect = placement.rectByNodeId.get(right.edge.targetId);
2237
2222
  const leftCross = (leftRect?.y ?? 0) + (leftRect?.height ?? 0) / 2;
2238
2223
  return (rightRect?.y ?? 0) + (rightRect?.height ?? 0) / 2 - leftCross || (edgeModelOrder.get(left.edge.id) ?? 0) - (edgeModelOrder.get(right.edge.id) ?? 0);
2239
2224
  }).map(({ edge }) => edge.id) : void 0;
2240
- const interactiveForwardLongEdgeSource = input.direction !== "left" && crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "source") && entries.some(({ edge }) => edge.targetId.startsWith("__layout_dummy:"));
2225
+ const interactiveForwardLongEdgeSource = input.direction !== "left" && crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint }) => endpoint === "source") && entries.some(({ edge }) => edge.targetId.startsWith("__layout_dummy:"));
2241
2226
  const layerOrdered = unzipping || interactiveTargetOrder || verticalNoneTargetOrder || input.settings.hierarchyHandling !== "INCLUDE_CHILDREN" && (crossingStrategy === "LAYER_SWEEP" || crossingStrategy === "MEDIAN_LAYER_SWEEP");
2242
2227
  entries.sort((left, right) => {
2243
2228
  if (interactiveLeftSourceOrder) {
@@ -2290,8 +2275,8 @@ function implicitEdgeEndpoints(input, placement, orientation) {
2290
2275
  const constraints = nodeSettings?.portConstraints;
2291
2276
  const portSpacing = Number(input.settings["spacing.portPort"] ?? 10);
2292
2277
  const fixedWrapAnchor = nodeId.startsWith("__layout_dummy:wrap:");
2293
- entries.forEach(({ edge, endpoint: endpoint$1 }, index) => {
2294
- const ordinal = endpoint$1 === "target" && input.settings["crossingMinimization.strategy"] !== "INTERACTIVE" && !layerOrdered && !(input.settings.directionCongruency === "ROTATION" && (input.direction === "down" || input.direction === "left")) ? entries.length - index : index + 1;
2278
+ entries.forEach(({ edge, endpoint }, index) => {
2279
+ const ordinal = endpoint === "target" && input.settings["crossingMinimization.strategy"] !== "INTERACTIVE" && !layerOrdered && !(input.settings.directionCongruency === "ROTATION" && (input.direction === "down" || input.direction === "left")) ? entries.length - index : index + 1;
2295
2280
  const orderedIndex = ordinal - 1;
2296
2281
  const crossPosition = mergeEdges || hypernode === true ? axisSize / 2 : constraints === "FIXED_RATIO" || constraints === "FIXED_POS" ? 0 : alignment === "BEGIN" ? axisStart + orderedIndex * portSpacing : alignment === "END" ? axisStart + availableAxisSize - (entries.length - orderedIndex - 1) * portSpacing : alignment === "CENTER" ? axisStart + (availableAxisSize - (entries.length - 1) * portSpacing) / 2 + orderedIndex * portSpacing : alignment === "JUSTIFIED" ? entries.length === 1 ? axisStart + availableAxisSize / 2 : axisStart + orderedIndex * availableAxisSize / (entries.length - 1) : axisStart + availableAxisSize * ordinal / (entries.length + 1);
2297
2282
  let point = horizontal ? {
@@ -2312,7 +2297,7 @@ function implicitEdgeEndpoints(input, placement, orientation) {
2312
2297
  source: point,
2313
2298
  target: point
2314
2299
  };
2315
- pair[endpoint$1] = point;
2300
+ pair[endpoint] = point;
2316
2301
  result.set(edge.id, pair);
2317
2302
  });
2318
2303
  }
@@ -2497,12 +2482,12 @@ function routeEdges(style) {
2497
2482
  flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
2498
2483
  }
2499
2484
  const increasing = input.direction === "right" || input.direction === "down";
2500
- const flowPortProtrusion = (edge, endpoint$1) => {
2501
- const node = nodeById.get(endpoint$1 === "source" ? edge.sourceId : edge.targetId);
2502
- const portName = endpoint$1 === "source" ? edge.sourcePort : edge.targetPort;
2485
+ const flowPortProtrusion = (edge, endpoint) => {
2486
+ const node = nodeById.get(endpoint === "source" ? edge.sourceId : edge.targetId);
2487
+ const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
2503
2488
  const port = node?.ports?.find((candidate) => candidate.name === portName);
2504
2489
  if (!node || !port) return 0;
2505
- return input.portSettings?.(port, node)?.["port.side"] === (horizontal ? endpoint$1 === "source" ? input.direction === "right" ? "EAST" : "WEST" : input.direction === "right" ? "WEST" : "EAST" : endpoint$1 === "source" ? input.direction === "down" ? "SOUTH" : "NORTH" : input.direction === "down" ? "NORTH" : "SOUTH") ? horizontal ? port.width ?? 0 : port.height ?? 0 : 0;
2490
+ return input.portSettings?.(port, node)?.["port.side"] === (horizontal ? endpoint === "source" ? input.direction === "right" ? "EAST" : "WEST" : input.direction === "right" ? "WEST" : "EAST" : endpoint === "source" ? input.direction === "down" ? "SOUTH" : "NORTH" : input.direction === "down" ? "NORTH" : "SOUTH") ? horizontal ? port.width ?? 0 : port.height ?? 0 : 0;
2506
2491
  };
2507
2492
  const adjacentNodeIds = /* @__PURE__ */ new Map();
2508
2493
  for (const edge of input.graph.edges) {
@@ -3821,108 +3806,355 @@ function getPolylineMidpoint(points) {
3821
3806
  }
3822
3807
 
3823
3808
  //#endregion
3824
- //#region src/authoring/coordinates.ts
3825
- /** Native compound nodes and sibling edges are parent-relative; crossing edges are world-relative. */
3826
- function frames(graph) {
3827
- const nodes = new Map(graph.nodes.map((n) => [n.id, n]));
3828
- const offsets = /* @__PURE__ */ new Map();
3829
- const visiting = /* @__PURE__ */ new Set();
3830
- function offset(id) {
3831
- const cached = offsets.get(id);
3832
- if (cached) return cached;
3833
- if (visiting.has(id)) throw new LayoutError(`Cyclic parent hierarchy at ${id}`, "INVALID_GRAPH");
3834
- visiting.add(id);
3835
- const parentId = nodes.get(id)?.parentId;
3836
- if (parentId == null) {
3837
- const origin = {
3838
- x: 0,
3839
- y: 0
3840
- };
3841
- visiting.delete(id);
3842
- offsets.set(id, origin);
3843
- return origin;
3844
- }
3845
- const parent = nodes.get(parentId);
3846
- const ancestor = offset(parentId);
3847
- const result = {
3848
- x: ancestor.x + parent.x,
3849
- y: ancestor.y + parent.y
3809
+ //#region src/layered/compound.ts
3810
+ const paddingOf = (value) => ({
3811
+ top: typeof value === "number" ? value : value?.top ?? 12,
3812
+ right: typeof value === "number" ? value : value?.right ?? 12,
3813
+ bottom: typeof value === "number" ? value : value?.bottom ?? 12,
3814
+ left: typeof value === "number" ? value : value?.left ?? 12
3815
+ });
3816
+ /** Solve each scope's occupied envelope bottom-up; never repair returned sibling positions. */
3817
+ function layoutCompounds(graph, options, flat) {
3818
+ const nodes = graph.nodes.map((node) => {
3819
+ const size = options.measure?.(node);
3820
+ return {
3821
+ ...node,
3822
+ width: size?.width ?? node.width ?? 0,
3823
+ height: size?.height ?? node.height ?? 0
3850
3824
  };
3851
- visiting.delete(id);
3852
- offsets.set(id, result);
3853
- return result;
3825
+ });
3826
+ const byId = new Map(nodes.map((node) => [node.id, node]));
3827
+ const ancestry = (id) => {
3828
+ const chain = [];
3829
+ let current = id;
3830
+ while (current != null) {
3831
+ if (chain.includes(current)) throw new Error(`Cyclic compound hierarchy at ${current}`);
3832
+ chain.push(current);
3833
+ current = byId.get(current)?.parentId ?? null;
3834
+ }
3835
+ return [...chain, null];
3836
+ };
3837
+ const branch = (id, scope) => {
3838
+ const chain = ancestry(id);
3839
+ return chain[chain.indexOf(scope) - 1];
3840
+ };
3841
+ const owner = (edge) => {
3842
+ const a = ancestry(edge.sourceId), b = ancestry(edge.targetId);
3843
+ if (edge.sourceId !== edge.targetId && b.includes(edge.sourceId)) return edge.sourceId;
3844
+ if (edge.sourceId !== edge.targetId && a.includes(edge.targetId)) return edge.targetId;
3845
+ return a.slice(1).find((id) => b.slice(1).includes(id)) ?? null;
3846
+ };
3847
+ const owned = /* @__PURE__ */ new Map();
3848
+ for (const edge of graph.edges) {
3849
+ const scope = owner(edge);
3850
+ owned.set(scope, [...owned.get(scope) ?? [], edge]);
3854
3851
  }
3855
- for (const node of graph.nodes) offsets.set(node.id, offset(node.id));
3856
- return {
3857
- nodes: offsets,
3858
- edges: new Map(graph.edges.map((edge) => {
3859
- const source = nodes.get(edge.sourceId);
3860
- const target = nodes.get(edge.targetId);
3861
- return [edge.id, (source.parentId ?? null) === (target.parentId ?? null) ? offset(source.id) : {
3862
- x: 0,
3863
- y: 0
3852
+ const edges = new Map(graph.edges.map((e) => [e.id, {
3853
+ ...e,
3854
+ x: 0,
3855
+ y: 0,
3856
+ width: e.width ?? 0,
3857
+ height: e.height ?? 0
3858
+ }]));
3859
+ const localLabels = /* @__PURE__ */ new Map();
3860
+ const compoundGeometry = /* @__PURE__ */ new Map();
3861
+ const childScopes = [...new Set(nodes.flatMap((n) => n.parentId == null ? [] : [n.parentId]))].sort((a, b) => ancestry(b).length - ancestry(a).length);
3862
+ const solve = (scope) => {
3863
+ const children = nodes.filter((n) => (n.parentId ?? null) === scope);
3864
+ if (!children.length) return;
3865
+ const container = scope == null ? void 0 : byId.get(scope);
3866
+ const config = container && options.compound?.(container);
3867
+ const direction = config?.direction ?? options.direction ?? graph.direction ?? "right";
3868
+ const horizontal = direction === "left" || direction === "right";
3869
+ const padding = paddingOf(config?.padding ?? options.padding);
3870
+ const insets = { ...padding };
3871
+ const header = config?.header;
3872
+ const side = header?.side ?? "top";
3873
+ if (header) insets[side] += side === "top" || side === "bottom" ? header.height : header.width;
3874
+ const scopeEdges = owned.get(scope) ?? [];
3875
+ const boundary = /* @__PURE__ */ new Map();
3876
+ const projected = [];
3877
+ for (const edge of scopeEdges) if (edge.sourceId === scope || edge.targetId === scope) {
3878
+ const id = branch(edge.sourceId === scope ? edge.targetId : edge.sourceId, scope);
3879
+ boundary.set(id, [...boundary.get(id) ?? [], edge]);
3880
+ } else projected.push({
3881
+ ...edge,
3882
+ sourceId: branch(edge.sourceId, scope),
3883
+ targetId: branch(edge.targetId, scope),
3884
+ points: void 0
3885
+ });
3886
+ const gap = options.settings?.["spacing.labelNode"] ?? options.spacing?.node ?? 20;
3887
+ const labelGap = options.settings?.["spacing.labelLabel"] ?? 8;
3888
+ const sizes = new Map(children.map((n) => {
3889
+ const labels$1 = boundary.get(n.id) ?? [];
3890
+ const labelWidth = Math.max(0, ...labels$1.map((e) => e.width ?? 0));
3891
+ const labelHeight = labels$1.reduce((sum, e) => sum + (e.height ?? 0), 0) + Math.max(0, labels$1.length - 1) * labelGap;
3892
+ return [n.id, {
3893
+ width: labels$1.length && !horizontal ? n.width + gap + labelWidth : Math.max(n.width, labelWidth),
3894
+ height: labels$1.length && horizontal ? n.height + gap + labelHeight : Math.max(n.height, labelHeight)
3864
3895
  }];
3865
- }))
3896
+ }));
3897
+ const labels = projected.filter((edge) => (edge.width ?? 0) > 0 && (edge.height ?? 0) > 0);
3898
+ const used = new Set([...graph.nodes, ...graph.edges].map((entity) => entity.id));
3899
+ const labelIds = /* @__PURE__ */ new Map();
3900
+ for (const edge of labels) {
3901
+ let id = `__layout_label_${edge.id}`;
3902
+ while ([
3903
+ id,
3904
+ `${id}_source`,
3905
+ `${id}_target`
3906
+ ].some((key) => used.has(key))) id += "_";
3907
+ for (const key of [
3908
+ id,
3909
+ `${id}_source`,
3910
+ `${id}_target`
3911
+ ]) used.add(key);
3912
+ labelIds.set(edge.id, id);
3913
+ sizes.set(id, {
3914
+ width: edge.width,
3915
+ height: edge.height
3916
+ });
3917
+ }
3918
+ const originalEdges = new Map(graph.edges.map((edge) => [edge.id, edge]));
3919
+ for (const edge of labels) {
3920
+ originalEdges.set(`${labelIds.get(edge.id)}_source`, edges.get(edge.id));
3921
+ originalEdges.set(`${labelIds.get(edge.id)}_target`, edges.get(edge.id));
3922
+ }
3923
+ const placementOrder = new Map(children.map((node, index) => [node.id, index]));
3924
+ for (const edge of labels) placementOrder.set(labelIds.get(edge.id), ((placementOrder.get(edge.sourceId) ?? 0) + (placementOrder.get(edge.targetId) ?? 0)) / 2 + (edge.sourceId === edge.targetId ? .25 : 0));
3925
+ const result$1 = flat({
3926
+ ...graph,
3927
+ nodes: [...children.map((n) => ({
3928
+ ...n,
3929
+ parentId: null
3930
+ })), ...labels.map((edge) => ({
3931
+ type: "node",
3932
+ id: labelIds.get(edge.id),
3933
+ data: void 0
3934
+ }))].sort((a, b) => placementOrder.get(a.id) - placementOrder.get(b.id)),
3935
+ edges: projected.flatMap((edge) => {
3936
+ const labelId = labelIds.get(edge.id);
3937
+ return labelId ? [{
3938
+ ...edge,
3939
+ id: `${labelId}_source`,
3940
+ targetId: labelId,
3941
+ targetPort: void 0,
3942
+ width: 0,
3943
+ height: 0
3944
+ }, {
3945
+ ...edge,
3946
+ id: `${labelId}_target`,
3947
+ sourceId: labelId,
3948
+ sourcePort: void 0,
3949
+ width: 0,
3950
+ height: 0
3951
+ }] : [edge];
3952
+ })
3953
+ }, {
3954
+ ...options,
3955
+ compound: void 0,
3956
+ direction,
3957
+ padding: insets,
3958
+ measure: (node) => sizes.get(node.id),
3959
+ nodeSettings: (node) => {
3960
+ const original = byId.get(node.id);
3961
+ return original ? options.nodeSettings?.(original) : void 0;
3962
+ },
3963
+ edgeSettings: (edge) => options.edgeSettings?.(originalEdges.get(edge.id) ?? edge)
3964
+ });
3965
+ for (const placed of result$1.nodes) {
3966
+ const node = byId.get(placed.id);
3967
+ if (node) Object.assign(node, {
3968
+ x: placed.x,
3969
+ y: placed.y,
3970
+ ports: placed.ports
3971
+ });
3972
+ }
3973
+ for (const edge of projected) {
3974
+ const labelId = labelIds.get(edge.id);
3975
+ if (labelId) {
3976
+ const label = result$1.nodes.find((n) => n.id === labelId);
3977
+ localLabels.set(edge.id, {
3978
+ scope,
3979
+ rect: {
3980
+ x: label.x,
3981
+ y: label.y,
3982
+ width: label.width,
3983
+ height: label.height
3984
+ }
3985
+ });
3986
+ }
3987
+ }
3988
+ for (const [id, labels$1] of boundary) {
3989
+ const node = byId.get(id);
3990
+ const totalHeight = labels$1.reduce((sum, e) => sum + (e.height ?? 0), 0) + Math.max(0, labels$1.length - 1) * labelGap;
3991
+ let cross = horizontal ? 0 : (Math.max(node.height, totalHeight) - totalHeight) / 2;
3992
+ for (const edge of labels$1) {
3993
+ const width = edge.width ?? 0, height = edge.height ?? 0;
3994
+ localLabels.set(edge.id, {
3995
+ scope,
3996
+ rect: {
3997
+ x: node.x + (horizontal ? (Math.max(node.width, width) - width) / 2 : node.width + gap),
3998
+ y: node.y + (horizontal ? node.height + gap : 0) + cross,
3999
+ width,
4000
+ height
4001
+ }
4002
+ });
4003
+ cross += height + labelGap;
4004
+ }
4005
+ }
4006
+ const boxes = [...children.map((n) => ({
4007
+ x: n.x,
4008
+ y: n.y,
4009
+ ...sizes.get(n.id)
4010
+ })), ...[...localLabels.values()].filter((l) => l.scope === scope).map((l) => l.rect)];
4011
+ let dx = Math.max(0, insets.left - Math.min(...boxes.map((b) => b.x)));
4012
+ let dy = Math.max(0, insets.top - Math.min(...boxes.map((b) => b.y)));
4013
+ if (header && (side === "top" || side === "bottom")) {
4014
+ const occupiedWidth = Math.max(...boxes.map((b) => b.x + b.width)) - Math.min(...boxes.map((b) => b.x)) + padding.left + padding.right;
4015
+ dx += Math.max(0, (header.width - occupiedWidth) / 2);
4016
+ }
4017
+ if (header && (side === "left" || side === "right")) {
4018
+ const occupiedHeight = Math.max(...boxes.map((b) => b.y + b.height)) - Math.min(...boxes.map((b) => b.y)) + padding.top + padding.bottom;
4019
+ dy += Math.max(0, (header.height - occupiedHeight) / 2);
4020
+ }
4021
+ const right = Math.max(...boxes.map((b) => b.x + dx + b.width));
4022
+ const bottom = Math.max(...boxes.map((b) => b.y + dy + b.height));
4023
+ for (const n of children) {
4024
+ n.x += dx;
4025
+ n.y += dy;
4026
+ }
4027
+ for (const l of localLabels.values()) if (l.scope === scope) {
4028
+ l.rect.x += dx;
4029
+ l.rect.y += dy;
4030
+ }
4031
+ if (container) {
4032
+ const width = Math.max(header?.width ?? 0, config?.minContentSize?.width ? config.minContentSize.width + insets.left + insets.right : 0, right + insets.right);
4033
+ const height = Math.max(header?.height ?? 0, config?.minContentSize?.height ? config.minContentSize.height + insets.top + insets.bottom : 0, bottom + insets.bottom);
4034
+ container.width = Math.max(container.width, width);
4035
+ container.height = Math.max(container.height, height);
4036
+ compoundGeometry.set(container.id, {
4037
+ bounds: {
4038
+ x: 0,
4039
+ y: 0,
4040
+ width: container.width,
4041
+ height: container.height
4042
+ },
4043
+ content: {
4044
+ x: insets.left,
4045
+ y: insets.top,
4046
+ width: container.width - insets.left - insets.right,
4047
+ height: container.height - insets.top - insets.bottom
4048
+ },
4049
+ ...header ? { header: {
4050
+ x: side === "right" ? container.width - header.width : 0,
4051
+ y: side === "bottom" ? container.height - header.height : 0,
4052
+ width: side === "top" || side === "bottom" ? container.width : header.width,
4053
+ height: side === "left" || side === "right" ? container.height : header.height
4054
+ } } : {}
4055
+ });
4056
+ }
3866
4057
  };
3867
- }
3868
- function worldGeometry(graph) {
3869
- const offsets = frames(graph);
3870
- return {
4058
+ for (const scope of childScopes) solve(scope);
4059
+ const roots = nodes.filter((n) => n.parentId == null);
4060
+ if (roots.length === 1 && compoundGeometry.has(roots[0].id) && !owned.get(null)?.length) Object.assign(roots[0], {
4061
+ x: 0,
4062
+ y: 0
4063
+ });
4064
+ else solve(null);
4065
+ const origin = (id) => {
4066
+ if (id == null) return {
4067
+ x: 0,
4068
+ y: 0
4069
+ };
4070
+ return ancestry(id).slice(0, -1).reduce((p, key) => ({
4071
+ x: p.x + byId.get(key).x,
4072
+ y: p.y + byId.get(key).y
4073
+ }), {
4074
+ x: 0,
4075
+ y: 0
4076
+ });
4077
+ };
4078
+ for (const [id, label] of localLabels) {
4079
+ const offset = origin(label.scope);
4080
+ Object.assign(edges.get(id), {
4081
+ ...label.rect,
4082
+ x: label.rect.x + offset.x,
4083
+ y: label.rect.y + offset.y
4084
+ });
4085
+ }
4086
+ const attachmentSettings = {};
4087
+ for (const edge of edges.values()) {
4088
+ const sourceGeometry = compoundGeometry.get(edge.sourceId), targetGeometry = compoundGeometry.get(edge.targetId);
4089
+ const intent = options.edgeAttachment?.(edge);
4090
+ attachmentSettings[edge.id] = {
4091
+ ...sourceGeometry && edge.sourceId !== edge.targetId && ancestry(edge.targetId).includes(edge.sourceId) ? { sourceAttachment: {
4092
+ bounds: intent?.source === "outer" ? {
4093
+ x: 0,
4094
+ y: 0,
4095
+ width: sourceGeometry.bounds.width,
4096
+ height: sourceGeometry.bounds.height
4097
+ } : sourceGeometry.content,
4098
+ facing: intent?.source === "outer" ? "outward" : "inward"
4099
+ } } : {},
4100
+ ...targetGeometry && intent?.target === "content" ? { targetAttachment: {
4101
+ bounds: targetGeometry.content,
4102
+ facing: "inward"
4103
+ } } : {}
4104
+ };
4105
+ }
4106
+ const worldNodes = nodes.map((n) => ({
4107
+ ...n,
4108
+ ...origin(n.id)
4109
+ }));
4110
+ const usedIds = new Set(nodes.map((n) => n.id));
4111
+ const headers = [...compoundGeometry].flatMap(([id, geometry]) => {
4112
+ if (!geometry.header) return [];
4113
+ const p = origin(id);
4114
+ let key = `__layout_header_${id}`;
4115
+ while (usedIds.has(key)) key += "_";
4116
+ usedIds.add(key);
4117
+ return [{
4118
+ type: "node",
4119
+ id: key,
4120
+ parentId: id,
4121
+ data: void 0,
4122
+ ...geometry.header,
4123
+ x: p.x + geometry.header.x,
4124
+ y: p.y + geometry.header.y
4125
+ }];
4126
+ });
4127
+ const snapshot = orthogonalRouting.route({
3871
4128
  ...graph,
3872
- nodes: graph.nodes.map((n) => {
3873
- const p = offsets.nodes.get(n.id);
3874
- return p.x === 0 && p.y === 0 ? n : {
3875
- ...n,
3876
- x: n.x + p.x,
3877
- y: n.y + p.y
3878
- };
3879
- }),
3880
- edges: graph.edges.map((e) => {
3881
- const p = offsets.edges.get(e.id);
3882
- return p.x === 0 && p.y === 0 ? e : {
3883
- ...e,
3884
- x: e.x + p.x,
3885
- y: e.y + p.y,
3886
- ...e.points ? { points: e.points.map((v) => ({
3887
- x: v.x + p.x,
3888
- y: v.y + p.y
3889
- })) } : {}
3890
- };
3891
- })
4129
+ nodes: [...worldNodes, ...headers],
4130
+ edges: [...edges.values()]
4131
+ }, {
4132
+ coordinateSpace: "world",
4133
+ edges: attachmentSettings
4134
+ });
4135
+ for (const [id, route] of snapshot.routes) {
4136
+ const polylines = routeToPolylines(route);
4137
+ Object.assign(edges.get(id), {
4138
+ points: polylines.flatMap((p) => p.map((point) => ({ ...point }))),
4139
+ routing: "orthogonal"
4140
+ });
4141
+ }
4142
+ for (const [id, geometry] of compoundGeometry) geometry.bounds = {
4143
+ ...geometry.bounds,
4144
+ x: byId.get(id).x,
4145
+ y: byId.get(id).y
3892
4146
  };
3893
- }
3894
- /** Ancestor frames stay fixed during authoring. Preserve untouched coordinates exactly. */
3895
- function nativeGeometry(graph, original) {
3896
- const offsets = frames(original);
3897
- const world = worldGeometry(original);
3898
- const originalNodes = new Map(original.nodes.map((n) => [n.id, n]));
3899
- const originalEdges = new Map(original.edges.map((e) => [e.id, e]));
3900
- const worldNodes = new Map(world.nodes.map((n) => [n.id, n]));
3901
- const worldEdges = new Map(world.edges.map((e) => [e.id, e]));
3902
- return {
4147
+ const result = {
3903
4148
  ...graph,
3904
- nodes: graph.nodes.map((n) => {
3905
- const p = offsets.nodes.get(n.id), before = originalNodes.get(n.id), prior = worldNodes.get(n.id);
3906
- return {
3907
- ...n,
3908
- x: n.x === prior.x ? before.x : n.x - p.x,
3909
- y: n.y === prior.y ? before.y : n.y - p.y
3910
- };
3911
- }),
3912
- edges: graph.edges.map((e) => {
3913
- const p = offsets.edges.get(e.id), before = originalEdges.get(e.id), prior = worldEdges.get(e.id);
3914
- const points = JSON.stringify(e.points) === JSON.stringify(prior.points) ? before.points : e.points?.map((v) => ({
3915
- x: v.x - p.x,
3916
- y: v.y - p.y
3917
- }));
3918
- return {
3919
- ...e,
3920
- x: e.x === prior.x ? before.x : e.x - p.x,
3921
- y: e.y === prior.y ? before.y : e.y - p.y,
3922
- ...points === void 0 ? {} : { points }
3923
- };
3924
- })
4149
+ edgeCoordinateSpace: "world",
4150
+ direction: options.direction ?? graph.direction ?? "right",
4151
+ nodes,
4152
+ edges: graph.edges.map((e) => edges.get(e.id)),
4153
+ compoundGeometry,
4154
+ compoundRoutes: snapshot.routes
3925
4155
  };
4156
+ recordRouteGeometry(result, snapshot.routes);
4157
+ return result;
3926
4158
  }
3927
4159
 
3928
4160
  //#endregion
@@ -5431,173 +5663,6 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
5431
5663
  };
5432
5664
  }
5433
5665
 
5434
- //#endregion
5435
- //#region src/authoring/routing.ts
5436
- function overlaps(a, b, gap = 0) {
5437
- return a.width > 0 && a.height > 0 && b.width > 0 && b.height > 0 && a.x < b.x + b.width + gap && a.x + a.width + gap > b.x && a.y < b.y + b.height + gap && a.y + a.height + gap > b.y;
5438
- }
5439
- /** Open-interior intersection: travelling along a rectangle boundary is allowed. */
5440
- function crossesRect(a, b, rect) {
5441
- let low = 0, high = 1;
5442
- for (const axis of ["x", "y"]) {
5443
- const delta = b[axis] - a[axis];
5444
- const min = rect[axis], max = min + (axis === "x" ? rect.width : rect.height);
5445
- if (Math.abs(delta) < 1e-10) {
5446
- if (a[axis] <= min || a[axis] >= max) return false;
5447
- } else {
5448
- const t1 = (min - a[axis]) / delta, t2 = (max - a[axis]) / delta;
5449
- low = Math.max(low, Math.min(t1, t2));
5450
- high = Math.min(high, Math.max(t1, t2));
5451
- }
5452
- }
5453
- return low < high - 1e-10;
5454
- }
5455
- function routeCrosses(points, rect) {
5456
- return points?.some((point, i) => i > 0 && crossesRect(points[i - 1], point, rect)) ?? false;
5457
- }
5458
- function endpoint(node, portName, direction, source) {
5459
- if (portName !== void 0) {
5460
- const port = node.ports?.find((p) => p.name === portName);
5461
- if (port?.x !== void 0 && port.y !== void 0) return {
5462
- x: node.x + port.x + (port.width ?? 0) / 2,
5463
- y: node.y + port.y + (port.height ?? 0) / 2
5464
- };
5465
- throw new LayoutError(`Port ${portName} on ${node.id} requires positioned geometry`, "MISSING_GEOMETRY");
5466
- }
5467
- const horizontal = direction === "left" || direction === "right";
5468
- const far = source === (direction === "right" || direction === "down");
5469
- return horizontal ? {
5470
- x: node.x + (far ? node.width : 0),
5471
- y: node.y + node.height / 2
5472
- } : {
5473
- x: node.x + node.width / 2,
5474
- y: node.y + (far ? node.height : 0)
5475
- };
5476
- }
5477
- /** Deterministic orthogonal visibility-grid search around fixed rectangles. */
5478
- function connect(start, end, obstacles, check) {
5479
- if (start.x === end.x && start.y === end.y) return [start];
5480
- for (let attempt = 0; attempt < 8; attempt++) {
5481
- check();
5482
- const margin = 64 * 2 ** attempt;
5483
- const left = Math.min(start.x, end.x) - margin;
5484
- const right = Math.max(start.x, end.x) + margin;
5485
- const top = Math.min(start.y, end.y) - margin;
5486
- const bottom = Math.max(start.y, end.y) + margin;
5487
- const local = obstacles.filter((r) => r.x <= right && r.x + r.width >= left && r.y <= bottom && r.y + r.height >= top);
5488
- const result = search(start, end, local, obstacles, check);
5489
- if (result || local.length === obstacles.length) return result;
5490
- }
5491
- }
5492
- function search(start, end, gridObstacles, obstacles, check) {
5493
- const xs = [...new Set([
5494
- start.x,
5495
- end.x,
5496
- ...gridObstacles.flatMap((r) => [r.x, r.x + r.width])
5497
- ])].sort((a, b) => a - b);
5498
- const ys = [...new Set([
5499
- start.y,
5500
- end.y,
5501
- ...gridObstacles.flatMap((r) => [r.y, r.y + r.height])
5502
- ])].sort((a, b) => a - b);
5503
- if (xs.length * ys.length > 4e4) return void 0;
5504
- const width = xs.length;
5505
- const point = (id) => ({
5506
- x: xs[id % width],
5507
- y: ys[Math.floor(id / width)]
5508
- });
5509
- const from = ys.indexOf(start.y) * width + xs.indexOf(start.x);
5510
- const to = ys.indexOf(end.y) * width + xs.indexOf(end.x);
5511
- const distances = new Map([[from, 0]]);
5512
- const previous = /* @__PURE__ */ new Map();
5513
- const queue = [];
5514
- function push(item) {
5515
- queue.push(item);
5516
- let i = queue.length - 1;
5517
- while (i > 0) {
5518
- const parent = i - 1 >> 1;
5519
- if (queue[parent].cost <= item.cost) break;
5520
- queue[i] = queue[parent];
5521
- i = parent;
5522
- }
5523
- queue[i] = item;
5524
- }
5525
- function pop() {
5526
- const first = queue[0];
5527
- const last = queue.pop();
5528
- if (queue.length) {
5529
- let i = 0;
5530
- while (i * 2 + 1 < queue.length) {
5531
- let child = i * 2 + 1;
5532
- if (child + 1 < queue.length && queue[child + 1].cost < queue[child].cost) child++;
5533
- if (last.cost <= queue[child].cost) break;
5534
- queue[i] = queue[child];
5535
- i = child;
5536
- }
5537
- queue[i] = last;
5538
- }
5539
- return first;
5540
- }
5541
- push({
5542
- id: from,
5543
- cost: 0
5544
- });
5545
- let visits = 0;
5546
- while (queue.length) {
5547
- if (++visits % 128 === 0) check();
5548
- const current = pop();
5549
- if (current.cost !== distances.get(current.id)) continue;
5550
- if (current.id === to) {
5551
- const result = [end];
5552
- let id = to;
5553
- while (id !== from) {
5554
- id = previous.get(id);
5555
- result.push(point(id));
5556
- }
5557
- return result.reverse();
5558
- }
5559
- const x = current.id % width, y = Math.floor(current.id / width);
5560
- const neighbors = [
5561
- x > 0 ? current.id - 1 : -1,
5562
- x + 1 < width ? current.id + 1 : -1,
5563
- y > 0 ? current.id - width : -1,
5564
- y + 1 < ys.length ? current.id + width : -1
5565
- ];
5566
- const a = point(current.id);
5567
- for (const next of neighbors) {
5568
- if (next < 0) continue;
5569
- const b = point(next);
5570
- if (obstacles.some((rect) => crossesRect(a, b, rect))) continue;
5571
- const cost = current.cost + Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
5572
- if (cost >= (distances.get(next) ?? Infinity)) continue;
5573
- distances.set(next, cost);
5574
- previous.set(next, current.id);
5575
- push({
5576
- id: next,
5577
- cost
5578
- });
5579
- }
5580
- }
5581
- }
5582
- function routeOrthogonal(edge, source, target, direction, obstacles, waypoints, check) {
5583
- const anchors = [
5584
- endpoint(source, edge.sourcePort, direction, true),
5585
- ...waypoints,
5586
- endpoint(target, edge.targetPort, direction, false)
5587
- ];
5588
- const points = [];
5589
- for (let i = 1; i < anchors.length; i++) {
5590
- const segment = connect(anchors[i - 1], anchors[i], obstacles, check);
5591
- if (!segment) return void 0;
5592
- points.push(...points.length ? segment.slice(1) : segment);
5593
- }
5594
- return points.filter((p, i) => {
5595
- if (i === 0 || i === points.length - 1) return true;
5596
- const before = points[i - 1], after = points[i + 1];
5597
- return !(before.x === p.x && p.x === after.x && (p.y - before.y) * (after.y - p.y) >= 0 || before.y === p.y && p.y === after.y && (p.x - before.x) * (after.x - p.x) >= 0);
5598
- });
5599
- }
5600
-
5601
5666
  //#endregion
5602
5667
  //#region src/authoring/partial.ts
5603
5668
  const geometryKeys = [
@@ -10556,7 +10621,7 @@ function runCommentBoxPipeline(graph, options, context) {
10556
10621
  x: horizontal ? point.x : point.x + crossShift,
10557
10622
  y: horizontal ? point.y + crossShift : point.y
10558
10623
  }));
10559
- const endpoint$1 = (source) => {
10624
+ const endpoint = (source) => {
10560
10625
  if (horizontal) return {
10561
10626
  x: direction === "right" === source ? (targetRect.x ?? 0) + (targetRect.width ?? 0) : targetRect.x ?? 0,
10562
10627
  y: (targetRect.y ?? 0) + (targetRect.height ?? 0) / 2
@@ -10566,8 +10631,8 @@ function runCommentBoxPipeline(graph, options, context) {
10566
10631
  y: direction === "down" === source ? (targetRect.y ?? 0) + (targetRect.height ?? 0) : targetRect.y ?? 0
10567
10632
  };
10568
10633
  };
10569
- if (edge.sourceId === targetId && points.length > 0) points[0] = endpoint$1(true);
10570
- if (edge.targetId === targetId && points.length > 0) points[points.length - 1] = endpoint$1(false);
10634
+ if (edge.sourceId === targetId && points.length > 0) points[0] = endpoint(true);
10635
+ if (edge.targetId === targetId && points.length > 0) points[points.length - 1] = endpoint(false);
10571
10636
  return [edge.id, {
10572
10637
  ...edge,
10573
10638
  x: horizontal ? edge.x : (edge.x ?? 0) + crossShift,
@@ -10769,126 +10834,16 @@ function runSeparatedComponents(graph, options, context) {
10769
10834
  };
10770
10835
  }
10771
10836
  function runCompoundPipeline(graph, options, context) {
10772
- const nodes = graph.nodes.map((node) => ({ ...node }));
10773
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
10774
- const depth = (node) => {
10775
- let value = 0;
10776
- let parentId = node.parentId;
10777
- const seen = /* @__PURE__ */ new Set();
10778
- while (parentId != null && !seen.has(parentId)) {
10779
- seen.add(parentId);
10780
- value++;
10781
- parentId = nodeById.get(parentId)?.parentId;
10782
- }
10783
- return value;
10784
- };
10785
- const parentIds = [...new Set(nodes.flatMap((node) => node.parentId == null ? [] : [node.parentId]))].sort((left, right) => depth(nodeById.get(right)) - depth(nodeById.get(left)));
10786
- const edgeById = new Map(graph.edges.map((edge) => [edge.id, { ...edge }]));
10787
- const padding = typeof options.padding === "number" ? options.padding : 12;
10788
- const layoutSiblings = (parentId) => {
10789
- const siblings = nodes.filter((node) => (node.parentId ?? null) === parentId);
10790
- if (siblings.length === 0) return;
10791
- const siblingIds = new Set(siblings.map((node) => node.id));
10792
- const siblingEdges = graph.edges.filter((edge) => siblingIds.has(edge.sourceId) && siblingIds.has(edge.targetId));
10793
- const flatNodes = siblings.map((node) => ({
10794
- ...node,
10795
- parentId: null
10796
- }));
10797
- const result = runLayeredPipeline({
10798
- ...graph,
10799
- nodes: flatNodes,
10800
- edges: siblingEdges
10801
- }, options, context);
10802
- for (const laidOut of result.nodes) {
10803
- const node = nodeById.get(laidOut.id);
10804
- if (!node) continue;
10805
- Object.assign(node, laidOut, { parentId });
10806
- }
10807
- for (const edge of result.edges) edgeById.set(edge.id, edge);
10808
- };
10809
- for (const parentId of parentIds) {
10810
- layoutSiblings(parentId);
10811
- const parent = nodeById.get(parentId);
10812
- if (!parent) continue;
10813
- const children = nodes.filter((node) => node.parentId === parentId);
10814
- const right = Math.max(0, ...children.map((node) => (node.x ?? 0) + (node.width ?? 0)));
10815
- const bottom = Math.max(0, ...children.map((node) => (node.y ?? 0) + (node.height ?? 0)));
10816
- parent.width = Math.max(parent.width ?? 0, right + padding);
10817
- parent.height = Math.max(parent.height ?? 0, bottom + padding);
10818
- }
10819
- const roots = nodes.filter((node) => node.parentId == null);
10820
- if (roots.length === 1 && parentIds.includes(roots[0].id)) Object.assign(roots[0], {
10821
- x: 0,
10822
- y: 0
10837
+ const result = layoutCompounds(graph, options, (scope, local) => runLayeredPipeline(scope, local, context));
10838
+ for (const route of result.compoundRoutes.values()) for (const diagnostic of route.diagnostics) context?.diagnostics.push({
10839
+ severity: "warning",
10840
+ code: diagnostic.code,
10841
+ message: diagnostic.message,
10842
+ entityIds: [route.edgeId],
10843
+ phase: "compound-routing",
10844
+ geometry: "routes"
10823
10845
  });
10824
- else layoutSiblings(null);
10825
- const absoluteRect = (id) => {
10826
- const node = nodeById.get(id);
10827
- if (!node) return {
10828
- x: 0,
10829
- y: 0,
10830
- width: 0,
10831
- height: 0
10832
- };
10833
- let x = node.x ?? 0;
10834
- let y = node.y ?? 0;
10835
- let parentId = node.parentId;
10836
- const seen = /* @__PURE__ */ new Set();
10837
- while (parentId != null && !seen.has(parentId)) {
10838
- seen.add(parentId);
10839
- const parent = nodeById.get(parentId);
10840
- if (!parent) break;
10841
- x += parent.x ?? 0;
10842
- y += parent.y ?? 0;
10843
- parentId = parent.parentId;
10844
- }
10845
- return {
10846
- x,
10847
- y,
10848
- width: node.width ?? 0,
10849
- height: node.height ?? 0
10850
- };
10851
- };
10852
- for (const edge of graph.edges) {
10853
- if (edgeById.get(edge.id)?.points !== void 0) continue;
10854
- const source = absoluteRect(edge.sourceId);
10855
- const target = absoluteRect(edge.targetId);
10856
- const start = {
10857
- x: source.x + source.width,
10858
- y: source.y + source.height / 2
10859
- };
10860
- const end = {
10861
- x: target.x,
10862
- y: target.y + target.height / 2
10863
- };
10864
- const track = (start.x + end.x) / 2;
10865
- const points = [
10866
- start,
10867
- {
10868
- x: track,
10869
- y: start.y
10870
- },
10871
- {
10872
- x: track,
10873
- y: end.y
10874
- },
10875
- end
10876
- ];
10877
- edgeById.set(edge.id, {
10878
- ...edge,
10879
- points,
10880
- x: track,
10881
- y: (start.y + end.y) / 2,
10882
- width: edge.width ?? 0,
10883
- height: edge.height ?? 0,
10884
- routing: "orthogonal"
10885
- });
10886
- }
10887
- return {
10888
- ...graph,
10889
- nodes,
10890
- edges: graph.edges.map((edge) => edgeById.get(edge.id) ?? edge)
10891
- };
10846
+ return result;
10892
10847
  }
10893
10848
  function runLayeredPipeline(graph, options, context) {
10894
10849
  if (options.settings?.noLayout) return {
@@ -11361,16 +11316,16 @@ function runLayeredPipeline(graph, options, context) {
11361
11316
  const targetDelta = postCompactionNodeCrossDeltas.get(edge.targetId) ?? 0;
11362
11317
  const shiftEndpoint = (endpointIndex, adjacentIndex, delta) => {
11363
11318
  if (delta === 0) return;
11364
- const endpoint$1 = points[endpointIndex];
11319
+ const endpoint = points[endpointIndex];
11365
11320
  const adjacent = points[adjacentIndex];
11366
- if (!endpoint$1) return;
11367
- const originalCross = horizontalAntiparallelFlow ? endpoint$1.y : endpoint$1.x;
11321
+ if (!endpoint) return;
11322
+ const originalCross = horizontalAntiparallelFlow ? endpoint.y : endpoint.x;
11368
11323
  points[endpointIndex] = horizontalAntiparallelFlow ? {
11369
- ...endpoint$1,
11370
- y: endpoint$1.y + delta
11324
+ ...endpoint,
11325
+ y: endpoint.y + delta
11371
11326
  } : {
11372
- ...endpoint$1,
11373
- x: endpoint$1.x + delta
11327
+ ...endpoint,
11328
+ x: endpoint.x + delta
11374
11329
  };
11375
11330
  if (adjacent && (horizontalAntiparallelFlow ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontalAntiparallelFlow ? {
11376
11331
  ...adjacent,
@@ -11460,14 +11415,14 @@ function runLayeredPipeline(graph, options, context) {
11460
11415
  for (const edge of [...incoming, ...outgoing]) {
11461
11416
  const points = [...mutableRouteMap.get(edge.id) ?? []];
11462
11417
  const endpointIndex = edge.sourceId === node.id ? 0 : points.length - 1;
11463
- const endpoint$1 = points[endpointIndex];
11464
- if (!endpoint$1) continue;
11418
+ const endpoint = points[endpointIndex];
11419
+ if (!endpoint) continue;
11465
11420
  points[endpointIndex] = horizontal ? {
11466
- ...endpoint$1,
11467
- x: endpoint$1.x + delta
11421
+ ...endpoint,
11422
+ x: endpoint.x + delta
11468
11423
  } : {
11469
- ...endpoint$1,
11470
- y: endpoint$1.y + delta
11424
+ ...endpoint,
11425
+ y: endpoint.y + delta
11471
11426
  };
11472
11427
  mutableRouteMap.set(edge.id, points);
11473
11428
  }
@@ -11572,14 +11527,14 @@ function runLayeredPipeline(graph, options, context) {
11572
11527
  if (points.length === 0) continue;
11573
11528
  const index = edge.sourceId === nodeId ? 0 : edge.targetId === nodeId ? points.length - 1 : void 0;
11574
11529
  if (index === void 0) continue;
11575
- const endpoint$1 = points[index];
11576
- const originalCross = horizontal ? endpoint$1.y : endpoint$1.x;
11530
+ const endpoint = points[index];
11531
+ const originalCross = horizontal ? endpoint.y : endpoint.x;
11577
11532
  points[index] = horizontal ? {
11578
- ...endpoint$1,
11579
- y: endpoint$1.y + delta
11533
+ ...endpoint,
11534
+ y: endpoint.y + delta
11580
11535
  } : {
11581
- ...endpoint$1,
11582
- x: endpoint$1.x + delta
11536
+ ...endpoint,
11537
+ x: endpoint.x + delta
11583
11538
  };
11584
11539
  const adjacentIndex = index === 0 ? 1 : points.length - 2;
11585
11540
  const adjacent = points[adjacentIndex];
@@ -11777,7 +11732,12 @@ function runLayeredPipeline(graph, options, context) {
11777
11732
  * custom phase strategies, and all layered edge-routing styles.
11778
11733
  */
11779
11734
  function getLayeredLayout(graph, options = {}) {
11780
- return runLayeredPipeline(graph, options);
11735
+ const result = runLayeredPipeline(graph, options);
11736
+ return {
11737
+ ...result,
11738
+ compoundGeometry: result.compoundGeometry ?? /* @__PURE__ */ new Map(),
11739
+ compoundRoutes: result.compoundRoutes ?? /* @__PURE__ */ new Map()
11740
+ };
11781
11741
  }
11782
11742
  const layeredAlgorithm = {
11783
11743
  id: "layered",
@@ -11807,4 +11767,4 @@ const layeredAlgorithm = {
11807
11767
  };
11808
11768
 
11809
11769
  //#endregion
11810
- export { minimizeCrossingsInteractively as A, JavaRandom as B, breakCyclesGreedily as C, breakCyclesWithModelOrderBreadthFirstSearch as D, breakCyclesWithDepthFirstSearch as E, placeNodesInteractively as F, UnsupportedLayoutError as H, placePorts as I, routeEdgesOrthogonally as L, minimizeCrossingsWithMedian as M, minimizeCrossingsWithModelOrder as N, breakCyclesWithModelOrderDepthFirstSearch as O, placeNodesInLayers as P, routeEdgesWithPolylines as R, breakCyclesByStronglyConnectedNodeType as S, breakCyclesInteractively as T, LayoutError as V, assignLayersByLongestPathToSink as _, elkLayeredEnumValues as a, breakCyclesByModelOrder as b, placeNodesWithLinearSegments as c, assignLayersWithMinWidth as d, assignLayersWithNetworkSimplex as f, assignLayersByLongestPath as g, assignLayersByDepthFirstModelOrder as h, toElkLayeredOptions as i, minimizeCrossingsWithBarycenter as j, getPolylineMidpoint as k, placeNodesWithBrandesKoepf as l, assignLayersByBreadthFirstModelOrder as m, layeredAlgorithm as n, elkLayeredOptionDefinitions as o, c as p, fromElkLayeredOptionId as r, placeNodesWithNetworkSimplex as s, getLayeredLayout as t, assignLayersWithStretchWidth as u, assignLayersInteractively as v, breakCyclesGreedilyByModelOrder as w, breakCyclesByStronglyConnectedConnectivity as x, assignLayersWithCoffmanGraham as y, routeEdgesWithSplines as z };
11770
+ export { minimizeCrossingsInteractively as A, JavaRandom as B, breakCyclesGreedily as C, breakCyclesWithModelOrderBreadthFirstSearch as D, breakCyclesWithDepthFirstSearch as E, placeNodesInteractively as F, placePorts as I, routeEdgesOrthogonally as L, minimizeCrossingsWithMedian as M, minimizeCrossingsWithModelOrder as N, breakCyclesWithModelOrderDepthFirstSearch as O, placeNodesInLayers as P, routeEdgesWithPolylines as R, breakCyclesByStronglyConnectedNodeType as S, breakCyclesInteractively as T, assignLayersByLongestPathToSink as _, elkLayeredEnumValues as a, breakCyclesByModelOrder as b, placeNodesWithLinearSegments as c, assignLayersWithMinWidth as d, assignLayersWithNetworkSimplex as f, assignLayersByLongestPath as g, assignLayersByDepthFirstModelOrder as h, toElkLayeredOptions as i, minimizeCrossingsWithBarycenter as j, getPolylineMidpoint as k, placeNodesWithBrandesKoepf as l, assignLayersByBreadthFirstModelOrder as m, layeredAlgorithm as n, elkLayeredOptionDefinitions as o, c as p, fromElkLayeredOptionId as r, placeNodesWithNetworkSimplex as s, getLayeredLayout as t, assignLayersWithStretchWidth as u, assignLayersInteractively as v, breakCyclesGreedilyByModelOrder as w, breakCyclesByStronglyConnectedConnectivity as x, assignLayersWithCoffmanGraham as y, routeEdgesWithSplines as z };