@statelyai/layout 0.1.2 → 0.1.3

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.
@@ -1,5 +1,5 @@
1
- import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-COIyXvwb.mjs";
2
- import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-BeKUA1Mb.mjs";
1
+ import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-CoeH9LRO.mjs";
2
+ import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-BtTSZbb6.mjs";
3
3
  import { createGraph } from "@statelyai/graph";
4
4
 
5
5
  //#region src/elkjs/statechart.ts
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { A as minimizeCrossingsWithBarycenter, B as LayoutError, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, V as UnsupportedLayoutError, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "./layered-COIyXvwb.mjs";
2
- import { a as getRandomLayout, c as rectanglePackingAlgorithm, d as fixedAlgorithm, f as getFixedLayout, i as sporeOverlapRemovalAlgorithm, l as boxAlgorithm, n as getSporeOverlapRemovalLayout, o as randomAlgorithm, r as sporeCompactionAlgorithm, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "./spore-BeKUA1Mb.mjs";
1
+ import { A as minimizeCrossingsWithBarycenter, B as LayoutError, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, V as UnsupportedLayoutError, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "./layered-CoeH9LRO.mjs";
2
+ import { a as getRandomLayout, c as rectanglePackingAlgorithm, d as fixedAlgorithm, f as getFixedLayout, i as sporeOverlapRemovalAlgorithm, l as boxAlgorithm, n as getSporeOverlapRemovalLayout, o as randomAlgorithm, r as sporeCompactionAlgorithm, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "./spore-BtTSZbb6.mjs";
3
3
  import { getGraphIssues } from "@statelyai/graph";
4
4
 
5
5
  //#region src/layout.ts
@@ -1,3 +1,3 @@
1
- import { A as minimizeCrossingsWithBarycenter, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, c as placeNodesWithLinearSegments, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, l as placeNodesWithBrandesKoepf, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, s as placeNodesWithNetworkSimplex, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "../layered-COIyXvwb.mjs";
1
+ import { A as minimizeCrossingsWithBarycenter, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, c as placeNodesWithLinearSegments, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, l as placeNodesWithBrandesKoepf, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, s as placeNodesWithNetworkSimplex, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "../layered-CoeH9LRO.mjs";
2
2
 
3
3
  export { assignLayersByBreadthFirstModelOrder, assignLayersByDepthFirstModelOrder, assignLayersByLongestPath, assignLayersByLongestPathToSink, assignLayersInteractively, assignLayersWithCoffmanGraham, assignLayersWithMinWidth, assignLayersWithNetworkSimplex, assignLayersWithStretchWidth, breakCyclesByModelOrder, breakCyclesByStronglyConnectedConnectivity, breakCyclesByStronglyConnectedNodeType, breakCyclesGreedily, breakCyclesGreedilyByModelOrder, breakCyclesInteractively, breakCyclesWithDepthFirstSearch, breakCyclesWithModelOrderBreadthFirstSearch, breakCyclesWithModelOrderDepthFirstSearch, elkLayeredEnumValues, elkLayeredOptionDefinitions, fromElkLayeredOptionId, getLayeredLayout, layeredAlgorithm, minimizeCrossingsInteractively, minimizeCrossingsWithBarycenter, minimizeCrossingsWithMedian, minimizeCrossingsWithModelOrder, placeNodesInLayers, placeNodesInteractively, placeNodesWithBrandesKoepf, placeNodesWithLinearSegments, placeNodesWithNetworkSimplex, routeEdgesOrthogonally, routeEdgesWithPolylines, routeEdgesWithSplines, toElkLayeredOptions };
@@ -2494,7 +2494,40 @@ function routeEdges(style) {
2494
2494
  }
2495
2495
  flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
2496
2496
  }
2497
+ const increasing = input.direction === "right" || input.direction === "down";
2498
+ const flowPortProtrusion = (edge, endpoint) => {
2499
+ const node = nodeById.get(endpoint === "source" ? edge.sourceId : edge.targetId);
2500
+ const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
2501
+ const port = node?.ports?.find((candidate) => candidate.name === portName);
2502
+ if (!node || !port) return 0;
2503
+ 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;
2504
+ };
2505
+ const adjacentNodeIds = /* @__PURE__ */ new Map();
2506
+ for (const edge of input.graph.edges) {
2507
+ const sourceNeighbors = adjacentNodeIds.get(edge.sourceId) ?? /* @__PURE__ */ new Set();
2508
+ sourceNeighbors.add(edge.targetId);
2509
+ adjacentNodeIds.set(edge.sourceId, sourceNeighbors);
2510
+ const targetNeighbors = adjacentNodeIds.get(edge.targetId) ?? /* @__PURE__ */ new Set();
2511
+ targetNeighbors.add(edge.sourceId);
2512
+ adjacentNodeIds.set(edge.targetId, targetNeighbors);
2513
+ }
2514
+ const feedbackComponentNodeIds = /* @__PURE__ */ new Set();
2515
+ for (const edge of input.graph.edges) {
2516
+ const sourceLayer = flowLayerByNodeId.get(edge.sourceId);
2517
+ const targetLayer = flowLayerByNodeId.get(edge.targetId);
2518
+ if (sourceLayer === void 0 || targetLayer === void 0) continue;
2519
+ if (sourceLayer === targetLayer) continue;
2520
+ if (increasing ? targetLayer > sourceLayer : targetLayer < sourceLayer) continue;
2521
+ const pending = [edge.sourceId, edge.targetId];
2522
+ while (pending.length > 0) {
2523
+ const nodeId = pending.pop();
2524
+ if (feedbackComponentNodeIds.has(nodeId)) continue;
2525
+ feedbackComponentNodeIds.add(nodeId);
2526
+ pending.push(...adjacentNodeIds.get(nodeId) ?? []);
2527
+ }
2528
+ }
2497
2529
  const labelExtraByGap = flowLayers.slice(0, -1).map(() => 0);
2530
+ const compactLabelCorridorGaps = /* @__PURE__ */ new Set();
2498
2531
  for (const edge of input.graph.edges) {
2499
2532
  const labelFlowSize = horizontal ? edge.width ?? 0 : edge.height ?? 0;
2500
2533
  if (labelFlowSize <= 0) continue;
@@ -2502,8 +2535,14 @@ function routeEdges(style) {
2502
2535
  const targetLayer = flowLayerByNodeId.get(edge.targetId);
2503
2536
  if (sourceLayer === void 0 || targetLayer === void 0) continue;
2504
2537
  if (Math.abs(sourceLayer - targetLayer) !== 1) continue;
2505
- const extra = (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER" ? labelFlowSize + input.spacing.layer : labelFlowSize + Number(input.settings["spacing.edgeLabel"] ?? 2);
2538
+ const placement$1 = input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER";
2539
+ const edgeNodeSpacing = Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10);
2540
+ const requiredGap = labelFlowSize + Math.max(input.spacing.layer, flowPortProtrusion(edge, "source") + edgeNodeSpacing) + Math.max(input.spacing.layer, flowPortProtrusion(edge, "target") + edgeNodeSpacing);
2506
2541
  const gap = Math.min(sourceLayer, targetLayer);
2542
+ const availableGap = flowLayers[gap + 1].start - flowLayers[gap].end;
2543
+ const compactCorridor = placement$1 === "CENTER" && (increasing ? targetLayer > sourceLayer : targetLayer < sourceLayer) && !feedbackComponentNodeIds.has(edge.sourceId);
2544
+ if (compactCorridor) compactLabelCorridorGaps.add(gap);
2545
+ const extra = compactCorridor ? Math.max(0, requiredGap - availableGap) : placement$1 === "CENTER" ? labelFlowSize + input.spacing.layer : labelFlowSize + Number(input.settings["spacing.edgeLabel"] ?? 2);
2507
2546
  labelExtraByGap[gap] = Math.max(labelExtraByGap[gap] ?? 0, extra);
2508
2547
  }
2509
2548
  let labelShift = 0;
@@ -2749,7 +2788,7 @@ function routeEdges(style) {
2749
2788
  const slots = slotsByGap[layerNo] ?? 0;
2750
2789
  const routesNearTarget = candidatesByGap[layerNo]?.some((candidate) => hasZeroFixedSideTarget(candidate.edge) && candidatesByGap[layerNo].filter(({ edge }) => edge.targetId === candidate.edge.targetId).length > candidatesByGap[layerNo].filter(({ edge }) => edge.sourceId === candidate.edge.sourceId).length);
2751
2790
  const preservedGap = (existingGapByLayer[layerNo] ?? input.spacing.layer) - (routesNearTarget ? edgeEdgeSpacing : 0);
2752
- const gapSpacing = slots === 0 ? existingGapByLayer[layerNo] ?? input.spacing.layer : Math.max(preservesNodeFlexibilityGap || (labelExtraByGap[layerNo] ?? 0) > 0 || [...selfLoopsByNodeId].some(([id, loops]) => (flowLayerByNodeId.get(id) === layerNo || flowLayerByNodeId.get(id) === layerNo + 1) && loops.some((edge) => input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true)) ? preservedGap : input.spacing.layer, 2 * edgeNodeSpacing + Math.max(0, slots - 1) * edgeEdgeSpacing);
2791
+ const gapSpacing = slots === 0 ? existingGapByLayer[layerNo] ?? input.spacing.layer : Math.max(preservesNodeFlexibilityGap || (labelExtraByGap[layerNo] ?? 0) > 0 || compactLabelCorridorGaps.has(layerNo) || [...selfLoopsByNodeId].some(([id, loops]) => (flowLayerByNodeId.get(id) === layerNo || flowLayerByNodeId.get(id) === layerNo + 1) && loops.some((edge) => input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true)) ? preservedGap : input.spacing.layer, 2 * edgeNodeSpacing + Math.max(0, slots - 1) * edgeEdgeSpacing);
2753
2792
  nextStart = bounds.end + gapSpacing;
2754
2793
  }
2755
2794
  implicitEndpoints = implicitEdgeEndpoints(input, placement, orientation);
@@ -1,4 +1,4 @@
1
- import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-COIyXvwb.mjs";
1
+ import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-CoeH9LRO.mjs";
2
2
  import { getNodeSize } from "@statelyai/graph/layout";
3
3
 
4
4
  //#region src/fixed.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/layout",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Extensible, graph-native layout algorithms for @statelyai/graph",
5
5
  "license": "EPL-2.0 OR GPL-3.0-or-later",
6
6
  "repository": {
@@ -3645,7 +3645,57 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
3645
3645
  flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
3646
3646
  }
3647
3647
 
3648
+ const increasing = input.direction === "right" || input.direction === "down";
3649
+ const flowPortProtrusion = (edge: GraphEdge, endpoint: "source" | "target"): number => {
3650
+ const node = nodeById.get(endpoint === "source" ? edge.sourceId : edge.targetId);
3651
+ const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
3652
+ const port = node?.ports?.find((candidate) => candidate.name === portName);
3653
+ if (!node || !port) return 0;
3654
+ const side = input.portSettings?.(port, node)?.["port.side"];
3655
+ const flowSide = horizontal
3656
+ ? endpoint === "source"
3657
+ ? input.direction === "right"
3658
+ ? "EAST"
3659
+ : "WEST"
3660
+ : input.direction === "right"
3661
+ ? "WEST"
3662
+ : "EAST"
3663
+ : endpoint === "source"
3664
+ ? input.direction === "down"
3665
+ ? "SOUTH"
3666
+ : "NORTH"
3667
+ : input.direction === "down"
3668
+ ? "NORTH"
3669
+ : "SOUTH";
3670
+ return side === flowSide ? (horizontal ? (port.width ?? 0) : (port.height ?? 0)) : 0;
3671
+ };
3672
+ const adjacentNodeIds = new Map<string, Set<string>>();
3673
+ for (const edge of input.graph.edges) {
3674
+ const sourceNeighbors = adjacentNodeIds.get(edge.sourceId) ?? new Set<string>();
3675
+ sourceNeighbors.add(edge.targetId);
3676
+ adjacentNodeIds.set(edge.sourceId, sourceNeighbors);
3677
+ const targetNeighbors = adjacentNodeIds.get(edge.targetId) ?? new Set<string>();
3678
+ targetNeighbors.add(edge.sourceId);
3679
+ adjacentNodeIds.set(edge.targetId, targetNeighbors);
3680
+ }
3681
+ const feedbackComponentNodeIds = new Set<string>();
3682
+ for (const edge of input.graph.edges) {
3683
+ const sourceLayer = flowLayerByNodeId.get(edge.sourceId);
3684
+ const targetLayer = flowLayerByNodeId.get(edge.targetId);
3685
+ if (sourceLayer === undefined || targetLayer === undefined) continue;
3686
+ if (sourceLayer === targetLayer) continue;
3687
+ const forward = increasing ? targetLayer > sourceLayer : targetLayer < sourceLayer;
3688
+ if (forward) continue;
3689
+ const pending = [edge.sourceId, edge.targetId];
3690
+ while (pending.length > 0) {
3691
+ const nodeId = pending.pop()!;
3692
+ if (feedbackComponentNodeIds.has(nodeId)) continue;
3693
+ feedbackComponentNodeIds.add(nodeId);
3694
+ pending.push(...(adjacentNodeIds.get(nodeId) ?? []));
3695
+ }
3696
+ }
3648
3697
  const labelExtraByGap = flowLayers.slice(0, -1).map(() => 0);
3698
+ const compactLabelCorridorGaps = new Set<number>();
3649
3699
  for (const edge of input.graph.edges) {
3650
3700
  const labelFlowSize = horizontal ? (edge.width ?? 0) : (edge.height ?? 0);
3651
3701
  if (labelFlowSize <= 0) continue;
@@ -3654,11 +3704,25 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
3654
3704
  if (sourceLayer === undefined || targetLayer === undefined) continue;
3655
3705
  if (Math.abs(sourceLayer - targetLayer) !== 1) continue;
3656
3706
  const placement = input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER";
3657
- const extra =
3658
- placement === "CENTER"
3707
+ const edgeNodeSpacing = Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10);
3708
+ // ELK models center labels as dummy layers. For an exterior port, using node-node
3709
+ // spacing beyond the port double-counts the corridor. Treat the label as edge
3710
+ // geometry while retaining normal layer spacing when it is the larger clearance.
3711
+ const requiredGap =
3712
+ labelFlowSize +
3713
+ Math.max(input.spacing.layer, flowPortProtrusion(edge, "source") + edgeNodeSpacing) +
3714
+ Math.max(input.spacing.layer, flowPortProtrusion(edge, "target") + edgeNodeSpacing);
3715
+ const gap = Math.min(sourceLayer, targetLayer);
3716
+ const availableGap = flowLayers[gap + 1]!.start - flowLayers[gap]!.end;
3717
+ const forward = increasing ? targetLayer > sourceLayer : targetLayer < sourceLayer;
3718
+ const compactCorridor =
3719
+ placement === "CENTER" && forward && !feedbackComponentNodeIds.has(edge.sourceId);
3720
+ if (compactCorridor) compactLabelCorridorGaps.add(gap);
3721
+ const extra = compactCorridor
3722
+ ? Math.max(0, requiredGap - availableGap)
3723
+ : placement === "CENTER"
3659
3724
  ? labelFlowSize + input.spacing.layer
3660
3725
  : labelFlowSize + Number(input.settings["spacing.edgeLabel"] ?? 2);
3661
- const gap = Math.min(sourceLayer, targetLayer);
3662
3726
  labelExtraByGap[gap] = Math.max(labelExtraByGap[gap] ?? 0, extra);
3663
3727
  }
3664
3728
  let labelShift = 0;
@@ -4049,6 +4113,7 @@ function routeEdges(style: "ORTHOGONAL" | "POLYLINE" | "SPLINES"): EdgeRouter {
4049
4113
  : Math.max(
4050
4114
  preservesNodeFlexibilityGap ||
4051
4115
  (labelExtraByGap[layerNo] ?? 0) > 0 ||
4116
+ compactLabelCorridorGaps.has(layerNo) ||
4052
4117
  [...selfLoopsByNodeId].some(
4053
4118
  ([id, loops]) =>
4054
4119
  (flowLayerByNodeId.get(id) === layerNo ||