@statelyai/layout 0.0.3 → 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 +11 -8
- package/dist/index.mjs +2 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-f_3mmGHr.mjs → layered-Bp8MkxWK.mjs} +43 -6
- package/dist/{spore-BfTduMDc.mjs → spore-ePWz2LtF.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +14 -20
- package/src/layered/index.ts +80 -16
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;
|
|
@@ -897,7 +900,7 @@ function toGraph(root, globalOptions = {}) {
|
|
|
897
900
|
if (isInsideSelfLoop(root, edge)) return [];
|
|
898
901
|
const source = endpoint(edge.sources?.[0] ?? edge.source, portOwnerById);
|
|
899
902
|
const target = endpoint(edge.targets?.[0] ?? edge.target, portOwnerById);
|
|
900
|
-
const labels = (edge.labels ?? []).filter(
|
|
903
|
+
const labels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
|
|
901
904
|
const labelLabelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
|
|
902
905
|
const labelWidth = Math.max(0, ...labels.map((label) => label.width ?? 0));
|
|
903
906
|
const labelHeight = labels.reduce((sum, label) => sum + (label.height ?? 0), 0) + Math.max(0, labels.length - 1) * labelLabelSpacing;
|
|
@@ -1000,7 +1003,7 @@ function applyLayout(root, graph, padding, layoutOptions) {
|
|
|
1000
1003
|
label.y ??= 0;
|
|
1001
1004
|
continue;
|
|
1002
1005
|
}
|
|
1003
|
-
if (!label
|
|
1006
|
+
if (!isLayoutEdgeLabel(label)) {
|
|
1004
1007
|
label.x ??= 0;
|
|
1005
1008
|
label.y ??= 0;
|
|
1006
1009
|
continue;
|
|
@@ -1026,7 +1029,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1026
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))));
|
|
1027
1030
|
}
|
|
1028
1031
|
for (const edge of root.edges ?? []) {
|
|
1029
|
-
const laidOutLabels = (edge.labels ?? []).filter(
|
|
1032
|
+
const laidOutLabels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
|
|
1030
1033
|
minimumX = Math.min(minimumX, ...laidOutLabels.map((label) => label.x ?? 0));
|
|
1031
1034
|
minimumY = Math.min(minimumY, ...laidOutLabels.map((label) => label.y ?? 0));
|
|
1032
1035
|
if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") !== true) {
|
|
@@ -1055,7 +1058,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1055
1058
|
point.x += shiftX;
|
|
1056
1059
|
point.y += shiftY;
|
|
1057
1060
|
}
|
|
1058
|
-
for (const label of (edge.labels ?? []).filter(
|
|
1061
|
+
for (const label of (edge.labels ?? []).filter(isLayoutEdgeLabel)) {
|
|
1059
1062
|
label.x = (label.x ?? 0) + shiftX;
|
|
1060
1063
|
label.y = (label.y ?? 0) + shiftY;
|
|
1061
1064
|
}
|
|
@@ -1091,7 +1094,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1091
1094
|
...(node.labels ?? []).map((label) => (node.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)),
|
|
1092
1095
|
...(node.ports ?? []).map((port) => (node.x ?? 0) + (port.x ?? 0) + (port.width ?? 0)),
|
|
1093
1096
|
...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.x ?? 0) + (port.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)))
|
|
1094
|
-
]), ...(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) => [
|
|
1095
1098
|
section.startPoint.x,
|
|
1096
1099
|
...(section.bendPoints ?? []).map((point) => point.x),
|
|
1097
1100
|
section.endPoint.x
|
|
@@ -1101,7 +1104,7 @@ function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
|
|
|
1101
1104
|
...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)),
|
|
1102
1105
|
...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0) + (port.height ?? 0)),
|
|
1103
1106
|
...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)))
|
|
1104
|
-
]), ...(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) => [
|
|
1105
1108
|
section.startPoint.y,
|
|
1106
1109
|
...(section.bendPoints ?? []).map((point) => point.y),
|
|
1107
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 };
|
|
@@ -8745,6 +8745,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8745
8745
|
let expandedRoutes = measure("edge-routing", () => edgeRouter(expanded.input, expanded.orientation, placement));
|
|
8746
8746
|
measure("post-compaction", () => applyPostCompaction(expanded.input, placement, expandedRoutes));
|
|
8747
8747
|
const antiparallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8748
|
+
const outerAntiparallelLabelIds = /* @__PURE__ */ new Set();
|
|
8748
8749
|
const parallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8749
8750
|
const postCompactionNodeCrossDeltas = /* @__PURE__ */ new Map();
|
|
8750
8751
|
const horizontalAntiparallelFlow = direction === "left" || direction === "right";
|
|
@@ -8834,12 +8835,13 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8834
8835
|
const secondRect = placement.rectByNodeId.get(pair[0].targetId);
|
|
8835
8836
|
if (!firstNode || !secondNode || !firstRect || !secondRect) continue;
|
|
8836
8837
|
const nonSelfNeighbors = (nodeId) => new Set(graph.edges.flatMap((edge) => edge.sourceId === edge.targetId ? [] : edge.sourceId === nodeId ? [edge.targetId] : edge.targetId === nodeId ? [edge.sourceId] : []));
|
|
8837
|
-
|
|
8838
|
+
const isolatedAntiparallelPair = nonSelfNeighbors(firstNode.id).size === 1 && nonSelfNeighbors(secondNode.id).size === 1;
|
|
8838
8839
|
const hasFlexiblePorts = (node) => {
|
|
8839
8840
|
const constraints = options.nodeSettings?.(node)?.portConstraints;
|
|
8840
8841
|
return constraints === void 0 || constraints === "UNDEFINED" || constraints === "FREE";
|
|
8841
8842
|
};
|
|
8842
|
-
|
|
8843
|
+
const hasFlexiblePairPorts = hasFlexiblePorts(firstNode) && hasFlexiblePorts(secondNode);
|
|
8844
|
+
if (isolatedAntiparallelPair && !hasFlexiblePairPorts) continue;
|
|
8843
8845
|
const firstBeforeSecond = horizontal ? firstRect.x + firstRect.width / 2 < secondRect.x + secondRect.width / 2 : firstRect.y + firstRect.height / 2 < secondRect.y + secondRect.height / 2;
|
|
8844
8846
|
const beforeRect = firstBeforeSecond ? firstRect : secondRect;
|
|
8845
8847
|
const afterRect = firstBeforeSecond ? secondRect : firstRect;
|
|
@@ -8850,13 +8852,20 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8850
8852
|
for (const edge of pair) {
|
|
8851
8853
|
const width = edge.width ?? 0;
|
|
8852
8854
|
const height = edge.height ?? 0;
|
|
8853
|
-
antiparallelLabelPositions.set(edge.id, horizontal ? {
|
|
8855
|
+
antiparallelLabelPositions.set(edge.id, isolatedAntiparallelPair ? horizontal ? {
|
|
8854
8856
|
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
8855
8857
|
y: cross
|
|
8856
8858
|
} : {
|
|
8857
8859
|
x: cross,
|
|
8858
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
|
|
8859
8867
|
});
|
|
8868
|
+
if (!isolatedAntiparallelPair && edge !== forward) outerAntiparallelLabelIds.add(edge.id);
|
|
8860
8869
|
cross += (horizontal ? height : width) + edgeSpacing;
|
|
8861
8870
|
}
|
|
8862
8871
|
const forwardLabel = antiparallelLabelPositions.get(forward.id);
|
|
@@ -8881,8 +8890,10 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8881
8890
|
x: nextCross
|
|
8882
8891
|
});
|
|
8883
8892
|
};
|
|
8884
|
-
|
|
8885
|
-
|
|
8893
|
+
if (isolatedAntiparallelPair) {
|
|
8894
|
+
alignPort(nodesById.get(forward.sourceId), forward.sourcePort);
|
|
8895
|
+
alignPort(nodesById.get(forward.targetId), forward.targetPort);
|
|
8896
|
+
}
|
|
8886
8897
|
}
|
|
8887
8898
|
}
|
|
8888
8899
|
if (postCompactionNodeCrossDeltas.size > 0) {
|
|
@@ -9239,7 +9250,33 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
9239
9250
|
};
|
|
9240
9251
|
const flexibleFeedbackLabel = labelPlacement === "CENTER" && inlineLabel && expanded.orientation.reversedEdgeIds.has(edge.id) && hasFlexiblePorts(sourceNode) && hasFlexiblePorts(targetNode) && sourceRect !== void 0 && targetRect !== void 0;
|
|
9241
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];
|
|
9242
|
-
const
|
|
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);
|
|
9243
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;
|
|
9244
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;
|
|
9245
9282
|
return {
|
|
@@ -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
|
package/package.json
CHANGED
package/src/elkjs/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ElkConstructorArguments,
|
|
15
15
|
ElkLayoutAlgorithmDescription,
|
|
16
16
|
ElkEdge,
|
|
17
|
+
ElkLabel,
|
|
17
18
|
ElkLayoutArguments,
|
|
18
19
|
ElkLayoutCategoryDescription,
|
|
19
20
|
ElkLayoutOptionDescription,
|
|
@@ -25,6 +26,13 @@ import type {
|
|
|
25
26
|
LaidOutElkNode,
|
|
26
27
|
} from "./types";
|
|
27
28
|
|
|
29
|
+
function isLayoutEdgeLabel(label: ElkLabel): boolean {
|
|
30
|
+
return (
|
|
31
|
+
getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true &&
|
|
32
|
+
(Boolean(label.text) || label.width !== undefined || label.height !== undefined)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
export type {
|
|
29
37
|
ElkConstructorArguments,
|
|
30
38
|
ElkCommonDescription,
|
|
@@ -1264,10 +1272,7 @@ function toGraph(root: ElkNode, globalOptions: Readonly<Record<string, unknown>>
|
|
|
1264
1272
|
if (isInsideSelfLoop(root, edge)) return [];
|
|
1265
1273
|
const source = endpoint(edge.sources?.[0] ?? edge.source, portOwnerById);
|
|
1266
1274
|
const target = endpoint(edge.targets?.[0] ?? edge.target, portOwnerById);
|
|
1267
|
-
const labels = (edge.labels ?? []).filter(
|
|
1268
|
-
(label) =>
|
|
1269
|
-
Boolean(label.text) && getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true,
|
|
1270
|
-
);
|
|
1275
|
+
const labels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
|
|
1271
1276
|
const labelLabelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
|
|
1272
1277
|
const labelWidth = Math.max(0, ...labels.map((label) => label.width ?? 0));
|
|
1273
1278
|
const labelHeight =
|
|
@@ -1387,7 +1392,7 @@ function applyLayout(
|
|
|
1387
1392
|
label.y ??= 0;
|
|
1388
1393
|
continue;
|
|
1389
1394
|
}
|
|
1390
|
-
if (!label
|
|
1395
|
+
if (!isLayoutEdgeLabel(label)) {
|
|
1391
1396
|
label.x ??= 0;
|
|
1392
1397
|
label.y ??= 0;
|
|
1393
1398
|
continue;
|
|
@@ -1443,10 +1448,7 @@ function normalizeElkGraphBounds(
|
|
|
1443
1448
|
);
|
|
1444
1449
|
}
|
|
1445
1450
|
for (const edge of root.edges ?? []) {
|
|
1446
|
-
const laidOutLabels = (edge.labels ?? []).filter(
|
|
1447
|
-
(label) =>
|
|
1448
|
-
Boolean(label.text) && getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true,
|
|
1449
|
-
);
|
|
1451
|
+
const laidOutLabels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
|
|
1450
1452
|
minimumX = Math.min(minimumX, ...laidOutLabels.map((label) => label.x ?? 0));
|
|
1451
1453
|
minimumY = Math.min(minimumY, ...laidOutLabels.map((label) => label.y ?? 0));
|
|
1452
1454
|
if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") !== true) {
|
|
@@ -1473,7 +1475,7 @@ function normalizeElkGraphBounds(
|
|
|
1473
1475
|
point.y += shiftY;
|
|
1474
1476
|
}
|
|
1475
1477
|
}
|
|
1476
|
-
for (const label of (edge.labels ?? []).filter(
|
|
1478
|
+
for (const label of (edge.labels ?? []).filter(isLayoutEdgeLabel)) {
|
|
1477
1479
|
label.x = (label.x ?? 0) + shiftX;
|
|
1478
1480
|
label.y = (label.y ?? 0) + shiftY;
|
|
1479
1481
|
}
|
|
@@ -1578,11 +1580,7 @@ function normalizeElkGraphBounds(
|
|
|
1578
1580
|
]),
|
|
1579
1581
|
...(root.edges ?? []).flatMap((edge) =>
|
|
1580
1582
|
(edge.labels ?? [])
|
|
1581
|
-
.filter(
|
|
1582
|
-
(label) =>
|
|
1583
|
-
Boolean(label.text) &&
|
|
1584
|
-
getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true,
|
|
1585
|
-
)
|
|
1583
|
+
.filter(isLayoutEdgeLabel)
|
|
1586
1584
|
.map((label) => (label.x ?? 0) + (label.width ?? 0)),
|
|
1587
1585
|
),
|
|
1588
1586
|
...(root.edges ?? []).flatMap((edge) =>
|
|
@@ -1617,11 +1615,7 @@ function normalizeElkGraphBounds(
|
|
|
1617
1615
|
]),
|
|
1618
1616
|
...(root.edges ?? []).flatMap((edge) =>
|
|
1619
1617
|
(edge.labels ?? [])
|
|
1620
|
-
.filter(
|
|
1621
|
-
(label) =>
|
|
1622
|
-
Boolean(label.text) &&
|
|
1623
|
-
getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true,
|
|
1624
|
-
)
|
|
1618
|
+
.filter(isLayoutEdgeLabel)
|
|
1625
1619
|
.map(
|
|
1626
1620
|
(label) =>
|
|
1627
1621
|
(label.y ?? 0) +
|
package/src/layered/index.ts
CHANGED
|
@@ -1760,6 +1760,7 @@ function runLayeredPipeline<N, E, G, P>(
|
|
|
1760
1760
|
);
|
|
1761
1761
|
measure("post-compaction", () => applyPostCompaction(expanded.input, placement, expandedRoutes));
|
|
1762
1762
|
const antiparallelLabelPositions = new Map<string, Point>();
|
|
1763
|
+
const outerAntiparallelLabelIds = new Set<string>();
|
|
1763
1764
|
const parallelLabelPositions = new Map<string, Point>();
|
|
1764
1765
|
const postCompactionNodeCrossDeltas = new Map<string, number>();
|
|
1765
1766
|
const horizontalAntiparallelFlow = direction === "left" || direction === "right";
|
|
@@ -1898,14 +1899,14 @@ function runLayeredPipeline<N, E, G, P>(
|
|
|
1898
1899
|
: [],
|
|
1899
1900
|
),
|
|
1900
1901
|
);
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
}
|
|
1902
|
+
const isolatedAntiparallelPair =
|
|
1903
|
+
nonSelfNeighbors(firstNode.id).size === 1 && nonSelfNeighbors(secondNode.id).size === 1;
|
|
1904
1904
|
const hasFlexiblePorts = (node: GraphNode): boolean => {
|
|
1905
1905
|
const constraints = options.nodeSettings?.(node)?.portConstraints;
|
|
1906
1906
|
return constraints === undefined || constraints === "UNDEFINED" || constraints === "FREE";
|
|
1907
1907
|
};
|
|
1908
|
-
|
|
1908
|
+
const hasFlexiblePairPorts = hasFlexiblePorts(firstNode) && hasFlexiblePorts(secondNode);
|
|
1909
|
+
if (isolatedAntiparallelPair && !hasFlexiblePairPorts) continue;
|
|
1909
1910
|
|
|
1910
1911
|
const firstBeforeSecond = horizontal
|
|
1911
1912
|
? firstRect.x + firstRect.width / 2 < secondRect.x + secondRect.width / 2
|
|
@@ -1929,16 +1930,44 @@ function runLayeredPipeline<N, E, G, P>(
|
|
|
1929
1930
|
const height = edge.height ?? 0;
|
|
1930
1931
|
antiparallelLabelPositions.set(
|
|
1931
1932
|
edge.id,
|
|
1932
|
-
|
|
1933
|
-
?
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1933
|
+
isolatedAntiparallelPair
|
|
1934
|
+
? horizontal
|
|
1935
|
+
? {
|
|
1936
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
1937
|
+
y: cross,
|
|
1938
|
+
}
|
|
1939
|
+
: {
|
|
1940
|
+
x: cross,
|
|
1941
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2,
|
|
1942
|
+
}
|
|
1943
|
+
: horizontal
|
|
1944
|
+
? {
|
|
1945
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
1946
|
+
y:
|
|
1947
|
+
edge === forward
|
|
1948
|
+
? (firstRect.y +
|
|
1949
|
+
firstRect.height / 2 +
|
|
1950
|
+
secondRect.y +
|
|
1951
|
+
secondRect.height / 2) /
|
|
1952
|
+
2 -
|
|
1953
|
+
height / 2
|
|
1954
|
+
: Math.max(firstRect.y + firstRect.height, secondRect.y + secondRect.height) +
|
|
1955
|
+
edgeSpacing,
|
|
1956
|
+
}
|
|
1957
|
+
: {
|
|
1958
|
+
x:
|
|
1959
|
+
edge === forward
|
|
1960
|
+
? (firstRect.x + firstRect.width / 2 + secondRect.x + secondRect.width / 2) /
|
|
1961
|
+
2 -
|
|
1962
|
+
width / 2
|
|
1963
|
+
: Math.max(firstRect.x + firstRect.width, secondRect.x + secondRect.width) +
|
|
1964
|
+
edgeSpacing,
|
|
1965
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2,
|
|
1966
|
+
},
|
|
1941
1967
|
);
|
|
1968
|
+
if (!isolatedAntiparallelPair && edge !== forward) {
|
|
1969
|
+
outerAntiparallelLabelIds.add(edge.id);
|
|
1970
|
+
}
|
|
1942
1971
|
cross += (horizontal ? height : width) + edgeSpacing;
|
|
1943
1972
|
}
|
|
1944
1973
|
|
|
@@ -1974,8 +2003,10 @@ function runLayeredPipeline<N, E, G, P>(
|
|
|
1974
2003
|
horizontal ? { ...rect, y: nextCross } : { ...rect, x: nextCross },
|
|
1975
2004
|
);
|
|
1976
2005
|
};
|
|
1977
|
-
|
|
1978
|
-
|
|
2006
|
+
if (isolatedAntiparallelPair) {
|
|
2007
|
+
alignPort(nodesById.get(forward.sourceId)!, forward.sourcePort);
|
|
2008
|
+
alignPort(nodesById.get(forward.targetId)!, forward.targetPort);
|
|
2009
|
+
}
|
|
1979
2010
|
}
|
|
1980
2011
|
}
|
|
1981
2012
|
if (postCompactionNodeCrossDeltas.size > 0) {
|
|
@@ -2482,8 +2513,41 @@ function runLayeredPipeline<N, E, G, P>(
|
|
|
2482
2513
|
? [sourceRect, targetRect]
|
|
2483
2514
|
: [targetRect, sourceRect]
|
|
2484
2515
|
: [undefined, undefined];
|
|
2516
|
+
const antiparallelLabelPosition = antiparallelLabelPositions.get(edge.id);
|
|
2517
|
+
const outerAntiparallelLabelPosition = (() => {
|
|
2518
|
+
if (!outerAntiparallelLabelIds.has(edge.id)) return undefined;
|
|
2519
|
+
const edgeNodeSpacing = Number(options.settings?.["spacing.edgeNode"] ?? 10);
|
|
2520
|
+
if (horizontal) {
|
|
2521
|
+
const x = (beforeFlowRect!.x + beforeFlowRect!.width + afterFlowRect!.x - width) / 2;
|
|
2522
|
+
const blockers = [
|
|
2523
|
+
sourceRect!,
|
|
2524
|
+
targetRect!,
|
|
2525
|
+
...[...placement.rectByNodeId.values()].filter(
|
|
2526
|
+
(rect) => rect.x < x + width && rect.x + rect.width > x,
|
|
2527
|
+
),
|
|
2528
|
+
];
|
|
2529
|
+
return {
|
|
2530
|
+
x,
|
|
2531
|
+
y: Math.max(...blockers.map((rect) => rect.y + rect.height)) + edgeNodeSpacing,
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
const y = (beforeFlowRect!.y + beforeFlowRect!.height + afterFlowRect!.y - height) / 2;
|
|
2535
|
+
const blockers = [
|
|
2536
|
+
sourceRect!,
|
|
2537
|
+
targetRect!,
|
|
2538
|
+
...[...placement.rectByNodeId.values()].filter(
|
|
2539
|
+
(rect) => rect.y < y + height && rect.y + rect.height > y,
|
|
2540
|
+
),
|
|
2541
|
+
];
|
|
2542
|
+
return {
|
|
2543
|
+
x: Math.max(...blockers.map((rect) => rect.x + rect.width)) + edgeNodeSpacing,
|
|
2544
|
+
y,
|
|
2545
|
+
};
|
|
2546
|
+
})();
|
|
2485
2547
|
const explicitLabelPosition =
|
|
2486
|
-
|
|
2548
|
+
outerAntiparallelLabelPosition ??
|
|
2549
|
+
antiparallelLabelPosition ??
|
|
2550
|
+
parallelLabelPositions.get(edge.id);
|
|
2487
2551
|
const x = explicitLabelPosition
|
|
2488
2552
|
? explicitLabelPosition.x
|
|
2489
2553
|
: flexibleFeedbackLabel
|