@statelyai/layout 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/elkjs/index.mjs +16 -12
- package/dist/index.mjs +2 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-QwJn2gb2.mjs → layered-Bp8MkxWK.mjs} +361 -15
- package/dist/{spore-D15xIQKj.mjs → spore-ePWz2LtF.mjs} +1 -1
- package/package.json +5 -1
- package/src/elkjs/index.ts +25 -24
- package/src/layered/index.ts +488 -7
- package/src/layered/long-edges.ts +7 -6
- package/src/layered/strategies.ts +66 -6
package/README.md
CHANGED
|
@@ -9,12 +9,15 @@ return `VisualGraph`; positions remain node fields and routes remain
|
|
|
9
9
|
|
|
10
10
|
## Status
|
|
11
11
|
|
|
12
|
+
<!-- layered compatibility coverage from test/oracle*.test.ts -->
|
|
13
|
+
|
|
12
14
|
The native layered implementation covers the complete 152-option elkjs 0.11.1
|
|
13
15
|
layered inventory with one simplified typed name per ELK option. Flat and
|
|
14
16
|
compound graphs, cross-hierarchy edges, ports, labels, self-loops, wrapping,
|
|
15
17
|
four directions, constraints, and replaceable phases are differential-tested
|
|
16
|
-
against elkjs
|
|
17
|
-
|
|
18
|
+
against elkjs, including a curated complex state-machine corpus in both primary
|
|
19
|
+
layout orientations. Partial, incremental, and route-only layout remain
|
|
20
|
+
explicit unimplemented capabilities.
|
|
18
21
|
|
|
19
22
|
## Install
|
|
20
23
|
|
package/dist/elkjs/index.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-
|
|
2
|
-
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-
|
|
1
|
+
import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-Bp8MkxWK.mjs";
|
|
2
|
+
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-ePWz2LtF.mjs";
|
|
3
3
|
import { createGraph } from "@statelyai/graph";
|
|
4
4
|
|
|
5
5
|
//#region src/elkjs/index.ts
|
|
6
|
+
function isLayoutEdgeLabel(label) {
|
|
7
|
+
return getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true && (Boolean(label.text) || label.width !== void 0 || label.height !== void 0);
|
|
8
|
+
}
|
|
6
9
|
var ELK = class {
|
|
7
10
|
#options;
|
|
8
11
|
#algorithmIds;
|
|
@@ -573,8 +576,9 @@ function applyNodeMicroLayout(graph, globalOptions) {
|
|
|
573
576
|
const configuredSizeOptions = getOption(node.layoutOptions ?? {}, "nodeSize.options");
|
|
574
577
|
const sizeOptions = new Set(configuredSizeOptions === void 0 ? ["DEFAULT_MINIMUM_SIZE"] : String(configuredSizeOptions).split(/[\s,;]+/).filter(Boolean));
|
|
575
578
|
const effectivelyFixedPortLabelSize = constraints.includes("PORT_LABELS") && !constraints.includes("NODE_LABELS") && !constraints.includes("MINIMUM_SIZE");
|
|
576
|
-
|
|
577
|
-
let
|
|
579
|
+
const preserveComputedCompoundSize = (node.children?.length ?? 0) > 0;
|
|
580
|
+
let width = effectivelyFixedPortLabelSize || preserveComputedCompoundSize ? node.width ?? 0 : 0;
|
|
581
|
+
let height = effectivelyFixedPortLabelSize || preserveComputedCompoundSize ? node.height ?? 0 : 0;
|
|
578
582
|
let insideHorizontalInset = 0;
|
|
579
583
|
let insideVerticalInset = 0;
|
|
580
584
|
const insideLabelCells = /* @__PURE__ */ new Map();
|
|
@@ -599,8 +603,8 @@ function applyNodeMicroLayout(graph, globalOptions) {
|
|
|
599
603
|
}
|
|
600
604
|
width = Math.max(width, (Math.max(sideCounts.NORTH, sideCounts.SOUTH) + 1) * spacing);
|
|
601
605
|
height = Math.max(height, (Math.max(sideCounts.EAST, sideCounts.WEST) + 1) * spacing);
|
|
602
|
-
if (sideCounts.NORTH === 0 && sideCounts.SOUTH === 0) width = 0;
|
|
603
|
-
if (sideCounts.EAST === 0 && sideCounts.WEST === 0) height = 0;
|
|
606
|
+
if (!preserveComputedCompoundSize && sideCounts.NORTH === 0 && sideCounts.SOUTH === 0) width = 0;
|
|
607
|
+
if (!preserveComputedCompoundSize && sideCounts.EAST === 0 && sideCounts.WEST === 0) height = 0;
|
|
604
608
|
}
|
|
605
609
|
if (constraints.includes("NODE_LABELS")) {
|
|
606
610
|
for (const label of node.labels ?? []) {
|
|
@@ -896,7 +900,7 @@ function toGraph(root, globalOptions = {}) {
|
|
|
896
900
|
if (isInsideSelfLoop(root, edge)) return [];
|
|
897
901
|
const source = endpoint(edge.sources?.[0] ?? edge.source, portOwnerById);
|
|
898
902
|
const target = endpoint(edge.targets?.[0] ?? edge.target, portOwnerById);
|
|
899
|
-
const labels = (edge.labels ?? []).filter(
|
|
903
|
+
const labels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
|
|
900
904
|
const labelLabelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
|
|
901
905
|
const labelWidth = Math.max(0, ...labels.map((label) => label.width ?? 0));
|
|
902
906
|
const labelHeight = labels.reduce((sum, label) => sum + (label.height ?? 0), 0) + Math.max(0, labels.length - 1) * labelLabelSpacing;
|
|
@@ -999,7 +1003,7 @@ function applyLayout(root, graph, padding, layoutOptions) {
|
|
|
999
1003
|
label.y ??= 0;
|
|
1000
1004
|
continue;
|
|
1001
1005
|
}
|
|
1002
|
-
if (!label
|
|
1006
|
+
if (!isLayoutEdgeLabel(label)) {
|
|
1003
1007
|
label.x ??= 0;
|
|
1004
1008
|
label.y ??= 0;
|
|
1005
1009
|
continue;
|
|
@@ -1025,7 +1029,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1025
1029
|
minimumY = Math.min(minimumY, node.y ?? 0, ...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0)), ...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0)), ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0))));
|
|
1026
1030
|
}
|
|
1027
1031
|
for (const edge of root.edges ?? []) {
|
|
1028
|
-
const laidOutLabels = (edge.labels ?? []).filter(
|
|
1032
|
+
const laidOutLabels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
|
|
1029
1033
|
minimumX = Math.min(minimumX, ...laidOutLabels.map((label) => label.x ?? 0));
|
|
1030
1034
|
minimumY = Math.min(minimumY, ...laidOutLabels.map((label) => label.y ?? 0));
|
|
1031
1035
|
if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") !== true) {
|
|
@@ -1054,7 +1058,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1054
1058
|
point.x += shiftX;
|
|
1055
1059
|
point.y += shiftY;
|
|
1056
1060
|
}
|
|
1057
|
-
for (const label of (edge.labels ?? []).filter(
|
|
1061
|
+
for (const label of (edge.labels ?? []).filter(isLayoutEdgeLabel)) {
|
|
1058
1062
|
label.x = (label.x ?? 0) + shiftX;
|
|
1059
1063
|
label.y = (label.y ?? 0) + shiftY;
|
|
1060
1064
|
}
|
|
@@ -1090,7 +1094,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1090
1094
|
...(node.labels ?? []).map((label) => (node.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)),
|
|
1091
1095
|
...(node.ports ?? []).map((port) => (node.x ?? 0) + (port.x ?? 0) + (port.width ?? 0)),
|
|
1092
1096
|
...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.x ?? 0) + (port.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)))
|
|
1093
|
-
]), ...(root.edges ?? []).flatMap((edge) => (edge.labels ?? []).filter(
|
|
1097
|
+
]), ...(root.edges ?? []).flatMap((edge) => (edge.labels ?? []).filter(isLayoutEdgeLabel).map((label) => (label.x ?? 0) + (label.width ?? 0))), ...(root.edges ?? []).flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
|
|
1094
1098
|
section.startPoint.x,
|
|
1095
1099
|
...(section.bendPoints ?? []).map((point) => point.x),
|
|
1096
1100
|
section.endPoint.x
|
|
@@ -1100,7 +1104,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1100
1104
|
...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)),
|
|
1101
1105
|
...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0) + (port.height ?? 0)),
|
|
1102
1106
|
...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)))
|
|
1103
|
-
]), ...(root.edges ?? []).flatMap((edge) => (edge.labels ?? []).filter(
|
|
1107
|
+
]), ...(root.edges ?? []).flatMap((edge) => (edge.labels ?? []).filter(isLayoutEdgeLabel).map((label) => (label.y ?? 0) + (label.height ?? 0) + (String(getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER") === "CENTER" ? 1 : 0))), ...(root.edges ?? []).flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
|
|
1104
1108
|
section.startPoint.y,
|
|
1105
1109
|
...(section.bendPoints ?? []).map((point) => point.y),
|
|
1106
1110
|
section.endPoint.y
|
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-
|
|
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-
|
|
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-Bp8MkxWK.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-ePWz2LtF.mjs";
|
|
3
3
|
import { getGraphIssues } from "@statelyai/graph";
|
|
4
4
|
|
|
5
5
|
//#region src/layout.ts
|
package/dist/layered/index.mjs
CHANGED
|
@@ -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-
|
|
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-Bp8MkxWK.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 };
|
|
@@ -195,6 +195,20 @@ const phaseRandomByInput = /* @__PURE__ */ new WeakMap();
|
|
|
195
195
|
function getOrientedEndpoints$1(edge, orientation) {
|
|
196
196
|
return orientation.reversedEdgeIds.has(edge.id) ? [edge.targetId, edge.sourceId] : [edge.sourceId, edge.targetId];
|
|
197
197
|
}
|
|
198
|
+
function getOrientedPortDirection(input, orientation, node, port) {
|
|
199
|
+
const configuredSide = input.portSettings?.(port, node)?.["port.side"];
|
|
200
|
+
if (configuredSide !== "NORTH" && configuredSide !== "SOUTH" && configuredSide !== "WEST" && configuredSide !== "EAST") return port.direction;
|
|
201
|
+
let incoming = false;
|
|
202
|
+
let outgoing = false;
|
|
203
|
+
for (const edge of input.graph.edges) {
|
|
204
|
+
const reversed = orientation.reversedEdgeIds.has(edge.id);
|
|
205
|
+
if (edge.sourceId === node.id && edge.sourcePort === port.name) if (reversed) incoming = true;
|
|
206
|
+
else outgoing = true;
|
|
207
|
+
if (edge.targetId === node.id && edge.targetPort === port.name) if (reversed) outgoing = true;
|
|
208
|
+
else incoming = true;
|
|
209
|
+
}
|
|
210
|
+
return incoming && outgoing ? "inout" : outgoing ? "out" : incoming ? "in" : port.direction;
|
|
211
|
+
}
|
|
198
212
|
function cycleModelOrder(input) {
|
|
199
213
|
const enforced = input.settings["considerModelOrder.groupModelOrder.cbGroupOrderStrategy"] === "ENFORCED";
|
|
200
214
|
const count = Math.max(1, input.graph.nodes.length);
|
|
@@ -2917,7 +2931,11 @@ function routeEdges(style) {
|
|
|
2917
2931
|
const sourceAwaySide = horizontal ? sourceRect.x < targetRect.x ? "WEST" : "EAST" : sourceRect.y < targetRect.y ? "NORTH" : "SOUTH";
|
|
2918
2932
|
const targetAwaySide = horizontal ? sourceRect.x < targetRect.x ? "EAST" : "WEST" : sourceRect.y < targetRect.y ? "SOUTH" : "NORTH";
|
|
2919
2933
|
const sameSideSelfLoop = source.id === target.id && feedbackSourcePortSide !== void 0 && feedbackSourcePortSide === feedbackTargetPortSide;
|
|
2920
|
-
const
|
|
2934
|
+
const hasFixedPortSide = (node) => {
|
|
2935
|
+
const constraints = String(input.nodeSettings?.(node)?.portConstraints ?? "UNDEFINED");
|
|
2936
|
+
return constraints !== "UNDEFINED" && constraints !== "FREE";
|
|
2937
|
+
};
|
|
2938
|
+
const fixedSideFeedback = style === "ORTHOGONAL" && !sameSideSelfLoop && (hasFixedPortSide(source) && feedbackSourcePortSide === sourceAwaySide || hasFixedPortSide(target) && feedbackTargetPortSide === targetAwaySide);
|
|
2921
2939
|
if (input.settings.feedbackEdges === true && reversedEdge || fixedSideFeedback) {
|
|
2922
2940
|
if (fixedSideFeedback) outsideFeedbackEdgeIds.add(edge.id);
|
|
2923
2941
|
const crossSpacing = Number(input.settings["spacing.edgeNode"] ?? 10);
|
|
@@ -3296,8 +3314,9 @@ function routeEdges(style) {
|
|
|
3296
3314
|
const fallbackMatchesPortSide = (fallback, rect, side) => side === void 0 || side === "UNDEFINED" || side === "EAST" && Math.abs(fallback.x - rect.x - rect.width) < 1e-9 || side === "WEST" && Math.abs(fallback.x - rect.x) < 1e-9 || side === "SOUTH" && Math.abs(fallback.y - rect.y - rect.height) < 1e-9 || side === "NORTH" && Math.abs(fallback.y - rect.y) < 1e-9;
|
|
3297
3315
|
const sourceFixedSide = sourcePort !== void 0 && fallbackMatchesPortSide(sourceFallback, sourceRect, sourcePortSide) && (sourcePort.width ?? 8) === 0 && (sourcePort.height ?? 8) === 0 && input.nodeSettings?.(source)?.portConstraints === "FIXED_SIDE" && input.graph.edges.filter((candidate) => candidate.sourceId === edge.sourceId && candidate.sourcePort === edge.sourcePort).length === 1;
|
|
3298
3316
|
const targetFixedSide = targetPort !== void 0 && fallbackMatchesPortSide(targetFallback, targetRect, targetPortSide) && (targetPort.width ?? 8) === 0 && (targetPort.height ?? 8) === 0 && input.nodeSettings?.(target)?.portConstraints === "FIXED_SIDE" && input.graph.edges.filter((candidate) => candidate.targetId === edge.targetId && candidate.targetPort === edge.targetPort).length === 1;
|
|
3299
|
-
const
|
|
3300
|
-
const
|
|
3317
|
+
const flexibleFeedback = reversedEdge && !hasFixedPortSide(source) && !hasFixedPortSide(target);
|
|
3318
|
+
const start = flexibleFeedback ? sourceFallback : sourceFixedSide ? sourceFallback : getPortPoint(source, edge.sourcePort, sourceRect, sourceFallback, input.direction, input);
|
|
3319
|
+
const end = flexibleFeedback ? targetFallback : targetFixedSide ? targetFallback : getPortPoint(target, edge.targetPort, targetRect, targetFallback, input.direction, input);
|
|
3301
3320
|
const sourceLayer = flowLayerByNodeId.get(edge.sourceId) ?? 0;
|
|
3302
3321
|
const targetLayer = flowLayerByNodeId.get(edge.targetId) ?? 0;
|
|
3303
3322
|
const earlierLayer = Math.min(sourceLayer, targetLayer);
|
|
@@ -3450,7 +3469,7 @@ function routeEdges(style) {
|
|
|
3450
3469
|
const routeEdgesOrthogonally = routeEdges("ORTHOGONAL");
|
|
3451
3470
|
const routeEdgesWithPolylines = routeEdges("POLYLINE");
|
|
3452
3471
|
const routeEdgesWithSplines = routeEdges("SPLINES");
|
|
3453
|
-
function placePorts(ports, rect, direction, portSettings, nodeSettings) {
|
|
3472
|
+
function placePorts(ports, rect, direction, portSettings, nodeSettings, portDirection) {
|
|
3454
3473
|
if (!ports) return void 0;
|
|
3455
3474
|
const horizontal = direction === "left" || direction === "right";
|
|
3456
3475
|
const reverse = direction === "up" || direction === "left";
|
|
@@ -3464,7 +3483,7 @@ function placePorts(ports, rect, direction, portSettings, nodeSettings) {
|
|
|
3464
3483
|
sideByPort.set(port, configured);
|
|
3465
3484
|
continue;
|
|
3466
3485
|
}
|
|
3467
|
-
const outgoing = port.direction === "out";
|
|
3486
|
+
const outgoing = (portDirection?.(port) ?? port.direction) === "out";
|
|
3468
3487
|
const normalFarSide = reverse ? !outgoing : outgoing;
|
|
3469
3488
|
const farSide = sideFixed && configured === "UNDEFINED" ? !normalFarSide : normalFarSide;
|
|
3470
3489
|
sideByPort.set(port, horizontal ? farSide ? "EAST" : "WEST" : farSide ? "SOUTH" : "NORTH");
|
|
@@ -3574,7 +3593,7 @@ function placePorts(ports, rect, direction, portSettings, nodeSettings) {
|
|
|
3574
3593
|
});
|
|
3575
3594
|
}
|
|
3576
3595
|
/** Account for port extents in ELK's graph-padding normalization. */
|
|
3577
|
-
function normalizePlacementForPortExtents(input, placement, order) {
|
|
3596
|
+
function normalizePlacementForPortExtents(input, placement, order, orientation) {
|
|
3578
3597
|
const horizontal = input.direction === "left" || input.direction === "right";
|
|
3579
3598
|
const physicalLayers = order.layers.map((layer) => layer.flatMap((id) => placement.rectByNodeId.has(id) ? [id] : [])).filter((layer) => layer.length > 0).sort((left, right) => {
|
|
3580
3599
|
const leftRect = placement.rectByNodeId.get(left[0]);
|
|
@@ -3609,7 +3628,7 @@ function normalizePlacementForPortExtents(input, placement, order) {
|
|
|
3609
3628
|
const ports = placePorts(node.ports, rect, input.direction, (port) => input.portSettings?.(port, node), {
|
|
3610
3629
|
...input.settings,
|
|
3611
3630
|
...input.nodeSettings?.(node)
|
|
3612
|
-
});
|
|
3631
|
+
}, (port) => getOrientedPortDirection(input, orientation, node, port));
|
|
3613
3632
|
for (const port of ports ?? []) {
|
|
3614
3633
|
const extent$1 = horizontal ? trailing ? Math.max(0, (port.x ?? 0) + (port.width ?? 0) - rect.width) : Math.max(0, -(port.x ?? 0)) : trailing ? Math.max(0, (port.y ?? 0) + (port.height ?? 0) - rect.height) : Math.max(0, -(port.y ?? 0));
|
|
3615
3634
|
if (trailing) trailingExtent = Math.max(trailingExtent, extent$1);
|
|
@@ -3628,7 +3647,7 @@ function normalizePlacementForPortExtents(input, placement, order) {
|
|
|
3628
3647
|
const ports = placePorts(node.ports, rect, input.direction, (port) => input.portSettings?.(port, node), {
|
|
3629
3648
|
...input.settings,
|
|
3630
3649
|
...input.nodeSettings?.(node)
|
|
3631
|
-
});
|
|
3650
|
+
}, (port) => getOrientedPortDirection(input, orientation, node, port));
|
|
3632
3651
|
for (const port of ports ?? []) {
|
|
3633
3652
|
minimumX = Math.min(minimumX, rect.x + (port.x ?? 0));
|
|
3634
3653
|
minimumY = Math.min(minimumY, rect.y + (port.y ?? 0));
|
|
@@ -4165,7 +4184,12 @@ function splitLongEdges(input, orientation, assignment) {
|
|
|
4165
4184
|
const targetPort = target?.ports?.find((port) => port.name === edge.targetPort);
|
|
4166
4185
|
const sourceSide = source && sourcePort ? input.portSettings?.(sourcePort, source)?.["port.side"] : void 0;
|
|
4167
4186
|
const targetSide = target && targetPort ? input.portSettings?.(targetPort, target)?.["port.side"] : void 0;
|
|
4168
|
-
const
|
|
4187
|
+
const hasFixedPortSide = (node) => {
|
|
4188
|
+
if (!node) return false;
|
|
4189
|
+
const constraints = String(input.nodeSettings?.(node)?.portConstraints ?? "UNDEFINED");
|
|
4190
|
+
return constraints !== "UNDEFINED" && constraints !== "FREE";
|
|
4191
|
+
};
|
|
4192
|
+
const fixedSideFeedback = sourceLayer > targetLayer && (hasFixedPortSide(source) && sourceSide === forwardSourceSide || hasFixedPortSide(target) && targetSide === forwardTargetSide);
|
|
4169
4193
|
if (span <= 1 || edge.sourceId === edge.targetId || input.settings.feedbackEdges === true && orientation.reversedEdgeIds.has(edge.id) || fixedSideFeedback) {
|
|
4170
4194
|
edges.push(edge);
|
|
4171
4195
|
originalEdgeBySegmentId.set(edge.id, edge);
|
|
@@ -8459,6 +8483,24 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8459
8483
|
left: options.padding?.left ?? 12
|
|
8460
8484
|
};
|
|
8461
8485
|
const switchedSideByPort = /* @__PURE__ */ new Map();
|
|
8486
|
+
const parallelPortIndexByKey = /* @__PURE__ */ new Map();
|
|
8487
|
+
const labeledEdgesByPair = /* @__PURE__ */ new Map();
|
|
8488
|
+
for (const edge of graph.edges) {
|
|
8489
|
+
const settings = options.edgeSettings?.(edge);
|
|
8490
|
+
if (edge.sourcePort === void 0 || edge.targetPort === void 0 || edge.sourceId === edge.targetId || (edge.width ?? 0) <= 0 || (edge.height ?? 0) <= 0 || (settings?.["edgeLabels.placement"] ?? "CENTER") !== "CENTER" || settings?.["edgeLabels.inline"] !== true) continue;
|
|
8491
|
+
const key = `${edge.sourceId}\0${edge.targetId}`;
|
|
8492
|
+
const pair = labeledEdgesByPair.get(key) ?? [];
|
|
8493
|
+
pair.push(edge);
|
|
8494
|
+
labeledEdgesByPair.set(key, pair);
|
|
8495
|
+
}
|
|
8496
|
+
for (const pair of labeledEdgesByPair.values()) {
|
|
8497
|
+
if (pair.length < 2) continue;
|
|
8498
|
+
for (const [index, edge] of pair.entries()) {
|
|
8499
|
+
const portIndex = options.settings?.["considerModelOrder.strategy"] === "PREFER_NODES" ? index : pair.length - index - 1;
|
|
8500
|
+
parallelPortIndexByKey.set(`${edge.sourceId}\0${edge.sourcePort}`, portIndex);
|
|
8501
|
+
parallelPortIndexByKey.set(`${edge.targetId}\0${edge.targetPort}`, portIndex);
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8462
8504
|
const input = {
|
|
8463
8505
|
graph,
|
|
8464
8506
|
sizes: new Map(graph.nodes.map((node) => [node.id, getNodeSize(node, options)])),
|
|
@@ -8477,6 +8519,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8477
8519
|
...options.edgeSettings === void 0 ? {} : { edgeSettings: options.edgeSettings },
|
|
8478
8520
|
portSettings: (port, node) => ({
|
|
8479
8521
|
...options.portSettings?.(port, node),
|
|
8522
|
+
...parallelPortIndexByKey.has(`${node.id}\0${port.name}`) ? { "port.index": parallelPortIndexByKey.get(`${node.id}\0${port.name}`) } : {},
|
|
8480
8523
|
...switchedSideByPort.has(`${node.id}\0${port.name}`) ? { "port.side": switchedSideByPort.get(`${node.id}\0${port.name}`) } : {}
|
|
8481
8524
|
})
|
|
8482
8525
|
};
|
|
@@ -8674,7 +8717,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8674
8717
|
});
|
|
8675
8718
|
}
|
|
8676
8719
|
}
|
|
8677
|
-
measure("port-margin-normalization", () => normalizePlacementForPortExtents(expanded.input, placement, order));
|
|
8720
|
+
measure("port-margin-normalization", () => normalizePlacementForPortExtents(expanded.input, placement, order, expanded.orientation));
|
|
8678
8721
|
{
|
|
8679
8722
|
const horizontal = direction === "left" || direction === "right";
|
|
8680
8723
|
const crossCenter = (nodeId) => {
|
|
@@ -8699,8 +8742,197 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8699
8742
|
}
|
|
8700
8743
|
const edgeRouting = options.settings?.edgeRouting ?? "ORTHOGONAL";
|
|
8701
8744
|
const edgeRouter = options.strategies?.routeEdges ?? (edgeRouting === "POLYLINE" ? routeEdgesWithPolylines : edgeRouting === "SPLINES" ? routeEdgesWithSplines : routeEdgesOrthogonally);
|
|
8702
|
-
|
|
8745
|
+
let expandedRoutes = measure("edge-routing", () => edgeRouter(expanded.input, expanded.orientation, placement));
|
|
8703
8746
|
measure("post-compaction", () => applyPostCompaction(expanded.input, placement, expandedRoutes));
|
|
8747
|
+
const antiparallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8748
|
+
const outerAntiparallelLabelIds = /* @__PURE__ */ new Set();
|
|
8749
|
+
const parallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8750
|
+
const postCompactionNodeCrossDeltas = /* @__PURE__ */ new Map();
|
|
8751
|
+
const horizontalAntiparallelFlow = direction === "left" || direction === "right";
|
|
8752
|
+
if (options.strategies?.routeEdges === void 0 && options.settings?.["cycleBreaking.strategy"] === "MODEL_ORDER" && options.settings?.["layering.strategy"] === "INTERACTIVE" && options.settings?.["crossingMinimization.forceNodeModelOrder"] === true && options.settings?.["nodePlacement.strategy"] === "BRANDES_KOEPF" && options.settings?.["nodePlacement.favorStraightEdges"] === true && options.settings?.["compaction.postCompaction.strategy"] === "LEFT" && options.settings?.["compaction.postCompaction.constraints"] === "SCANLINE") {
|
|
8753
|
+
const neighbors = new Map(graph.nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
|
|
8754
|
+
for (const edge of graph.edges) {
|
|
8755
|
+
if (edge.sourceId === edge.targetId) continue;
|
|
8756
|
+
neighbors.get(edge.sourceId)?.add(edge.targetId);
|
|
8757
|
+
neighbors.get(edge.targetId)?.add(edge.sourceId);
|
|
8758
|
+
}
|
|
8759
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8760
|
+
for (const node of graph.nodes) {
|
|
8761
|
+
if (visited.has(node.id)) continue;
|
|
8762
|
+
const component = [];
|
|
8763
|
+
const pending = [node.id];
|
|
8764
|
+
while (pending.length > 0) {
|
|
8765
|
+
const id = pending.pop();
|
|
8766
|
+
if (visited.has(id)) continue;
|
|
8767
|
+
visited.add(id);
|
|
8768
|
+
const member = graph.nodes.find((candidate) => candidate.id === id);
|
|
8769
|
+
if (member) component.push(member);
|
|
8770
|
+
for (const neighbor of neighbors.get(id) ?? []) pending.push(neighbor);
|
|
8771
|
+
}
|
|
8772
|
+
if (component.length < 2) continue;
|
|
8773
|
+
const componentIds = new Set(component.map((member) => member.id));
|
|
8774
|
+
if (!graph.edges.some((edge) => componentIds.has(edge.sourceId) && componentIds.has(edge.targetId) && expanded.orientation.reversedEdgeIds.has(edge.id))) continue;
|
|
8775
|
+
const directedPairCounts = /* @__PURE__ */ new Map();
|
|
8776
|
+
for (const edge of graph.edges) {
|
|
8777
|
+
if (!componentIds.has(edge.sourceId) || !componentIds.has(edge.targetId)) continue;
|
|
8778
|
+
const key = `${edge.sourceId}\0${edge.targetId}`;
|
|
8779
|
+
directedPairCounts.set(key, (directedPairCounts.get(key) ?? 0) + 1);
|
|
8780
|
+
}
|
|
8781
|
+
if ([...directedPairCounts.values()].some((count) => count > 1)) continue;
|
|
8782
|
+
if (component.some((member) => {
|
|
8783
|
+
const constraints = options.nodeSettings?.(member)?.portConstraints;
|
|
8784
|
+
return constraints !== void 0 && constraints !== "UNDEFINED" && constraints !== "FREE";
|
|
8785
|
+
})) continue;
|
|
8786
|
+
const layerCounts = /* @__PURE__ */ new Map();
|
|
8787
|
+
for (const member of component) {
|
|
8788
|
+
const layer = expanded.assignment.layerByNodeId.get(member.id);
|
|
8789
|
+
if (layer === void 0) continue;
|
|
8790
|
+
layerCounts.set(layer, (layerCounts.get(layer) ?? 0) + 1);
|
|
8791
|
+
}
|
|
8792
|
+
if ([...layerCounts.values()].some((count) => count !== 1)) continue;
|
|
8793
|
+
const rects = component.flatMap((member) => {
|
|
8794
|
+
const rect = mutableRects.get(member.id);
|
|
8795
|
+
return rect ? [{
|
|
8796
|
+
member,
|
|
8797
|
+
rect
|
|
8798
|
+
}] : [];
|
|
8799
|
+
});
|
|
8800
|
+
if (rects.length !== component.length) continue;
|
|
8801
|
+
const center = (Math.min(...rects.map(({ rect }) => horizontalAntiparallelFlow ? rect.y : rect.x)) + Math.max(...rects.map(({ rect }) => horizontalAntiparallelFlow ? rect.y + rect.height : rect.x + rect.width))) / 2;
|
|
8802
|
+
for (const { member, rect } of rects) {
|
|
8803
|
+
const currentCross = horizontalAntiparallelFlow ? rect.y : rect.x;
|
|
8804
|
+
const nextCross = horizontalAntiparallelFlow ? center - rect.height / 2 : center - rect.width / 2;
|
|
8805
|
+
postCompactionNodeCrossDeltas.set(member.id, nextCross - currentCross);
|
|
8806
|
+
mutableRects.set(member.id, horizontalAntiparallelFlow ? {
|
|
8807
|
+
...rect,
|
|
8808
|
+
y: nextCross
|
|
8809
|
+
} : {
|
|
8810
|
+
...rect,
|
|
8811
|
+
x: nextCross
|
|
8812
|
+
});
|
|
8813
|
+
}
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
{
|
|
8817
|
+
const horizontal = horizontalAntiparallelFlow;
|
|
8818
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
8819
|
+
const pairEdges = /* @__PURE__ */ new Map();
|
|
8820
|
+
for (const edge of graph.edges) {
|
|
8821
|
+
if (edge.sourceId === edge.targetId || (edge.width ?? 0) <= 0 || (edge.height ?? 0) <= 0) continue;
|
|
8822
|
+
const key = [edge.sourceId, edge.targetId].sort().join("\0");
|
|
8823
|
+
const pair = pairEdges.get(key) ?? [];
|
|
8824
|
+
pair.push(edge);
|
|
8825
|
+
pairEdges.set(key, pair);
|
|
8826
|
+
}
|
|
8827
|
+
for (const pair of pairEdges.values()) {
|
|
8828
|
+
if (pair.length !== 2 || pair[0].sourceId !== pair[1].targetId || pair[0].targetId !== pair[1].sourceId || pair.some((edge) => {
|
|
8829
|
+
const settings = options.edgeSettings?.(edge);
|
|
8830
|
+
return (settings?.["edgeLabels.placement"] ?? "CENTER") !== "CENTER" || settings?.["edgeLabels.inline"] !== true;
|
|
8831
|
+
})) continue;
|
|
8832
|
+
const firstNode = nodesById.get(pair[0].sourceId);
|
|
8833
|
+
const secondNode = nodesById.get(pair[0].targetId);
|
|
8834
|
+
const firstRect = placement.rectByNodeId.get(pair[0].sourceId);
|
|
8835
|
+
const secondRect = placement.rectByNodeId.get(pair[0].targetId);
|
|
8836
|
+
if (!firstNode || !secondNode || !firstRect || !secondRect) continue;
|
|
8837
|
+
const nonSelfNeighbors = (nodeId) => new Set(graph.edges.flatMap((edge) => edge.sourceId === edge.targetId ? [] : edge.sourceId === nodeId ? [edge.targetId] : edge.targetId === nodeId ? [edge.sourceId] : []));
|
|
8838
|
+
const isolatedAntiparallelPair = nonSelfNeighbors(firstNode.id).size === 1 && nonSelfNeighbors(secondNode.id).size === 1;
|
|
8839
|
+
const hasFlexiblePorts = (node) => {
|
|
8840
|
+
const constraints = options.nodeSettings?.(node)?.portConstraints;
|
|
8841
|
+
return constraints === void 0 || constraints === "UNDEFINED" || constraints === "FREE";
|
|
8842
|
+
};
|
|
8843
|
+
const hasFlexiblePairPorts = hasFlexiblePorts(firstNode) && hasFlexiblePorts(secondNode);
|
|
8844
|
+
if (isolatedAntiparallelPair && !hasFlexiblePairPorts) continue;
|
|
8845
|
+
const firstBeforeSecond = horizontal ? firstRect.x + firstRect.width / 2 < secondRect.x + secondRect.width / 2 : firstRect.y + firstRect.height / 2 < secondRect.y + secondRect.height / 2;
|
|
8846
|
+
const beforeRect = firstBeforeSecond ? firstRect : secondRect;
|
|
8847
|
+
const afterRect = firstBeforeSecond ? secondRect : firstRect;
|
|
8848
|
+
const forward = pair.find((edge) => firstBeforeSecond ? edge.sourceId === pair[0].sourceId : edge.sourceId === pair[0].targetId);
|
|
8849
|
+
if (!forward?.sourcePort || !forward.targetPort) continue;
|
|
8850
|
+
let cross = Math.min(horizontal ? firstRect.y : firstRect.x, horizontal ? secondRect.y : secondRect.x);
|
|
8851
|
+
const edgeSpacing = Number(options.settings?.["spacing.edgeEdge"] ?? 10);
|
|
8852
|
+
for (const edge of pair) {
|
|
8853
|
+
const width = edge.width ?? 0;
|
|
8854
|
+
const height = edge.height ?? 0;
|
|
8855
|
+
antiparallelLabelPositions.set(edge.id, isolatedAntiparallelPair ? horizontal ? {
|
|
8856
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
8857
|
+
y: cross
|
|
8858
|
+
} : {
|
|
8859
|
+
x: cross,
|
|
8860
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2
|
|
8861
|
+
} : horizontal ? {
|
|
8862
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
8863
|
+
y: edge === forward ? (firstRect.y + firstRect.height / 2 + secondRect.y + secondRect.height / 2) / 2 - height / 2 : Math.max(firstRect.y + firstRect.height, secondRect.y + secondRect.height) + edgeSpacing
|
|
8864
|
+
} : {
|
|
8865
|
+
x: edge === forward ? (firstRect.x + firstRect.width / 2 + secondRect.x + secondRect.width / 2) / 2 - width / 2 : Math.max(firstRect.x + firstRect.width, secondRect.x + secondRect.width) + edgeSpacing,
|
|
8866
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2
|
|
8867
|
+
});
|
|
8868
|
+
if (!isolatedAntiparallelPair && edge !== forward) outerAntiparallelLabelIds.add(edge.id);
|
|
8869
|
+
cross += (horizontal ? height : width) + edgeSpacing;
|
|
8870
|
+
}
|
|
8871
|
+
const forwardLabel = antiparallelLabelPositions.get(forward.id);
|
|
8872
|
+
const forwardCross = horizontal ? Math.round(forwardLabel.y + (forward.height ?? 0) / 2) : Math.round(forwardLabel.x + (forward.width ?? 0) / 2);
|
|
8873
|
+
const alignPort = (node, portName) => {
|
|
8874
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
8875
|
+
const port = node.ports?.find((candidate) => candidate.name === portName);
|
|
8876
|
+
if (!rect || !port) return;
|
|
8877
|
+
const placedPort = placePorts(node.ports, rect, direction, (candidate) => input.portSettings?.(candidate, node), {
|
|
8878
|
+
...options.settings,
|
|
8879
|
+
...options.nodeSettings?.(node)
|
|
8880
|
+
}, (candidate) => getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate))?.find((candidate) => candidate.name === portName);
|
|
8881
|
+
if (!placedPort) return;
|
|
8882
|
+
const nextCross = forwardCross - (horizontal ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2);
|
|
8883
|
+
const currentCross = horizontal ? rect.y : rect.x;
|
|
8884
|
+
postCompactionNodeCrossDeltas.set(node.id, (postCompactionNodeCrossDeltas.get(node.id) ?? 0) + nextCross - currentCross);
|
|
8885
|
+
mutableRects.set(node.id, horizontal ? {
|
|
8886
|
+
...rect,
|
|
8887
|
+
y: nextCross
|
|
8888
|
+
} : {
|
|
8889
|
+
...rect,
|
|
8890
|
+
x: nextCross
|
|
8891
|
+
});
|
|
8892
|
+
};
|
|
8893
|
+
if (isolatedAntiparallelPair) {
|
|
8894
|
+
alignPort(nodesById.get(forward.sourceId), forward.sourcePort);
|
|
8895
|
+
alignPort(nodesById.get(forward.targetId), forward.targetPort);
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
if (postCompactionNodeCrossDeltas.size > 0) {
|
|
8900
|
+
const pointsByEdgeId = new Map(expandedRoutes.pointsByEdgeId);
|
|
8901
|
+
for (const edge of graph.edges) {
|
|
8902
|
+
const points = [...pointsByEdgeId.get(edge.id) ?? []];
|
|
8903
|
+
if (points.length === 0) continue;
|
|
8904
|
+
const sourceDelta = postCompactionNodeCrossDeltas.get(edge.sourceId) ?? 0;
|
|
8905
|
+
const targetDelta = postCompactionNodeCrossDeltas.get(edge.targetId) ?? 0;
|
|
8906
|
+
const shiftEndpoint = (endpointIndex, adjacentIndex, delta) => {
|
|
8907
|
+
if (delta === 0) return;
|
|
8908
|
+
const endpoint = points[endpointIndex];
|
|
8909
|
+
const adjacent = points[adjacentIndex];
|
|
8910
|
+
if (!endpoint) return;
|
|
8911
|
+
const originalCross = horizontalAntiparallelFlow ? endpoint.y : endpoint.x;
|
|
8912
|
+
points[endpointIndex] = horizontalAntiparallelFlow ? {
|
|
8913
|
+
...endpoint,
|
|
8914
|
+
y: endpoint.y + delta
|
|
8915
|
+
} : {
|
|
8916
|
+
...endpoint,
|
|
8917
|
+
x: endpoint.x + delta
|
|
8918
|
+
};
|
|
8919
|
+
if (adjacent && (horizontalAntiparallelFlow ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontalAntiparallelFlow ? {
|
|
8920
|
+
...adjacent,
|
|
8921
|
+
y: adjacent.y + delta
|
|
8922
|
+
} : {
|
|
8923
|
+
...adjacent,
|
|
8924
|
+
x: adjacent.x + delta
|
|
8925
|
+
};
|
|
8926
|
+
};
|
|
8927
|
+
shiftEndpoint(0, 1, sourceDelta);
|
|
8928
|
+
shiftEndpoint(points.length - 1, points.length - 2, targetDelta);
|
|
8929
|
+
pointsByEdgeId.set(edge.id, points);
|
|
8930
|
+
}
|
|
8931
|
+
expandedRoutes = {
|
|
8932
|
+
...expandedRoutes,
|
|
8933
|
+
pointsByEdgeId
|
|
8934
|
+
};
|
|
8935
|
+
}
|
|
8704
8936
|
let routes = measure("long-edge-joining", () => joinLongEdgeRoutes(expandedRoutes, expanded.segmentIdsByEdgeId, edgeRouting === "SPLINES" || options.settings?.unnecessaryBendpoints === true, edgeRouting === "SPLINES", Number(options.settings?.["spacing.edgeNodeBetweenLayers"] ?? 10)));
|
|
8705
8937
|
if (options.settings?.["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT") {
|
|
8706
8938
|
const horizontal = direction === "right" || direction === "left";
|
|
@@ -8832,6 +9064,84 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8832
9064
|
};
|
|
8833
9065
|
routes.pointsByEdgeId.set(edge.id, points);
|
|
8834
9066
|
}
|
|
9067
|
+
for (const pair of labeledEdgesByPair.values()) {
|
|
9068
|
+
if (pair.length < 2) continue;
|
|
9069
|
+
const sourceRect = mutableRects.get(pair[0].sourceId);
|
|
9070
|
+
const targetRect = mutableRects.get(pair[0].targetId);
|
|
9071
|
+
if (!sourceRect || !targetRect) continue;
|
|
9072
|
+
const horizontal = direction === "left" || direction === "right";
|
|
9073
|
+
const beforeRect = horizontal ? sourceRect.x <= targetRect.x ? sourceRect : targetRect : sourceRect.y <= targetRect.y ? sourceRect : targetRect;
|
|
9074
|
+
const afterRect = beforeRect === sourceRect ? targetRect : sourceRect;
|
|
9075
|
+
const edgeSpacing = Number(options.settings?.["spacing.edgeEdge"] ?? 10);
|
|
9076
|
+
let cross = horizontal ? padding.top : padding.left;
|
|
9077
|
+
const orderedPair = options.settings?.["considerModelOrder.strategy"] === "PREFER_NODES" ? pair : [...pair].reverse();
|
|
9078
|
+
for (const edge of orderedPair) {
|
|
9079
|
+
const width = edge.width ?? 0;
|
|
9080
|
+
const height = edge.height ?? 0;
|
|
9081
|
+
parallelLabelPositions.set(edge.id, horizontal ? {
|
|
9082
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
9083
|
+
y: cross
|
|
9084
|
+
} : {
|
|
9085
|
+
x: cross,
|
|
9086
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2
|
|
9087
|
+
});
|
|
9088
|
+
cross += (horizontal ? height : width) + edgeSpacing;
|
|
9089
|
+
}
|
|
9090
|
+
const anchorEdge = orderedPair[0];
|
|
9091
|
+
const anchorLabel = parallelLabelPositions.get(anchorEdge.id);
|
|
9092
|
+
const desiredCross = Math.round(horizontal ? anchorLabel.y + (anchorEdge.height ?? 0) / 2 : anchorLabel.x + (anchorEdge.width ?? 0) / 2);
|
|
9093
|
+
const alignEndpoint = (nodeId, portName) => {
|
|
9094
|
+
const node = graph.nodes.find((candidate) => candidate.id === nodeId);
|
|
9095
|
+
const rect = mutableRects.get(nodeId);
|
|
9096
|
+
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
9097
|
+
if (!node || !rect || !port) return;
|
|
9098
|
+
const placedPort = placePorts(node.ports, rect, direction, (candidate) => input.portSettings?.(candidate, node), {
|
|
9099
|
+
...options.settings,
|
|
9100
|
+
...options.nodeSettings?.(node)
|
|
9101
|
+
}, (candidate) => getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate))?.find((candidate) => candidate.name === portName);
|
|
9102
|
+
if (!placedPort) return;
|
|
9103
|
+
const localCross = horizontal ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2;
|
|
9104
|
+
const currentCross = horizontal ? rect.y : rect.x;
|
|
9105
|
+
const nextCross = desiredCross - localCross;
|
|
9106
|
+
const delta = nextCross - currentCross;
|
|
9107
|
+
mutableRects.set(nodeId, horizontal ? {
|
|
9108
|
+
...rect,
|
|
9109
|
+
y: nextCross
|
|
9110
|
+
} : {
|
|
9111
|
+
...rect,
|
|
9112
|
+
x: nextCross
|
|
9113
|
+
});
|
|
9114
|
+
for (const edge of graph.edges) {
|
|
9115
|
+
const points = [...routes.pointsByEdgeId.get(edge.id) ?? []];
|
|
9116
|
+
if (points.length === 0) continue;
|
|
9117
|
+
const index = edge.sourceId === nodeId ? 0 : edge.targetId === nodeId ? points.length - 1 : void 0;
|
|
9118
|
+
if (index === void 0) continue;
|
|
9119
|
+
const endpoint = points[index];
|
|
9120
|
+
const originalCross = horizontal ? endpoint.y : endpoint.x;
|
|
9121
|
+
points[index] = horizontal ? {
|
|
9122
|
+
...endpoint,
|
|
9123
|
+
y: endpoint.y + delta
|
|
9124
|
+
} : {
|
|
9125
|
+
...endpoint,
|
|
9126
|
+
x: endpoint.x + delta
|
|
9127
|
+
};
|
|
9128
|
+
const adjacentIndex = index === 0 ? 1 : points.length - 2;
|
|
9129
|
+
const adjacent = points[adjacentIndex];
|
|
9130
|
+
if (adjacent && (horizontal ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontal ? {
|
|
9131
|
+
...adjacent,
|
|
9132
|
+
y: adjacent.y + delta
|
|
9133
|
+
} : {
|
|
9134
|
+
...adjacent,
|
|
9135
|
+
x: adjacent.x + delta
|
|
9136
|
+
};
|
|
9137
|
+
routes.pointsByEdgeId.set(edge.id, points);
|
|
9138
|
+
}
|
|
9139
|
+
};
|
|
9140
|
+
if (anchorEdge.sourcePort && anchorEdge.targetPort) {
|
|
9141
|
+
alignEndpoint(anchorEdge.sourceId, anchorEdge.sourcePort);
|
|
9142
|
+
alignEndpoint(anchorEdge.targetId, anchorEdge.targetPort);
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
8835
9145
|
const routedPortAnchors = /* @__PURE__ */ new Map();
|
|
8836
9146
|
for (const edge of graph.edges) {
|
|
8837
9147
|
const points = routes.pointsByEdgeId.get(edge.id);
|
|
@@ -8846,7 +9156,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8846
9156
|
let ports = placePorts(node.ports, rect, direction, (port) => input.portSettings?.(port, node), {
|
|
8847
9157
|
...options.settings,
|
|
8848
9158
|
...options.nodeSettings?.(node)
|
|
8849
|
-
});
|
|
9159
|
+
}, (port) => getOrientedPortDirection(expanded.input, expanded.orientation, node, port));
|
|
8850
9160
|
if (options.nodeSettings?.(node)?.portConstraints === "FIXED_SIDE") ports = ports?.map((port) => {
|
|
8851
9161
|
if ((port.width ?? 8) !== 0 || (port.height ?? 8) !== 0) return port;
|
|
8852
9162
|
const anchor = routedPortAnchors.get(`${node.id}\0${port.name}`);
|
|
@@ -8921,8 +9231,9 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8921
9231
|
}] : [];
|
|
8922
9232
|
}).sort((left, right) => right.length - left.length)[0] : void 0;
|
|
8923
9233
|
const sourceNode = graph.nodes.find((node) => node.id === edge.sourceId);
|
|
9234
|
+
const targetNode = graph.nodes.find((node) => node.id === edge.targetId);
|
|
8924
9235
|
const sourcePort = sourceNode?.ports?.find((port) => port.name === edge.sourcePort);
|
|
8925
|
-
const targetPort =
|
|
9236
|
+
const targetPort = targetNode?.ports?.find((port) => port.name === edge.targetPort);
|
|
8926
9237
|
const sourcePortSide = sourceNode && sourcePort ? options.portSettings?.(sourcePort, sourceNode)?.["port.side"] : void 0;
|
|
8927
9238
|
const targetPortSide = sourceNode && targetPort ? options.portSettings?.(targetPort, sourceNode)?.["port.side"] : void 0;
|
|
8928
9239
|
const horizontalFeedbackTrack = edge.sourceId === edge.targetId && (sourcePortSide === "EAST" || sourcePortSide === "WEST") && targetPortSide === sourcePortSide || (horizontalFeedbackCandidate?.length ?? -1) >= (verticalFeedbackCandidate?.length ?? -1) ? horizontalFeedbackCandidate : void 0;
|
|
@@ -8931,8 +9242,43 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8931
9242
|
const edgeNodeSpacing = Number(options.settings?.["spacing.edgeNodeBetweenLayers"] ?? 10);
|
|
8932
9243
|
const routeX = labelPlacement === "TAIL" ? firstPoint.x + labelSpacing : labelPlacement === "HEAD" ? lastPoint.x - width - labelSpacing : inlineLabel && horizontalFeedbackTrack ? (horizontalFeedbackTrack.start.x + horizontalFeedbackTrack.end.x - width) / 2 : inlineLabel && verticalFeedbackTrack ? verticalFeedbackTrack.start.x > (minimumFeedbackNodeX + maximumFeedbackNodeX) / 2 ? verticalFeedbackTrack.start.x + labelSpacing + 1 : verticalFeedbackTrack.start.x - labelSpacing - width - 1 : inlineLabel && verticalTrack ? (direction === "right" ? trackNearTarget : !trackNearTarget) ? verticalTrack.x - edgeNodeSpacing - width : verticalTrack.x + edgeNodeSpacing : inlineLabel ? horizontal ? Math.floor(midpoint.x - width / 2) : Math.ceil(midpoint.x - width / 2) : midpoint.x - width / 2;
|
|
8933
9244
|
const routeY = labelPlacement === "CENTER" && inlineLabel && horizontalFeedbackTrack ? horizontalFeedbackTrack.start.y - height / 2 - .5 : labelPlacement === "CENTER" && inlineLabel && verticalFeedbackTrack ? (verticalFeedbackTrack.start.y + verticalFeedbackTrack.end.y - height) / 2 : labelPlacement === "CENTER" && inlineLabel ? midpoint.y - height / 2 - .5 : labelPlacement === "CENTER" && placeLabelUp ? midpoint.y - height - labelSpacing - Math.round(edgeThickness / 2) : (labelPlacement === "CENTER" ? midpoint.y : (firstPoint.y + lastPoint.y) / 2) + labelSpacing + Math.round(edgeThickness / 2);
|
|
8934
|
-
const
|
|
8935
|
-
const
|
|
9245
|
+
const sourceRect = placement.rectByNodeId.get(edge.sourceId);
|
|
9246
|
+
const targetRect = placement.rectByNodeId.get(edge.targetId);
|
|
9247
|
+
const hasFlexiblePorts = (node) => {
|
|
9248
|
+
const constraints = node ? options.nodeSettings?.(node)?.portConstraints : void 0;
|
|
9249
|
+
return constraints === void 0 || constraints === "UNDEFINED" || constraints === "FREE";
|
|
9250
|
+
};
|
|
9251
|
+
const flexibleFeedbackLabel = labelPlacement === "CENTER" && inlineLabel && expanded.orientation.reversedEdgeIds.has(edge.id) && hasFlexiblePorts(sourceNode) && hasFlexiblePorts(targetNode) && sourceRect !== void 0 && targetRect !== void 0;
|
|
9252
|
+
const [beforeFlowRect, afterFlowRect] = sourceRect && targetRect ? horizontal ? sourceRect.x <= targetRect.x ? [sourceRect, targetRect] : [targetRect, sourceRect] : sourceRect.y <= targetRect.y ? [sourceRect, targetRect] : [targetRect, sourceRect] : [void 0, void 0];
|
|
9253
|
+
const antiparallelLabelPosition = antiparallelLabelPositions.get(edge.id);
|
|
9254
|
+
const explicitLabelPosition = (() => {
|
|
9255
|
+
if (!outerAntiparallelLabelIds.has(edge.id)) return void 0;
|
|
9256
|
+
const edgeNodeSpacing$1 = Number(options.settings?.["spacing.edgeNode"] ?? 10);
|
|
9257
|
+
if (horizontal) {
|
|
9258
|
+
const x$1 = (beforeFlowRect.x + beforeFlowRect.width + afterFlowRect.x - width) / 2;
|
|
9259
|
+
const blockers$1 = [
|
|
9260
|
+
sourceRect,
|
|
9261
|
+
targetRect,
|
|
9262
|
+
...[...placement.rectByNodeId.values()].filter((rect) => rect.x < x$1 + width && rect.x + rect.width > x$1)
|
|
9263
|
+
];
|
|
9264
|
+
return {
|
|
9265
|
+
x: x$1,
|
|
9266
|
+
y: Math.max(...blockers$1.map((rect) => rect.y + rect.height)) + edgeNodeSpacing$1
|
|
9267
|
+
};
|
|
9268
|
+
}
|
|
9269
|
+
const y$1 = (beforeFlowRect.y + beforeFlowRect.height + afterFlowRect.y - height) / 2;
|
|
9270
|
+
const blockers = [
|
|
9271
|
+
sourceRect,
|
|
9272
|
+
targetRect,
|
|
9273
|
+
...[...placement.rectByNodeId.values()].filter((rect) => rect.y < y$1 + height && rect.y + rect.height > y$1)
|
|
9274
|
+
];
|
|
9275
|
+
return {
|
|
9276
|
+
x: Math.max(...blockers.map((rect) => rect.x + rect.width)) + edgeNodeSpacing$1,
|
|
9277
|
+
y: y$1
|
|
9278
|
+
};
|
|
9279
|
+
})() ?? antiparallelLabelPosition ?? parallelLabelPositions.get(edge.id);
|
|
9280
|
+
const x = explicitLabelPosition ? explicitLabelPosition.x : flexibleFeedbackLabel ? horizontal ? (beforeFlowRect.x + beforeFlowRect.width + afterFlowRect.x - width) / 2 : targetRect.x + (targetRect.width - width) / 2 : labelPlacement === "CENTER" && horizontal && labelDummyRect ? labelDummyRect.x : routeX;
|
|
9281
|
+
const y = explicitLabelPosition ? explicitLabelPosition.y : flexibleFeedbackLabel ? horizontal ? targetRect.y + (targetRect.height - height) / 2 : (beforeFlowRect.y + beforeFlowRect.height + afterFlowRect.y - height) / 2 : labelPlacement === "CENTER" && !horizontal && labelDummyRect ? labelDummyRect.y : routeY;
|
|
8936
9282
|
return {
|
|
8937
9283
|
...edge,
|
|
8938
9284
|
x,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-
|
|
1
|
+
import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-Bp8MkxWK.mjs";
|
|
2
2
|
import { getNodeSize } from "@statelyai/graph/layout";
|
|
3
3
|
|
|
4
4
|
//#region src/fixed.ts
|