@statelyai/layout 0.0.1 → 0.0.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.
- package/README.md +1 -3
- package/dist/elkjs/index.mjs +15 -11
- package/dist/{index-D2RodsZY.d.mts → index-8xYkohbz.d.mts} +2 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/layered/index.d.mts +1 -1
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-Dd868WZY.mjs → layered-f_3mmGHr.mjs} +717 -62
- package/dist/{spore-fTgSoRLP.mjs → spore-BfTduMDc.mjs} +1 -1
- package/package.json +20 -6
- package/src/box.ts +135 -0
- package/src/elkjs/index.ts +1832 -0
- package/src/elkjs/types.ts +103 -0
- package/src/errors.ts +16 -0
- package/src/fixed.ts +80 -0
- package/src/index.ts +92 -0
- package/src/java-random.ts +46 -0
- package/src/layered/bk-node-placement.ts +715 -0
- package/src/layered/elk-enum-values.ts +171 -0
- package/src/layered/elk-options.generated.ts +315 -0
- package/src/layered/elk-options.ts +98 -0
- package/src/layered/flexible-ports.ts +11 -0
- package/src/layered/high-degree.ts +127 -0
- package/src/layered/index.ts +2555 -0
- package/src/layered/layer-unzipping.ts +217 -0
- package/src/layered/linear-segments-node-placement.ts +447 -0
- package/src/layered/long-edges.ts +406 -0
- package/src/layered/min-width.ts +159 -0
- package/src/layered/multi-edge-wrapping.ts +460 -0
- package/src/layered/network-simplex-node-placement.ts +500 -0
- package/src/layered/network-simplex.ts +346 -0
- package/src/layered/node-promotion.ts +197 -0
- package/src/layered/spacing.ts +37 -0
- package/src/layered/spline-bezier.ts +102 -0
- package/src/layered/strategies.ts +5403 -0
- package/src/layered/stretch-width.ts +136 -0
- package/src/layered/types.ts +111 -0
- package/src/layout.ts +202 -0
- package/src/packing.ts +74 -0
- package/src/random.ts +142 -0
- package/src/spore.ts +103 -0
- package/src/types.ts +84 -0
|
@@ -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);
|
|
@@ -1721,6 +1735,14 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1721
1735
|
input.settings["compaction.postCompaction.constraints"];
|
|
1722
1736
|
if (strategy === "NONE") return placement;
|
|
1723
1737
|
const rects = placement.rectByNodeId;
|
|
1738
|
+
const originalEndpointsByEdgeId = new Map([...routes?.pointsByEdgeId ?? []].flatMap(([edgeId, points]) => {
|
|
1739
|
+
const start = points[0];
|
|
1740
|
+
const end = points.at(-1);
|
|
1741
|
+
return start && end ? [[edgeId, {
|
|
1742
|
+
start: { ...start },
|
|
1743
|
+
end: { ...end }
|
|
1744
|
+
}]] : [];
|
|
1745
|
+
}));
|
|
1724
1746
|
const compactables = [...rects].map(([id, rect]) => {
|
|
1725
1747
|
const longEdgeDummy = id.startsWith("__layout_dummy:") && rect.width === 0 && rect.height === 0;
|
|
1726
1748
|
const incidentEdge = longEdgeDummy ? input.graph.edges.find((edge) => edge.sourceId === id || edge.targetId === id) : void 0;
|
|
@@ -1736,6 +1758,7 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1736
1758
|
};
|
|
1737
1759
|
});
|
|
1738
1760
|
if (routes) for (const [edgeId, readonlyPoints] of routes.pointsByEdgeId) {
|
|
1761
|
+
if (routes.outsideFeedbackEdgeIds?.has(edgeId)) continue;
|
|
1739
1762
|
const points = readonlyPoints;
|
|
1740
1763
|
for (let index = 0; index + 1 < points.length; index++) {
|
|
1741
1764
|
const first = points[index];
|
|
@@ -1756,7 +1779,10 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1756
1779
|
}
|
|
1757
1780
|
const verticalSpacing = (left, right) => left.kind === "node" && right.kind === "node" ? input.spacing.node : left.kind === "segment" && right.kind === "segment" && left.edgeId === right.edgeId ? 1 : Number(input.settings[left.kind === "segment" && right.kind === "segment" ? "spacing.edgeEdge" : "spacing.edgeNode"] ?? 10);
|
|
1758
1781
|
const horizontalSpacing = (left, right) => left.kind === "node" && right.kind === "node" ? input.spacing.node : left.kind === "segment" && right.kind === "segment" && left.edgeId === right.edgeId ? 0 : Number(input.settings[left.kind === "segment" && right.kind === "segment" ? "spacing.edgeEdge" : "spacing.edgeNode"] ?? 10);
|
|
1782
|
+
const centerLabeledEdgeIds = new Set(input.graph.edges.filter((edge) => (edge.width ?? 0) > 0 && (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER").map((edge) => edge.id));
|
|
1783
|
+
const labelFlowLocked = new Set(compactables.filter((item) => item.kind === "segment" && item.edgeId !== void 0 && centerLabeledEdgeIds.has(item.edgeId)).map((item) => item.id));
|
|
1759
1784
|
const compact$1 = (direction, locked = /* @__PURE__ */ new Set()) => {
|
|
1785
|
+
locked = new Set([...labelFlowLocked, ...locked]);
|
|
1760
1786
|
const nodes = compactables.map((item) => ({
|
|
1761
1787
|
...item,
|
|
1762
1788
|
x: direction === "RIGHT" ? -item.x - item.width : item.x,
|
|
@@ -1823,6 +1849,7 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1823
1849
|
for (const node of input.graph.nodes) {
|
|
1824
1850
|
const targets = outgoing.get(node.id) ?? [];
|
|
1825
1851
|
if (targets.length <= (incomingDegree.get(node.id) ?? 0) || targets.length === 0) continue;
|
|
1852
|
+
if (input.graph.edges.some((edge) => edge.sourceId === node.id && centerLabeledEdgeIds.has(edge.id))) continue;
|
|
1826
1853
|
const item = itemByNodeId.get(node.id);
|
|
1827
1854
|
if (!item) continue;
|
|
1828
1855
|
const upper = Math.min(...targets.map((targetId) => (itemByNodeId.get(targetId)?.x ?? item.x) - item.width - input.spacing.node));
|
|
@@ -1830,6 +1857,49 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1830
1857
|
}
|
|
1831
1858
|
}
|
|
1832
1859
|
}
|
|
1860
|
+
if (strategy === "EDGE_LENGTH" && input.settings["nodePlacement.favorStraightEdges"] === true && (input.direction === "down" || input.direction === "up")) {
|
|
1861
|
+
const itemByNodeId = new Map(compactables.flatMap((item) => item.kind === "node" && item.nodeId ? [[item.nodeId, item]] : []));
|
|
1862
|
+
const incomingDegree = new Map(input.graph.nodes.map((node) => [node.id, 0]));
|
|
1863
|
+
for (const edge of input.graph.edges) if (edge.sourceId !== edge.targetId) incomingDegree.set(edge.targetId, (incomingDegree.get(edge.targetId) ?? 0) + 1);
|
|
1864
|
+
for (const edge of input.graph.edges) {
|
|
1865
|
+
if (edge.sourceId === edge.targetId || (incomingDegree.get(edge.targetId) ?? 0) !== 1) continue;
|
|
1866
|
+
const endpoints$2 = originalEndpointsByEdgeId.get(edge.id);
|
|
1867
|
+
const source = itemByNodeId.get(edge.sourceId);
|
|
1868
|
+
const target = itemByNodeId.get(edge.targetId);
|
|
1869
|
+
if (!endpoints$2 || !source || !target) continue;
|
|
1870
|
+
if (!(input.direction === "down" ? endpoints$2.start.y < endpoints$2.end.y : endpoints$2.start.y > endpoints$2.end.y)) continue;
|
|
1871
|
+
const desiredTargetX = source.x + (endpoints$2.start.x - source.originalX) - (endpoints$2.end.x - target.originalX);
|
|
1872
|
+
if (compactables.some((candidate) => {
|
|
1873
|
+
if (candidate.kind !== "node" || candidate === target) return false;
|
|
1874
|
+
if (!(candidate.y < target.y + target.height && candidate.y + candidate.height > target.y)) return false;
|
|
1875
|
+
const spacing = input.spacing.node;
|
|
1876
|
+
return desiredTargetX < candidate.x + candidate.width + spacing && desiredTargetX + target.width + spacing > candidate.x;
|
|
1877
|
+
})) continue;
|
|
1878
|
+
target.x = desiredTargetX;
|
|
1879
|
+
const alignedX = source.x + (endpoints$2.start.x - source.originalX);
|
|
1880
|
+
for (const segment of compactables) if (segment.kind === "segment" && segment.edgeId === edge.id) segment.x = alignedX;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (input.direction === "right" && centerLabeledEdgeIds.size > 0) {
|
|
1884
|
+
const itemByNodeId = new Map(compactables.flatMap((item) => item.kind === "node" && item.nodeId ? [[item.nodeId, item]] : []));
|
|
1885
|
+
const edgeNodeSpacing = Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10);
|
|
1886
|
+
for (const edge of input.graph.edges) {
|
|
1887
|
+
if (!centerLabeledEdgeIds.has(edge.id)) continue;
|
|
1888
|
+
const source = itemByNodeId.get(edge.sourceId);
|
|
1889
|
+
const target = itemByNodeId.get(edge.targetId);
|
|
1890
|
+
const originalEndpoints = originalEndpointsByEdgeId.get(edge.id);
|
|
1891
|
+
if (!source || !target || !originalEndpoints || originalEndpoints.start.x >= originalEndpoints.end.x) continue;
|
|
1892
|
+
const tracks = compactables.filter((item) => item.kind === "segment" && item.edgeId === edge.id);
|
|
1893
|
+
const track = tracks.length > 0 ? Math.max(...tracks.map((item) => item.x)) : void 0;
|
|
1894
|
+
const targetNode = input.graph.nodes.find((node) => node.id === edge.targetId);
|
|
1895
|
+
const targetPort = targetNode?.ports?.find((port) => port.name === edge.targetPort);
|
|
1896
|
+
if (track !== void 0 && targetNode !== void 0 && targetPort !== void 0 && (targetPort.width ?? 8) === 0 && (targetPort.height ?? 8) === 0 && input.nodeSettings?.(targetNode)?.portConstraints === "FIXED_SIDE" && input.graph.edges.filter((candidate) => candidate.targetId === edge.targetId).length > input.graph.edges.filter((candidate) => candidate.sourceId === edge.sourceId).length) source.x = Math.max(source.x, track - edgeNodeSpacing - (edge.width ?? 0) - input.spacing.layer - source.width);
|
|
1897
|
+
else {
|
|
1898
|
+
const routeStart = track !== void 0 ? track + edgeNodeSpacing : source.x + source.width + input.spacing.layer;
|
|
1899
|
+
target.x = Math.max(target.x, routeStart + (edge.width ?? 0) + input.spacing.layer);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1833
1903
|
const offset = input.padding.left - Math.min(...compactables.map((item) => item.x));
|
|
1834
1904
|
for (const item of compactables) item.x += offset;
|
|
1835
1905
|
const nodeDeltaById = /* @__PURE__ */ new Map();
|
|
@@ -1846,14 +1916,71 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1846
1916
|
item.points[1].x += delta;
|
|
1847
1917
|
}
|
|
1848
1918
|
if (routes) {
|
|
1919
|
+
const edgeRouting = input.settings.edgeRouting ?? "ORTHOGONAL";
|
|
1920
|
+
const compactedSplineControls = routes.splineNubControlsByEdgeId ? new Map(routes.splineNubControlsByEdgeId) : void 0;
|
|
1849
1921
|
const edgeById = new Map(input.graph.edges.map((edge) => [edge.id, edge]));
|
|
1850
1922
|
for (const [edgeId, readonlyPoints] of routes.pointsByEdgeId) {
|
|
1851
|
-
const points = readonlyPoints;
|
|
1923
|
+
const points = [...readonlyPoints];
|
|
1852
1924
|
const edge = edgeById.get(edgeId);
|
|
1853
1925
|
if (!edge || points.length === 0) continue;
|
|
1854
|
-
|
|
1855
|
-
|
|
1926
|
+
const originalEndpoints = originalEndpointsByEdgeId.get(edgeId);
|
|
1927
|
+
if (originalEndpoints) {
|
|
1928
|
+
points[0] = {
|
|
1929
|
+
...originalEndpoints.start,
|
|
1930
|
+
x: originalEndpoints.start.x + (nodeDeltaById.get(edge.sourceId) ?? 0)
|
|
1931
|
+
};
|
|
1932
|
+
points[points.length - 1] = {
|
|
1933
|
+
...originalEndpoints.end,
|
|
1934
|
+
x: originalEndpoints.end.x + (nodeDeltaById.get(edge.targetId) ?? 0)
|
|
1935
|
+
};
|
|
1936
|
+
if (routes.outsideFeedbackEdgeIds?.has(edgeId)) {
|
|
1937
|
+
const horizontal = input.direction === "left" || input.direction === "right";
|
|
1938
|
+
const startDelta = points[0].x - originalEndpoints.start.x;
|
|
1939
|
+
const endDelta = points.at(-1).x - originalEndpoints.end.x;
|
|
1940
|
+
if (horizontal && points.length >= 6) {
|
|
1941
|
+
for (const index of [1, 2]) points[index] = {
|
|
1942
|
+
...points[index],
|
|
1943
|
+
x: points[index].x + startDelta
|
|
1944
|
+
};
|
|
1945
|
+
for (const index of [points.length - 3, points.length - 2]) points[index] = {
|
|
1946
|
+
...points[index],
|
|
1947
|
+
x: points[index].x + endDelta
|
|
1948
|
+
};
|
|
1949
|
+
} else if (!horizontal && points.length >= 4) {
|
|
1950
|
+
points[1] = {
|
|
1951
|
+
...points[1],
|
|
1952
|
+
x: points[1].x + startDelta
|
|
1953
|
+
};
|
|
1954
|
+
points[points.length - 2] = {
|
|
1955
|
+
...points[points.length - 2],
|
|
1956
|
+
x: points[points.length - 2].x + endDelta
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
let compactedPoints = points;
|
|
1962
|
+
if (edgeRouting === "ORTHOGONAL") {
|
|
1963
|
+
const horizontal = input.direction === "left" || input.direction === "right";
|
|
1964
|
+
const orthogonal = [];
|
|
1965
|
+
for (const [index, point] of points.entries()) {
|
|
1966
|
+
const previous = orthogonal.at(-1);
|
|
1967
|
+
if (previous && Math.abs(previous.x - point.x) > 1e-9 && Math.abs(previous.y - point.y) > 1e-9) {
|
|
1968
|
+
const approachingTarget = index === points.length - 1;
|
|
1969
|
+
orthogonal.push(horizontal === approachingTarget ? {
|
|
1970
|
+
x: previous.x,
|
|
1971
|
+
y: point.y
|
|
1972
|
+
} : {
|
|
1973
|
+
x: point.x,
|
|
1974
|
+
y: previous.y
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
orthogonal.push(point);
|
|
1978
|
+
}
|
|
1979
|
+
compactedPoints = simplifyRoute$1(orthogonal);
|
|
1980
|
+
} else if (edgeRouting === "SPLINES") compactedSplineControls?.delete(edgeId);
|
|
1981
|
+
routes.pointsByEdgeId.set(edgeId, compactedPoints);
|
|
1856
1982
|
}
|
|
1983
|
+
if (compactedSplineControls) routes.splineNubControlsByEdgeId = compactedSplineControls;
|
|
1857
1984
|
}
|
|
1858
1985
|
return placement;
|
|
1859
1986
|
}
|
|
@@ -2046,8 +2173,16 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2046
2173
|
const forward = input.settings["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT" || sourceFlow <= targetFlow;
|
|
2047
2174
|
const feedback = input.settings.feedbackEdges === true && orientation?.reversedEdgeIds.has(edge.id) === true;
|
|
2048
2175
|
const directionReversed = input.direction === "left" || input.direction === "up";
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2176
|
+
const endpointSide = (nodeId, portName) => {
|
|
2177
|
+
const node = nodeById.get(nodeId);
|
|
2178
|
+
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
2179
|
+
const constraints = node ? input.nodeSettings?.(node)?.portConstraints : void 0;
|
|
2180
|
+
if (!node || !port || constraints !== "FIXED_SIDE" && constraints !== "FIXED_ORDER" && constraints !== "FIXED_RATIO" && constraints !== "FIXED_POS") return;
|
|
2181
|
+
const configured = input.portSettings?.(port, node)?.["port.side"];
|
|
2182
|
+
return configured === "WEST" || configured === "NORTH" ? "before" : configured === "EAST" || configured === "SOUTH" ? "after" : void 0;
|
|
2183
|
+
};
|
|
2184
|
+
const sourceSide = endpointSide(edge.sourceId, edge.sourcePort) ?? (feedback ? directionReversed ? "before" : "after" : forward ? "after" : "before");
|
|
2185
|
+
const targetSide = endpointSide(edge.targetId, edge.targetPort) ?? (feedback ? directionReversed ? "after" : "before" : forward ? "before" : "after");
|
|
2051
2186
|
const sourceKey = `${edge.sourceId}:${sourceSide}`;
|
|
2052
2187
|
const targetKey = `${edge.targetId}:${targetSide}`;
|
|
2053
2188
|
const sourceGroup = groups.get(sourceKey) ?? [];
|
|
@@ -2169,8 +2304,14 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2169
2304
|
function routeEdges(style) {
|
|
2170
2305
|
return (input, orientation, placement) => {
|
|
2171
2306
|
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
2307
|
+
const hasZeroFixedSideTarget = (edge) => {
|
|
2308
|
+
const target = nodeById.get(edge.targetId);
|
|
2309
|
+
const port = target?.ports?.find((candidate) => candidate.name === edge.targetPort);
|
|
2310
|
+
return target !== void 0 && port !== void 0 && (port.width ?? 8) === 0 && (port.height ?? 8) === 0 && input.nodeSettings?.(target)?.portConstraints === "FIXED_SIDE";
|
|
2311
|
+
};
|
|
2172
2312
|
const pointsByEdgeId = /* @__PURE__ */ new Map();
|
|
2173
2313
|
const splineNubControlsByEdgeId = /* @__PURE__ */ new Map();
|
|
2314
|
+
const outsideFeedbackEdgeIds = /* @__PURE__ */ new Set();
|
|
2174
2315
|
const horizontal = input.direction === "left" || input.direction === "right";
|
|
2175
2316
|
const reverse = input.direction === "up" || input.direction === "left";
|
|
2176
2317
|
const mutableRects = placement.rectByNodeId;
|
|
@@ -2181,6 +2322,35 @@ function routeEdges(style) {
|
|
|
2181
2322
|
loops.push(edge);
|
|
2182
2323
|
selfLoopsByNodeId.set(edge.sourceId, loops);
|
|
2183
2324
|
}
|
|
2325
|
+
const selfLoopEntriesByX = [...selfLoopsByNodeId].sort(([leftId], [rightId]) => (mutableRects.get(leftId)?.x ?? 0) - (mutableRects.get(rightId)?.x ?? 0));
|
|
2326
|
+
for (const [id, loops] of selfLoopEntriesByX) {
|
|
2327
|
+
const rect = mutableRects.get(id);
|
|
2328
|
+
const node = nodeById.get(id);
|
|
2329
|
+
if (!rect || !node) continue;
|
|
2330
|
+
const eastLoops = loops.filter((edge) => {
|
|
2331
|
+
const sourcePort = node.ports?.find((port) => port.name === edge.sourcePort);
|
|
2332
|
+
const targetPort = node.ports?.find((port) => port.name === edge.targetPort);
|
|
2333
|
+
return sourcePort !== void 0 && targetPort !== void 0 && input.portSettings?.(sourcePort, node)?.["port.side"] === "EAST" && input.portSettings?.(targetPort, node)?.["port.side"] === "EAST";
|
|
2334
|
+
});
|
|
2335
|
+
if (eastLoops.length === 0) continue;
|
|
2336
|
+
const selfLoopSpacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
2337
|
+
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
2338
|
+
const exteriorWidth = selfLoopSpacing + eastLoops.reduce((sum, edge) => sum + (edge.width ?? 0) + labelSpacing, 1);
|
|
2339
|
+
const nodeRight = rect.x + rect.width;
|
|
2340
|
+
const rightCandidates = [...mutableRects.entries()].filter(([candidateId, candidateRect]) => candidateId !== id && candidateRect.x >= nodeRight - 1e-9);
|
|
2341
|
+
if (rightCandidates.length === 0) continue;
|
|
2342
|
+
const nextX = Math.min(...rightCandidates.map(([, candidateRect]) => candidateRect.x));
|
|
2343
|
+
const shift = nodeRight + exteriorWidth + input.spacing.node - nextX;
|
|
2344
|
+
if (shift <= 0) continue;
|
|
2345
|
+
for (const [candidateId, candidateRect] of mutableRects) {
|
|
2346
|
+
if (candidateId === id || candidateRect.x < nextX - 1e-9) continue;
|
|
2347
|
+
mutableRects.set(candidateId, {
|
|
2348
|
+
...candidateRect,
|
|
2349
|
+
x: candidateRect.x + shift
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
const northReserveByLayer = /* @__PURE__ */ new Map();
|
|
2184
2354
|
for (const [id, loops] of selfLoopsByNodeId) {
|
|
2185
2355
|
const rect = mutableRects.get(id);
|
|
2186
2356
|
if (!rect) continue;
|
|
@@ -2198,12 +2368,17 @@ function routeEdges(style) {
|
|
|
2198
2368
|
else {
|
|
2199
2369
|
const sideLoopCount = distribution === "NORTH_SOUTH" ? Math.ceil(loops.length / 2) : loops.length;
|
|
2200
2370
|
const reserve = (ordering === "SEQUENCED" ? Math.min(1, sideLoopCount) : sideLoopCount) * spacing + splineOffset;
|
|
2201
|
-
|
|
2202
|
-
...rect,
|
|
2203
|
-
y: rect.y + reserve
|
|
2204
|
-
});
|
|
2371
|
+
northReserveByLayer.set(rect.y, Math.max(northReserveByLayer.get(rect.y) ?? 0, reserve));
|
|
2205
2372
|
}
|
|
2206
2373
|
}
|
|
2374
|
+
const northReserves = [...northReserveByLayer].sort(([left], [right]) => left - right);
|
|
2375
|
+
for (const [candidateId, candidateRect] of mutableRects) {
|
|
2376
|
+
const reserve = northReserves.reduce((total, [layerY, layerReserve]) => candidateRect.y + 1e-9 >= layerY ? total + layerReserve : total, 0);
|
|
2377
|
+
if (reserve > 0) mutableRects.set(candidateId, {
|
|
2378
|
+
...candidateRect,
|
|
2379
|
+
y: candidateRect.y + reserve
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2207
2382
|
const edgeLabelSideSelection = input.settings["edgeLabels.sideSelection"] ?? "SMART_DOWN";
|
|
2208
2383
|
if (edgeLabelSideSelection === "ALWAYS_UP" || edgeLabelSideSelection === "SMART_UP" || edgeLabelSideSelection === "DIRECTION_UP") {
|
|
2209
2384
|
let crossShift = 0;
|
|
@@ -2243,12 +2418,13 @@ function routeEdges(style) {
|
|
|
2243
2418
|
}
|
|
2244
2419
|
const labelExtraByGap = flowLayers.slice(0, -1).map(() => 0);
|
|
2245
2420
|
for (const edge of input.graph.edges) {
|
|
2246
|
-
|
|
2421
|
+
const labelFlowSize = horizontal ? edge.width ?? 0 : edge.height ?? 0;
|
|
2422
|
+
if (labelFlowSize <= 0) continue;
|
|
2247
2423
|
const sourceLayer = flowLayerByNodeId.get(edge.sourceId);
|
|
2248
2424
|
const targetLayer = flowLayerByNodeId.get(edge.targetId);
|
|
2249
2425
|
if (sourceLayer === void 0 || targetLayer === void 0) continue;
|
|
2250
2426
|
if (Math.abs(sourceLayer - targetLayer) !== 1) continue;
|
|
2251
|
-
const extra = (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER" ?
|
|
2427
|
+
const extra = (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER" ? labelFlowSize + input.spacing.layer : labelFlowSize + Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
2252
2428
|
const gap = Math.min(sourceLayer, targetLayer);
|
|
2253
2429
|
labelExtraByGap[gap] = Math.max(labelExtraByGap[gap] ?? 0, extra);
|
|
2254
2430
|
}
|
|
@@ -2493,13 +2669,15 @@ function routeEdges(style) {
|
|
|
2493
2669
|
bounds.start = nextStart;
|
|
2494
2670
|
bounds.end = nextStart + size;
|
|
2495
2671
|
const slots = slotsByGap[layerNo] ?? 0;
|
|
2496
|
-
const
|
|
2672
|
+
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);
|
|
2673
|
+
const preservedGap = (existingGapByLayer[layerNo] ?? input.spacing.layer) - (routesNearTarget ? edgeEdgeSpacing : 0);
|
|
2674
|
+
const gapSpacing = slots === 0 ? existingGapByLayer[layerNo] ?? input.spacing.layer : Math.max(preservesNodeFlexibilityGap || (labelExtraByGap[layerNo] ?? 0) > 0 ? preservedGap : input.spacing.layer, 2 * edgeNodeSpacing + Math.max(0, slots - 1) * edgeEdgeSpacing);
|
|
2497
2675
|
nextStart = bounds.end + gapSpacing;
|
|
2498
2676
|
}
|
|
2499
2677
|
implicitEndpoints = implicitEdgeEndpoints(input, placement, orientation);
|
|
2500
2678
|
for (const [gap, candidates] of candidatesByGap.entries()) for (const candidate of candidates) {
|
|
2501
2679
|
if (candidate.straight) continue;
|
|
2502
|
-
const firstTrack = (flowLayers[gap]?.end ?? 0) + edgeNodeSpacing + (candidate.slot ?? 0) * edgeEdgeSpacing;
|
|
2680
|
+
const firstTrack = input.direction === "right" && hasZeroFixedSideTarget(candidate.edge) && candidates.filter(({ edge }) => edge.targetId === candidate.edge.targetId).length > candidates.filter(({ edge }) => edge.sourceId === candidate.edge.sourceId).length ? (flowLayers[gap + 1]?.start ?? 0) - edgeNodeSpacing - (candidate.slot ?? 0) * edgeEdgeSpacing : (flowLayers[gap]?.end ?? 0) + edgeNodeSpacing + (candidate.slot ?? 0) * edgeEdgeSpacing;
|
|
2503
2681
|
if (candidate.secondSlot !== void 0 && candidate.crossover !== void 0) orthogonalDetourByEdgeId.set(candidate.edge.id, {
|
|
2504
2682
|
firstTrack,
|
|
2505
2683
|
secondTrack: (flowLayers[gap]?.end ?? 0) + edgeNodeSpacing + candidate.secondSlot * edgeEdgeSpacing,
|
|
@@ -2663,7 +2841,7 @@ function routeEdges(style) {
|
|
|
2663
2841
|
}] : [];
|
|
2664
2842
|
});
|
|
2665
2843
|
const incoming = segments.map(() => 0);
|
|
2666
|
-
for (const dependency of acyclicDependencies) incoming[dependency.target]
|
|
2844
|
+
for (const dependency of acyclicDependencies) incoming[dependency.target] = (incoming[dependency.target] ?? 0) + 1;
|
|
2667
2845
|
const queue = incoming.flatMap((count, index) => count === 0 ? [index] : []);
|
|
2668
2846
|
const rank = segments.map(() => 0);
|
|
2669
2847
|
for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
|
|
@@ -2745,67 +2923,92 @@ function routeEdges(style) {
|
|
|
2745
2923
|
const sourceRect = placement.rectByNodeId.get(edge.sourceId);
|
|
2746
2924
|
const targetRect = placement.rectByNodeId.get(edge.targetId);
|
|
2747
2925
|
if (!source || !target || !sourceRect || !targetRect) continue;
|
|
2748
|
-
|
|
2749
|
-
|
|
2926
|
+
const reversedEdge = orientation.reversedEdgeIds.has(edge.id);
|
|
2927
|
+
const feedbackSourcePort = source.ports?.find((port) => port.name === edge.sourcePort);
|
|
2928
|
+
const feedbackTargetPort = target.ports?.find((port) => port.name === edge.targetPort);
|
|
2929
|
+
const feedbackSourcePortSide = feedbackSourcePort ? input.portSettings?.(feedbackSourcePort, source)?.["port.side"] : void 0;
|
|
2930
|
+
const feedbackTargetPortSide = feedbackTargetPort ? input.portSettings?.(feedbackTargetPort, target)?.["port.side"] : void 0;
|
|
2931
|
+
const sourceAwaySide = horizontal ? sourceRect.x < targetRect.x ? "WEST" : "EAST" : sourceRect.y < targetRect.y ? "NORTH" : "SOUTH";
|
|
2932
|
+
const targetAwaySide = horizontal ? sourceRect.x < targetRect.x ? "EAST" : "WEST" : sourceRect.y < targetRect.y ? "SOUTH" : "NORTH";
|
|
2933
|
+
const sameSideSelfLoop = source.id === target.id && feedbackSourcePortSide !== void 0 && feedbackSourcePortSide === feedbackTargetPortSide;
|
|
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);
|
|
2939
|
+
if (input.settings.feedbackEdges === true && reversedEdge || fixedSideFeedback) {
|
|
2940
|
+
if (fixedSideFeedback) outsideFeedbackEdgeIds.add(edge.id);
|
|
2941
|
+
const crossSpacing = Number(input.settings["spacing.edgeNode"] ?? 10);
|
|
2942
|
+
const flowSpacing = fixedSideFeedback ? Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10) + (source.id === target.id ? Number(input.settings["spacing.nodeSelfLoop"] ?? 10) : 0) : crossSpacing;
|
|
2943
|
+
const sourceFallback$1 = implicitEndpoints.get(edge.id)?.source ?? {
|
|
2944
|
+
x: sourceRect.x + sourceRect.width / 2,
|
|
2945
|
+
y: sourceRect.y + sourceRect.height / 2
|
|
2946
|
+
};
|
|
2947
|
+
const targetFallback$1 = implicitEndpoints.get(edge.id)?.target ?? {
|
|
2948
|
+
x: targetRect.x + targetRect.width / 2,
|
|
2949
|
+
y: targetRect.y + targetRect.height / 2
|
|
2950
|
+
};
|
|
2951
|
+
const start$1 = fixedSideFeedback ? getPortPoint(source, edge.sourcePort, sourceRect, sourceFallback$1, input.direction, input) : sourceFallback$1;
|
|
2952
|
+
const end$1 = fixedSideFeedback ? getPortPoint(target, edge.targetPort, targetRect, targetFallback$1, input.direction, input) : targetFallback$1;
|
|
2750
2953
|
if (horizontal) {
|
|
2751
|
-
const sign = reverse ? -1 : 1;
|
|
2752
|
-
const
|
|
2753
|
-
|
|
2754
|
-
y: sourceRect.y + sourceRect.height / 2
|
|
2755
|
-
};
|
|
2756
|
-
const end$1 = {
|
|
2757
|
-
x: sign > 0 ? targetRect.x : targetRect.x + targetRect.width,
|
|
2758
|
-
y: targetRect.y + targetRect.height / 2
|
|
2759
|
-
};
|
|
2760
|
-
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.y + rect.height)) + spacing;
|
|
2954
|
+
const sign = fixedSideFeedback ? Math.sign(sourceRect.x - targetRect.x) || 1 : reverse ? -1 : 1;
|
|
2955
|
+
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.y + rect.height)) + crossSpacing + (fixedSideFeedback ? .5 : 0);
|
|
2956
|
+
const preserveCenters = fixedSideFeedback && input.settings.unnecessaryBendpoints === true;
|
|
2761
2957
|
pointsByEdgeId.set(edge.id, [
|
|
2762
2958
|
start$1,
|
|
2763
2959
|
{
|
|
2764
|
-
x: start$1.x + sign *
|
|
2960
|
+
x: start$1.x + sign * flowSpacing,
|
|
2765
2961
|
y: start$1.y
|
|
2766
2962
|
},
|
|
2767
2963
|
{
|
|
2768
|
-
x: start$1.x + sign *
|
|
2964
|
+
x: start$1.x + sign * flowSpacing,
|
|
2769
2965
|
y: outerCross
|
|
2770
2966
|
},
|
|
2967
|
+
...preserveCenters ? [{
|
|
2968
|
+
x: sourceRect.x + sourceRect.width / 2,
|
|
2969
|
+
y: outerCross
|
|
2970
|
+
}, {
|
|
2971
|
+
x: targetRect.x + targetRect.width / 2,
|
|
2972
|
+
y: outerCross
|
|
2973
|
+
}] : [],
|
|
2771
2974
|
{
|
|
2772
|
-
x: end$1.x - sign *
|
|
2975
|
+
x: end$1.x - sign * flowSpacing,
|
|
2773
2976
|
y: outerCross
|
|
2774
2977
|
},
|
|
2775
2978
|
{
|
|
2776
|
-
x: end$1.x - sign *
|
|
2979
|
+
x: end$1.x - sign * flowSpacing,
|
|
2777
2980
|
y: end$1.y
|
|
2778
2981
|
},
|
|
2779
2982
|
end$1
|
|
2780
2983
|
]);
|
|
2781
2984
|
} else {
|
|
2782
|
-
const sign = reverse ? -1 : 1;
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
y: sign > 0 ? sourceRect.y + sourceRect.height : sourceRect.y
|
|
2786
|
-
};
|
|
2787
|
-
const end$1 = {
|
|
2788
|
-
x: targetRect.x + targetRect.width / 2,
|
|
2789
|
-
y: sign > 0 ? targetRect.y : targetRect.y + targetRect.height
|
|
2790
|
-
};
|
|
2791
|
-
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.x + rect.width)) + spacing;
|
|
2985
|
+
const sign = fixedSideFeedback ? Math.sign(sourceRect.y - targetRect.y) || 1 : reverse ? -1 : 1;
|
|
2986
|
+
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.x + rect.width)) + crossSpacing + (fixedSideFeedback ? .5 : 0);
|
|
2987
|
+
const preserveCenters = fixedSideFeedback && input.settings.unnecessaryBendpoints === true;
|
|
2792
2988
|
pointsByEdgeId.set(edge.id, [
|
|
2793
2989
|
start$1,
|
|
2794
2990
|
{
|
|
2795
2991
|
x: start$1.x,
|
|
2796
|
-
y: start$1.y + sign *
|
|
2992
|
+
y: start$1.y + sign * flowSpacing
|
|
2797
2993
|
},
|
|
2798
2994
|
{
|
|
2799
2995
|
x: outerCross,
|
|
2800
|
-
y: start$1.y + sign *
|
|
2996
|
+
y: start$1.y + sign * flowSpacing
|
|
2801
2997
|
},
|
|
2998
|
+
...preserveCenters ? [{
|
|
2999
|
+
x: outerCross,
|
|
3000
|
+
y: sourceRect.y + sourceRect.height / 2
|
|
3001
|
+
}, {
|
|
3002
|
+
x: outerCross,
|
|
3003
|
+
y: targetRect.y + targetRect.height / 2
|
|
3004
|
+
}] : [],
|
|
2802
3005
|
{
|
|
2803
3006
|
x: outerCross,
|
|
2804
|
-
y: end$1.y - sign *
|
|
3007
|
+
y: end$1.y - sign * flowSpacing
|
|
2805
3008
|
},
|
|
2806
3009
|
{
|
|
2807
3010
|
x: end$1.x,
|
|
2808
|
-
y: end$1.y - sign *
|
|
3011
|
+
y: end$1.y - sign * flowSpacing
|
|
2809
3012
|
},
|
|
2810
3013
|
end$1
|
|
2811
3014
|
]);
|
|
@@ -2870,6 +3073,75 @@ function routeEdges(style) {
|
|
|
2870
3073
|
const distribution = nodeSettings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
2871
3074
|
const ordering = nodeSettings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
2872
3075
|
if (style === "ORTHOGONAL") {
|
|
3076
|
+
if (sameSideSelfLoop) {
|
|
3077
|
+
const endpoints$2 = implicitEndpoints.get(edge.id);
|
|
3078
|
+
const start$2 = getPortPoint(source, edge.sourcePort, sourceRect, endpoints$2?.source ?? {
|
|
3079
|
+
x: sourceRect.x + sourceRect.width / 2,
|
|
3080
|
+
y: sourceRect.y + sourceRect.height / 2
|
|
3081
|
+
}, input.direction, input);
|
|
3082
|
+
const end$2 = getPortPoint(target, edge.targetPort, targetRect, endpoints$2?.target ?? {
|
|
3083
|
+
x: targetRect.x + targetRect.width / 2,
|
|
3084
|
+
y: targetRect.y + targetRect.height / 2
|
|
3085
|
+
}, input.direction, input);
|
|
3086
|
+
const side = feedbackSourcePortSide;
|
|
3087
|
+
const sameSideLoops = loops.filter((candidate) => {
|
|
3088
|
+
const candidateSourcePort = source.ports?.find((port) => port.name === candidate.sourcePort);
|
|
3089
|
+
const candidateTargetPort = source.ports?.find((port) => port.name === candidate.targetPort);
|
|
3090
|
+
const candidateSourceSide = candidateSourcePort ? input.portSettings?.(candidateSourcePort, source)?.["port.side"] : void 0;
|
|
3091
|
+
const candidateTargetSide = candidateTargetPort ? input.portSettings?.(candidateTargetPort, source)?.["port.side"] : void 0;
|
|
3092
|
+
return candidateSourceSide === side && candidateTargetSide === side;
|
|
3093
|
+
});
|
|
3094
|
+
const sameSideLoopIndex = sameSideLoops.findIndex((candidate) => candidate.id === edge.id);
|
|
3095
|
+
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
3096
|
+
const trackDistance = spacing + sameSideLoops.slice(0, Math.max(0, sameSideLoopIndex)).reduce((extent$1, candidate) => extent$1 + (side === "EAST" || side === "WEST" ? candidate.width ?? 0 : candidate.height ?? 0) + labelSpacing, 0);
|
|
3097
|
+
if (side === "EAST" || side === "WEST") {
|
|
3098
|
+
const nodeRects = [...placement.rectByNodeId.values()];
|
|
3099
|
+
const minimumNodeX = Math.min(...nodeRects.map((rect) => rect.x));
|
|
3100
|
+
const maximumNodeX = Math.max(...nodeRects.map((rect) => rect.x + rect.width));
|
|
3101
|
+
const maximumNodeY = Math.max(...nodeRects.map((rect) => rect.y + rect.height));
|
|
3102
|
+
const precedingLabelHeight = sameSideLoops.slice(0, Math.max(0, sameSideLoopIndex)).reduce((extent$1, candidate) => extent$1 + (candidate.height ?? 0) + labelSpacing, 0);
|
|
3103
|
+
const nearTrack = side === "EAST" ? sourceRect.x + sourceRect.width + spacing : sourceRect.x - spacing;
|
|
3104
|
+
const farTrack = side === "EAST" ? maximumNodeX + trackDistance + (edge.width ?? 0) : minimumNodeX - trackDistance - (edge.width ?? 0);
|
|
3105
|
+
const exteriorY = maximumNodeY + spacing + precedingLabelHeight + (edge.height ?? 0) / 2 + .5;
|
|
3106
|
+
pointsByEdgeId.set(edge.id, [
|
|
3107
|
+
start$2,
|
|
3108
|
+
{
|
|
3109
|
+
x: nearTrack,
|
|
3110
|
+
y: start$2.y
|
|
3111
|
+
},
|
|
3112
|
+
{
|
|
3113
|
+
x: nearTrack,
|
|
3114
|
+
y: exteriorY
|
|
3115
|
+
},
|
|
3116
|
+
{
|
|
3117
|
+
x: farTrack,
|
|
3118
|
+
y: exteriorY
|
|
3119
|
+
},
|
|
3120
|
+
{
|
|
3121
|
+
x: farTrack,
|
|
3122
|
+
y: end$2.y
|
|
3123
|
+
},
|
|
3124
|
+
end$2
|
|
3125
|
+
]);
|
|
3126
|
+
outsideFeedbackEdgeIds.add(edge.id);
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
const track$1 = side === "SOUTH" ? sourceRect.y + sourceRect.height + trackDistance : sourceRect.y - trackDistance;
|
|
3130
|
+
pointsByEdgeId.set(edge.id, [
|
|
3131
|
+
start$2,
|
|
3132
|
+
{
|
|
3133
|
+
x: start$2.x,
|
|
3134
|
+
y: track$1
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
x: end$2.x,
|
|
3138
|
+
y: track$1
|
|
3139
|
+
},
|
|
3140
|
+
end$2
|
|
3141
|
+
]);
|
|
3142
|
+
outsideFeedbackEdgeIds.add(edge.id);
|
|
3143
|
+
continue;
|
|
3144
|
+
}
|
|
2873
3145
|
const routeHorizontalSide = (side, indexOnSide, countOnSide) => {
|
|
2874
3146
|
const denominator = countOnSide * 2 + 1;
|
|
2875
3147
|
const sequenced = ordering === "SEQUENCED";
|
|
@@ -3035,8 +3307,16 @@ function routeEdges(style) {
|
|
|
3035
3307
|
x: targetRect.x + targetRect.width / 2,
|
|
3036
3308
|
y: targetRect.y + (reverse ? targetRect.height : 0)
|
|
3037
3309
|
});
|
|
3038
|
-
const
|
|
3039
|
-
const
|
|
3310
|
+
const sourcePort = source.ports?.find((port) => port.name === edge.sourcePort);
|
|
3311
|
+
const targetPort = target.ports?.find((port) => port.name === edge.targetPort);
|
|
3312
|
+
const sourcePortSide = sourcePort ? input.portSettings?.(sourcePort, source)?.["port.side"] : void 0;
|
|
3313
|
+
const targetPortSide = targetPort ? input.portSettings?.(targetPort, target)?.["port.side"] : void 0;
|
|
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;
|
|
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;
|
|
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;
|
|
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);
|
|
3040
3320
|
const sourceLayer = flowLayerByNodeId.get(edge.sourceId) ?? 0;
|
|
3041
3321
|
const targetLayer = flowLayerByNodeId.get(edge.targetId) ?? 0;
|
|
3042
3322
|
const earlierLayer = Math.min(sourceLayer, targetLayer);
|
|
@@ -3181,14 +3461,15 @@ function routeEdges(style) {
|
|
|
3181
3461
|
}
|
|
3182
3462
|
return {
|
|
3183
3463
|
pointsByEdgeId,
|
|
3184
|
-
splineNubControlsByEdgeId
|
|
3464
|
+
splineNubControlsByEdgeId,
|
|
3465
|
+
outsideFeedbackEdgeIds
|
|
3185
3466
|
};
|
|
3186
3467
|
};
|
|
3187
3468
|
}
|
|
3188
3469
|
const routeEdgesOrthogonally = routeEdges("ORTHOGONAL");
|
|
3189
3470
|
const routeEdgesWithPolylines = routeEdges("POLYLINE");
|
|
3190
3471
|
const routeEdgesWithSplines = routeEdges("SPLINES");
|
|
3191
|
-
function placePorts(ports, rect, direction, portSettings, nodeSettings) {
|
|
3472
|
+
function placePorts(ports, rect, direction, portSettings, nodeSettings, portDirection) {
|
|
3192
3473
|
if (!ports) return void 0;
|
|
3193
3474
|
const horizontal = direction === "left" || direction === "right";
|
|
3194
3475
|
const reverse = direction === "up" || direction === "left";
|
|
@@ -3202,7 +3483,7 @@ function placePorts(ports, rect, direction, portSettings, nodeSettings) {
|
|
|
3202
3483
|
sideByPort.set(port, configured);
|
|
3203
3484
|
continue;
|
|
3204
3485
|
}
|
|
3205
|
-
const outgoing = port.direction === "out";
|
|
3486
|
+
const outgoing = (portDirection?.(port) ?? port.direction) === "out";
|
|
3206
3487
|
const normalFarSide = reverse ? !outgoing : outgoing;
|
|
3207
3488
|
const farSide = sideFixed && configured === "UNDEFINED" ? !normalFarSide : normalFarSide;
|
|
3208
3489
|
sideByPort.set(port, horizontal ? farSide ? "EAST" : "WEST" : farSide ? "SOUTH" : "NORTH");
|
|
@@ -3312,7 +3593,7 @@ function placePorts(ports, rect, direction, portSettings, nodeSettings) {
|
|
|
3312
3593
|
});
|
|
3313
3594
|
}
|
|
3314
3595
|
/** Account for port extents in ELK's graph-padding normalization. */
|
|
3315
|
-
function normalizePlacementForPortExtents(input, placement, order) {
|
|
3596
|
+
function normalizePlacementForPortExtents(input, placement, order, orientation) {
|
|
3316
3597
|
const horizontal = input.direction === "left" || input.direction === "right";
|
|
3317
3598
|
const physicalLayers = order.layers.map((layer) => layer.flatMap((id) => placement.rectByNodeId.has(id) ? [id] : [])).filter((layer) => layer.length > 0).sort((left, right) => {
|
|
3318
3599
|
const leftRect = placement.rectByNodeId.get(left[0]);
|
|
@@ -3347,7 +3628,7 @@ function normalizePlacementForPortExtents(input, placement, order) {
|
|
|
3347
3628
|
const ports = placePorts(node.ports, rect, input.direction, (port) => input.portSettings?.(port, node), {
|
|
3348
3629
|
...input.settings,
|
|
3349
3630
|
...input.nodeSettings?.(node)
|
|
3350
|
-
});
|
|
3631
|
+
}, (port) => getOrientedPortDirection(input, orientation, node, port));
|
|
3351
3632
|
for (const port of ports ?? []) {
|
|
3352
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));
|
|
3353
3634
|
if (trailing) trailingExtent = Math.max(trailingExtent, extent$1);
|
|
@@ -3366,7 +3647,7 @@ function normalizePlacementForPortExtents(input, placement, order) {
|
|
|
3366
3647
|
const ports = placePorts(node.ports, rect, input.direction, (port) => input.portSettings?.(port, node), {
|
|
3367
3648
|
...input.settings,
|
|
3368
3649
|
...input.nodeSettings?.(node)
|
|
3369
|
-
});
|
|
3650
|
+
}, (port) => getOrientedPortDirection(input, orientation, node, port));
|
|
3370
3651
|
for (const port of ports ?? []) {
|
|
3371
3652
|
minimumX = Math.min(minimumX, rect.x + (port.x ?? 0));
|
|
3372
3653
|
minimumY = Math.min(minimumY, rect.y + (port.y ?? 0));
|
|
@@ -3616,7 +3897,7 @@ function normalizeAndBalance(nodes, previousLayerCounts) {
|
|
|
3616
3897
|
}
|
|
3617
3898
|
return filling;
|
|
3618
3899
|
}
|
|
3619
|
-
function runNetworkSimplex(nodes,
|
|
3900
|
+
function runNetworkSimplex(nodes, _edges, iterationLimit, previousLayerCounts, balance = true) {
|
|
3620
3901
|
const orderedEdges = nodes.flatMap((node) => node.outgoing);
|
|
3621
3902
|
orderedEdges.forEach((edge, index) => edge.order = index);
|
|
3622
3903
|
assignInitialLayers(nodes);
|
|
@@ -3890,10 +4171,26 @@ function splitLongEdges(input, orientation, assignment) {
|
|
|
3890
4171
|
const originalEdgeBySegmentId = /* @__PURE__ */ new Map();
|
|
3891
4172
|
const usedNodeIds = new Set(nodes.map((node) => node.id));
|
|
3892
4173
|
const originalNodeIds = new Set(usedNodeIds);
|
|
4174
|
+
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
4175
|
+
const forwardSourceSide = input.direction === "right" ? "EAST" : input.direction === "left" ? "WEST" : input.direction === "down" ? "SOUTH" : "NORTH";
|
|
4176
|
+
const forwardTargetSide = input.direction === "right" ? "WEST" : input.direction === "left" ? "EAST" : input.direction === "down" ? "NORTH" : "SOUTH";
|
|
3893
4177
|
for (const edge of input.graph.edges) {
|
|
3894
4178
|
const sourceLayer = layerByNodeId.get(edge.sourceId) ?? 0;
|
|
3895
4179
|
const targetLayer = layerByNodeId.get(edge.targetId) ?? 0;
|
|
3896
|
-
|
|
4180
|
+
const span = Math.abs(targetLayer - sourceLayer);
|
|
4181
|
+
const source = nodeById.get(edge.sourceId);
|
|
4182
|
+
const target = nodeById.get(edge.targetId);
|
|
4183
|
+
const sourcePort = source?.ports?.find((port) => port.name === edge.sourcePort);
|
|
4184
|
+
const targetPort = target?.ports?.find((port) => port.name === edge.targetPort);
|
|
4185
|
+
const sourceSide = source && sourcePort ? input.portSettings?.(sourcePort, source)?.["port.side"] : void 0;
|
|
4186
|
+
const targetSide = target && targetPort ? input.portSettings?.(targetPort, target)?.["port.side"] : void 0;
|
|
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);
|
|
4193
|
+
if (span <= 1 || edge.sourceId === edge.targetId || input.settings.feedbackEdges === true && orientation.reversedEdgeIds.has(edge.id) || fixedSideFeedback) {
|
|
3897
4194
|
edges.push(edge);
|
|
3898
4195
|
originalEdgeBySegmentId.set(edge.id, edge);
|
|
3899
4196
|
if (orientation.reversedEdgeIds.has(edge.id)) reversedEdgeIds.add(edge.id);
|
|
@@ -4055,7 +4352,9 @@ function joinLongEdgeRoutes(routes, segmentIdsByEdgeId, preserveInternalDuplicat
|
|
|
4055
4352
|
return result;
|
|
4056
4353
|
};
|
|
4057
4354
|
const pointsByEdgeId = /* @__PURE__ */ new Map();
|
|
4355
|
+
const outsideFeedbackEdgeIds = /* @__PURE__ */ new Set();
|
|
4058
4356
|
for (const [edgeId, segmentIds] of segmentIdsByEdgeId) {
|
|
4357
|
+
if (segmentIds.some((segmentId) => routes.outsideFeedbackEdgeIds?.has(segmentId))) outsideFeedbackEdgeIds.add(edgeId);
|
|
4059
4358
|
if (convertLongSplines && segmentIds.length > 1) {
|
|
4060
4359
|
const segments = segmentIds.map((segmentId) => routes.pointsByEdgeId.get(segmentId) ?? []).filter((points$1) => points$1.length >= 2);
|
|
4061
4360
|
if (segments.length > 1) {
|
|
@@ -4128,7 +4427,10 @@ function joinLongEdgeRoutes(routes, segmentIdsByEdgeId, preserveInternalDuplicat
|
|
|
4128
4427
|
} else for (const segmentId of segmentIds) appendPoints(points, routes.pointsByEdgeId.get(segmentId) ?? [], preserveInternalDuplicates || segmentIds.length === 1);
|
|
4129
4428
|
pointsByEdgeId.set(edgeId, preserveInternalDuplicates || segmentIds.length === 1 ? points : simplify(points));
|
|
4130
4429
|
}
|
|
4131
|
-
return {
|
|
4430
|
+
return {
|
|
4431
|
+
pointsByEdgeId,
|
|
4432
|
+
outsideFeedbackEdgeIds
|
|
4433
|
+
};
|
|
4132
4434
|
}
|
|
4133
4435
|
|
|
4134
4436
|
//#endregion
|
|
@@ -8181,6 +8483,24 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8181
8483
|
left: options.padding?.left ?? 12
|
|
8182
8484
|
};
|
|
8183
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
|
+
}
|
|
8184
8504
|
const input = {
|
|
8185
8505
|
graph,
|
|
8186
8506
|
sizes: new Map(graph.nodes.map((node) => [node.id, getNodeSize(node, options)])),
|
|
@@ -8199,6 +8519,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8199
8519
|
...options.edgeSettings === void 0 ? {} : { edgeSettings: options.edgeSettings },
|
|
8200
8520
|
portSettings: (port, node) => ({
|
|
8201
8521
|
...options.portSettings?.(port, node),
|
|
8522
|
+
...parallelPortIndexByKey.has(`${node.id}\0${port.name}`) ? { "port.index": parallelPortIndexByKey.get(`${node.id}\0${port.name}`) } : {},
|
|
8202
8523
|
...switchedSideByPort.has(`${node.id}\0${port.name}`) ? { "port.side": switchedSideByPort.get(`${node.id}\0${port.name}`) } : {}
|
|
8203
8524
|
})
|
|
8204
8525
|
};
|
|
@@ -8396,7 +8717,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8396
8717
|
});
|
|
8397
8718
|
}
|
|
8398
8719
|
}
|
|
8399
|
-
measure("port-margin-normalization", () => normalizePlacementForPortExtents(expanded.input, placement, order));
|
|
8720
|
+
measure("port-margin-normalization", () => normalizePlacementForPortExtents(expanded.input, placement, order, expanded.orientation));
|
|
8400
8721
|
{
|
|
8401
8722
|
const horizontal = direction === "left" || direction === "right";
|
|
8402
8723
|
const crossCenter = (nodeId) => {
|
|
@@ -8421,8 +8742,186 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8421
8742
|
}
|
|
8422
8743
|
const edgeRouting = options.settings?.edgeRouting ?? "ORTHOGONAL";
|
|
8423
8744
|
const edgeRouter = options.strategies?.routeEdges ?? (edgeRouting === "POLYLINE" ? routeEdgesWithPolylines : edgeRouting === "SPLINES" ? routeEdgesWithSplines : routeEdgesOrthogonally);
|
|
8424
|
-
|
|
8745
|
+
let expandedRoutes = measure("edge-routing", () => edgeRouter(expanded.input, expanded.orientation, placement));
|
|
8425
8746
|
measure("post-compaction", () => applyPostCompaction(expanded.input, placement, expandedRoutes));
|
|
8747
|
+
const antiparallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8748
|
+
const parallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8749
|
+
const postCompactionNodeCrossDeltas = /* @__PURE__ */ new Map();
|
|
8750
|
+
const horizontalAntiparallelFlow = direction === "left" || direction === "right";
|
|
8751
|
+
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") {
|
|
8752
|
+
const neighbors = new Map(graph.nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
|
|
8753
|
+
for (const edge of graph.edges) {
|
|
8754
|
+
if (edge.sourceId === edge.targetId) continue;
|
|
8755
|
+
neighbors.get(edge.sourceId)?.add(edge.targetId);
|
|
8756
|
+
neighbors.get(edge.targetId)?.add(edge.sourceId);
|
|
8757
|
+
}
|
|
8758
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8759
|
+
for (const node of graph.nodes) {
|
|
8760
|
+
if (visited.has(node.id)) continue;
|
|
8761
|
+
const component = [];
|
|
8762
|
+
const pending = [node.id];
|
|
8763
|
+
while (pending.length > 0) {
|
|
8764
|
+
const id = pending.pop();
|
|
8765
|
+
if (visited.has(id)) continue;
|
|
8766
|
+
visited.add(id);
|
|
8767
|
+
const member = graph.nodes.find((candidate) => candidate.id === id);
|
|
8768
|
+
if (member) component.push(member);
|
|
8769
|
+
for (const neighbor of neighbors.get(id) ?? []) pending.push(neighbor);
|
|
8770
|
+
}
|
|
8771
|
+
if (component.length < 2) continue;
|
|
8772
|
+
const componentIds = new Set(component.map((member) => member.id));
|
|
8773
|
+
if (!graph.edges.some((edge) => componentIds.has(edge.sourceId) && componentIds.has(edge.targetId) && expanded.orientation.reversedEdgeIds.has(edge.id))) continue;
|
|
8774
|
+
const directedPairCounts = /* @__PURE__ */ new Map();
|
|
8775
|
+
for (const edge of graph.edges) {
|
|
8776
|
+
if (!componentIds.has(edge.sourceId) || !componentIds.has(edge.targetId)) continue;
|
|
8777
|
+
const key = `${edge.sourceId}\0${edge.targetId}`;
|
|
8778
|
+
directedPairCounts.set(key, (directedPairCounts.get(key) ?? 0) + 1);
|
|
8779
|
+
}
|
|
8780
|
+
if ([...directedPairCounts.values()].some((count) => count > 1)) continue;
|
|
8781
|
+
if (component.some((member) => {
|
|
8782
|
+
const constraints = options.nodeSettings?.(member)?.portConstraints;
|
|
8783
|
+
return constraints !== void 0 && constraints !== "UNDEFINED" && constraints !== "FREE";
|
|
8784
|
+
})) continue;
|
|
8785
|
+
const layerCounts = /* @__PURE__ */ new Map();
|
|
8786
|
+
for (const member of component) {
|
|
8787
|
+
const layer = expanded.assignment.layerByNodeId.get(member.id);
|
|
8788
|
+
if (layer === void 0) continue;
|
|
8789
|
+
layerCounts.set(layer, (layerCounts.get(layer) ?? 0) + 1);
|
|
8790
|
+
}
|
|
8791
|
+
if ([...layerCounts.values()].some((count) => count !== 1)) continue;
|
|
8792
|
+
const rects = component.flatMap((member) => {
|
|
8793
|
+
const rect = mutableRects.get(member.id);
|
|
8794
|
+
return rect ? [{
|
|
8795
|
+
member,
|
|
8796
|
+
rect
|
|
8797
|
+
}] : [];
|
|
8798
|
+
});
|
|
8799
|
+
if (rects.length !== component.length) continue;
|
|
8800
|
+
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;
|
|
8801
|
+
for (const { member, rect } of rects) {
|
|
8802
|
+
const currentCross = horizontalAntiparallelFlow ? rect.y : rect.x;
|
|
8803
|
+
const nextCross = horizontalAntiparallelFlow ? center - rect.height / 2 : center - rect.width / 2;
|
|
8804
|
+
postCompactionNodeCrossDeltas.set(member.id, nextCross - currentCross);
|
|
8805
|
+
mutableRects.set(member.id, horizontalAntiparallelFlow ? {
|
|
8806
|
+
...rect,
|
|
8807
|
+
y: nextCross
|
|
8808
|
+
} : {
|
|
8809
|
+
...rect,
|
|
8810
|
+
x: nextCross
|
|
8811
|
+
});
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
}
|
|
8815
|
+
{
|
|
8816
|
+
const horizontal = horizontalAntiparallelFlow;
|
|
8817
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
8818
|
+
const pairEdges = /* @__PURE__ */ new Map();
|
|
8819
|
+
for (const edge of graph.edges) {
|
|
8820
|
+
if (edge.sourceId === edge.targetId || (edge.width ?? 0) <= 0 || (edge.height ?? 0) <= 0) continue;
|
|
8821
|
+
const key = [edge.sourceId, edge.targetId].sort().join("\0");
|
|
8822
|
+
const pair = pairEdges.get(key) ?? [];
|
|
8823
|
+
pair.push(edge);
|
|
8824
|
+
pairEdges.set(key, pair);
|
|
8825
|
+
}
|
|
8826
|
+
for (const pair of pairEdges.values()) {
|
|
8827
|
+
if (pair.length !== 2 || pair[0].sourceId !== pair[1].targetId || pair[0].targetId !== pair[1].sourceId || pair.some((edge) => {
|
|
8828
|
+
const settings = options.edgeSettings?.(edge);
|
|
8829
|
+
return (settings?.["edgeLabels.placement"] ?? "CENTER") !== "CENTER" || settings?.["edgeLabels.inline"] !== true;
|
|
8830
|
+
})) continue;
|
|
8831
|
+
const firstNode = nodesById.get(pair[0].sourceId);
|
|
8832
|
+
const secondNode = nodesById.get(pair[0].targetId);
|
|
8833
|
+
const firstRect = placement.rectByNodeId.get(pair[0].sourceId);
|
|
8834
|
+
const secondRect = placement.rectByNodeId.get(pair[0].targetId);
|
|
8835
|
+
if (!firstNode || !secondNode || !firstRect || !secondRect) continue;
|
|
8836
|
+
const nonSelfNeighbors = (nodeId) => new Set(graph.edges.flatMap((edge) => edge.sourceId === edge.targetId ? [] : edge.sourceId === nodeId ? [edge.targetId] : edge.targetId === nodeId ? [edge.sourceId] : []));
|
|
8837
|
+
if (nonSelfNeighbors(firstNode.id).size !== 1 || nonSelfNeighbors(secondNode.id).size !== 1) continue;
|
|
8838
|
+
const hasFlexiblePorts = (node) => {
|
|
8839
|
+
const constraints = options.nodeSettings?.(node)?.portConstraints;
|
|
8840
|
+
return constraints === void 0 || constraints === "UNDEFINED" || constraints === "FREE";
|
|
8841
|
+
};
|
|
8842
|
+
if (!hasFlexiblePorts(firstNode) || !hasFlexiblePorts(secondNode)) continue;
|
|
8843
|
+
const firstBeforeSecond = horizontal ? firstRect.x + firstRect.width / 2 < secondRect.x + secondRect.width / 2 : firstRect.y + firstRect.height / 2 < secondRect.y + secondRect.height / 2;
|
|
8844
|
+
const beforeRect = firstBeforeSecond ? firstRect : secondRect;
|
|
8845
|
+
const afterRect = firstBeforeSecond ? secondRect : firstRect;
|
|
8846
|
+
const forward = pair.find((edge) => firstBeforeSecond ? edge.sourceId === pair[0].sourceId : edge.sourceId === pair[0].targetId);
|
|
8847
|
+
if (!forward?.sourcePort || !forward.targetPort) continue;
|
|
8848
|
+
let cross = Math.min(horizontal ? firstRect.y : firstRect.x, horizontal ? secondRect.y : secondRect.x);
|
|
8849
|
+
const edgeSpacing = Number(options.settings?.["spacing.edgeEdge"] ?? 10);
|
|
8850
|
+
for (const edge of pair) {
|
|
8851
|
+
const width = edge.width ?? 0;
|
|
8852
|
+
const height = edge.height ?? 0;
|
|
8853
|
+
antiparallelLabelPositions.set(edge.id, horizontal ? {
|
|
8854
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
8855
|
+
y: cross
|
|
8856
|
+
} : {
|
|
8857
|
+
x: cross,
|
|
8858
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2
|
|
8859
|
+
});
|
|
8860
|
+
cross += (horizontal ? height : width) + edgeSpacing;
|
|
8861
|
+
}
|
|
8862
|
+
const forwardLabel = antiparallelLabelPositions.get(forward.id);
|
|
8863
|
+
const forwardCross = horizontal ? Math.round(forwardLabel.y + (forward.height ?? 0) / 2) : Math.round(forwardLabel.x + (forward.width ?? 0) / 2);
|
|
8864
|
+
const alignPort = (node, portName) => {
|
|
8865
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
8866
|
+
const port = node.ports?.find((candidate) => candidate.name === portName);
|
|
8867
|
+
if (!rect || !port) return;
|
|
8868
|
+
const placedPort = placePorts(node.ports, rect, direction, (candidate) => input.portSettings?.(candidate, node), {
|
|
8869
|
+
...options.settings,
|
|
8870
|
+
...options.nodeSettings?.(node)
|
|
8871
|
+
}, (candidate) => getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate))?.find((candidate) => candidate.name === portName);
|
|
8872
|
+
if (!placedPort) return;
|
|
8873
|
+
const nextCross = forwardCross - (horizontal ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2);
|
|
8874
|
+
const currentCross = horizontal ? rect.y : rect.x;
|
|
8875
|
+
postCompactionNodeCrossDeltas.set(node.id, (postCompactionNodeCrossDeltas.get(node.id) ?? 0) + nextCross - currentCross);
|
|
8876
|
+
mutableRects.set(node.id, horizontal ? {
|
|
8877
|
+
...rect,
|
|
8878
|
+
y: nextCross
|
|
8879
|
+
} : {
|
|
8880
|
+
...rect,
|
|
8881
|
+
x: nextCross
|
|
8882
|
+
});
|
|
8883
|
+
};
|
|
8884
|
+
alignPort(nodesById.get(forward.sourceId), forward.sourcePort);
|
|
8885
|
+
alignPort(nodesById.get(forward.targetId), forward.targetPort);
|
|
8886
|
+
}
|
|
8887
|
+
}
|
|
8888
|
+
if (postCompactionNodeCrossDeltas.size > 0) {
|
|
8889
|
+
const pointsByEdgeId = new Map(expandedRoutes.pointsByEdgeId);
|
|
8890
|
+
for (const edge of graph.edges) {
|
|
8891
|
+
const points = [...pointsByEdgeId.get(edge.id) ?? []];
|
|
8892
|
+
if (points.length === 0) continue;
|
|
8893
|
+
const sourceDelta = postCompactionNodeCrossDeltas.get(edge.sourceId) ?? 0;
|
|
8894
|
+
const targetDelta = postCompactionNodeCrossDeltas.get(edge.targetId) ?? 0;
|
|
8895
|
+
const shiftEndpoint = (endpointIndex, adjacentIndex, delta) => {
|
|
8896
|
+
if (delta === 0) return;
|
|
8897
|
+
const endpoint = points[endpointIndex];
|
|
8898
|
+
const adjacent = points[adjacentIndex];
|
|
8899
|
+
if (!endpoint) return;
|
|
8900
|
+
const originalCross = horizontalAntiparallelFlow ? endpoint.y : endpoint.x;
|
|
8901
|
+
points[endpointIndex] = horizontalAntiparallelFlow ? {
|
|
8902
|
+
...endpoint,
|
|
8903
|
+
y: endpoint.y + delta
|
|
8904
|
+
} : {
|
|
8905
|
+
...endpoint,
|
|
8906
|
+
x: endpoint.x + delta
|
|
8907
|
+
};
|
|
8908
|
+
if (adjacent && (horizontalAntiparallelFlow ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontalAntiparallelFlow ? {
|
|
8909
|
+
...adjacent,
|
|
8910
|
+
y: adjacent.y + delta
|
|
8911
|
+
} : {
|
|
8912
|
+
...adjacent,
|
|
8913
|
+
x: adjacent.x + delta
|
|
8914
|
+
};
|
|
8915
|
+
};
|
|
8916
|
+
shiftEndpoint(0, 1, sourceDelta);
|
|
8917
|
+
shiftEndpoint(points.length - 1, points.length - 2, targetDelta);
|
|
8918
|
+
pointsByEdgeId.set(edge.id, points);
|
|
8919
|
+
}
|
|
8920
|
+
expandedRoutes = {
|
|
8921
|
+
...expandedRoutes,
|
|
8922
|
+
pointsByEdgeId
|
|
8923
|
+
};
|
|
8924
|
+
}
|
|
8426
8925
|
let routes = measure("long-edge-joining", () => joinLongEdgeRoutes(expandedRoutes, expanded.segmentIdsByEdgeId, edgeRouting === "SPLINES" || options.settings?.unnecessaryBendpoints === true, edgeRouting === "SPLINES", Number(options.settings?.["spacing.edgeNodeBetweenLayers"] ?? 10)));
|
|
8427
8926
|
if (options.settings?.["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT") {
|
|
8428
8927
|
const horizontal = direction === "right" || direction === "left";
|
|
@@ -8554,12 +9053,115 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8554
9053
|
};
|
|
8555
9054
|
routes.pointsByEdgeId.set(edge.id, points);
|
|
8556
9055
|
}
|
|
9056
|
+
for (const pair of labeledEdgesByPair.values()) {
|
|
9057
|
+
if (pair.length < 2) continue;
|
|
9058
|
+
const sourceRect = mutableRects.get(pair[0].sourceId);
|
|
9059
|
+
const targetRect = mutableRects.get(pair[0].targetId);
|
|
9060
|
+
if (!sourceRect || !targetRect) continue;
|
|
9061
|
+
const horizontal = direction === "left" || direction === "right";
|
|
9062
|
+
const beforeRect = horizontal ? sourceRect.x <= targetRect.x ? sourceRect : targetRect : sourceRect.y <= targetRect.y ? sourceRect : targetRect;
|
|
9063
|
+
const afterRect = beforeRect === sourceRect ? targetRect : sourceRect;
|
|
9064
|
+
const edgeSpacing = Number(options.settings?.["spacing.edgeEdge"] ?? 10);
|
|
9065
|
+
let cross = horizontal ? padding.top : padding.left;
|
|
9066
|
+
const orderedPair = options.settings?.["considerModelOrder.strategy"] === "PREFER_NODES" ? pair : [...pair].reverse();
|
|
9067
|
+
for (const edge of orderedPair) {
|
|
9068
|
+
const width = edge.width ?? 0;
|
|
9069
|
+
const height = edge.height ?? 0;
|
|
9070
|
+
parallelLabelPositions.set(edge.id, horizontal ? {
|
|
9071
|
+
x: (beforeRect.x + beforeRect.width + afterRect.x - width) / 2,
|
|
9072
|
+
y: cross
|
|
9073
|
+
} : {
|
|
9074
|
+
x: cross,
|
|
9075
|
+
y: (beforeRect.y + beforeRect.height + afterRect.y - height) / 2
|
|
9076
|
+
});
|
|
9077
|
+
cross += (horizontal ? height : width) + edgeSpacing;
|
|
9078
|
+
}
|
|
9079
|
+
const anchorEdge = orderedPair[0];
|
|
9080
|
+
const anchorLabel = parallelLabelPositions.get(anchorEdge.id);
|
|
9081
|
+
const desiredCross = Math.round(horizontal ? anchorLabel.y + (anchorEdge.height ?? 0) / 2 : anchorLabel.x + (anchorEdge.width ?? 0) / 2);
|
|
9082
|
+
const alignEndpoint = (nodeId, portName) => {
|
|
9083
|
+
const node = graph.nodes.find((candidate) => candidate.id === nodeId);
|
|
9084
|
+
const rect = mutableRects.get(nodeId);
|
|
9085
|
+
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
9086
|
+
if (!node || !rect || !port) return;
|
|
9087
|
+
const placedPort = placePorts(node.ports, rect, direction, (candidate) => input.portSettings?.(candidate, node), {
|
|
9088
|
+
...options.settings,
|
|
9089
|
+
...options.nodeSettings?.(node)
|
|
9090
|
+
}, (candidate) => getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate))?.find((candidate) => candidate.name === portName);
|
|
9091
|
+
if (!placedPort) return;
|
|
9092
|
+
const localCross = horizontal ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2;
|
|
9093
|
+
const currentCross = horizontal ? rect.y : rect.x;
|
|
9094
|
+
const nextCross = desiredCross - localCross;
|
|
9095
|
+
const delta = nextCross - currentCross;
|
|
9096
|
+
mutableRects.set(nodeId, horizontal ? {
|
|
9097
|
+
...rect,
|
|
9098
|
+
y: nextCross
|
|
9099
|
+
} : {
|
|
9100
|
+
...rect,
|
|
9101
|
+
x: nextCross
|
|
9102
|
+
});
|
|
9103
|
+
for (const edge of graph.edges) {
|
|
9104
|
+
const points = [...routes.pointsByEdgeId.get(edge.id) ?? []];
|
|
9105
|
+
if (points.length === 0) continue;
|
|
9106
|
+
const index = edge.sourceId === nodeId ? 0 : edge.targetId === nodeId ? points.length - 1 : void 0;
|
|
9107
|
+
if (index === void 0) continue;
|
|
9108
|
+
const endpoint = points[index];
|
|
9109
|
+
const originalCross = horizontal ? endpoint.y : endpoint.x;
|
|
9110
|
+
points[index] = horizontal ? {
|
|
9111
|
+
...endpoint,
|
|
9112
|
+
y: endpoint.y + delta
|
|
9113
|
+
} : {
|
|
9114
|
+
...endpoint,
|
|
9115
|
+
x: endpoint.x + delta
|
|
9116
|
+
};
|
|
9117
|
+
const adjacentIndex = index === 0 ? 1 : points.length - 2;
|
|
9118
|
+
const adjacent = points[adjacentIndex];
|
|
9119
|
+
if (adjacent && (horizontal ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontal ? {
|
|
9120
|
+
...adjacent,
|
|
9121
|
+
y: adjacent.y + delta
|
|
9122
|
+
} : {
|
|
9123
|
+
...adjacent,
|
|
9124
|
+
x: adjacent.x + delta
|
|
9125
|
+
};
|
|
9126
|
+
routes.pointsByEdgeId.set(edge.id, points);
|
|
9127
|
+
}
|
|
9128
|
+
};
|
|
9129
|
+
if (anchorEdge.sourcePort && anchorEdge.targetPort) {
|
|
9130
|
+
alignEndpoint(anchorEdge.sourceId, anchorEdge.sourcePort);
|
|
9131
|
+
alignEndpoint(anchorEdge.targetId, anchorEdge.targetPort);
|
|
9132
|
+
}
|
|
9133
|
+
}
|
|
9134
|
+
const routedPortAnchors = /* @__PURE__ */ new Map();
|
|
9135
|
+
for (const edge of graph.edges) {
|
|
9136
|
+
const points = routes.pointsByEdgeId.get(edge.id);
|
|
9137
|
+
const first = points?.[0];
|
|
9138
|
+
const last = points?.at(-1);
|
|
9139
|
+
if (edge.sourcePort !== void 0 && first && graph.edges.filter((candidate) => candidate.sourceId === edge.sourceId && candidate.sourcePort === edge.sourcePort).length === 1) routedPortAnchors.set(`${edge.sourceId}\0${edge.sourcePort}`, first);
|
|
9140
|
+
if (edge.targetPort !== void 0 && last && graph.edges.filter((candidate) => candidate.targetId === edge.targetId && candidate.targetPort === edge.targetPort).length === 1) routedPortAnchors.set(`${edge.targetId}\0${edge.targetPort}`, last);
|
|
9141
|
+
}
|
|
8557
9142
|
const nodes = graph.nodes.map((node) => {
|
|
8558
9143
|
const rect = placement.rectByNodeId.get(node.id);
|
|
8559
9144
|
if (!rect) throw new Error(`Node placement missing for ${node.id}`);
|
|
8560
|
-
|
|
9145
|
+
let ports = placePorts(node.ports, rect, direction, (port) => input.portSettings?.(port, node), {
|
|
8561
9146
|
...options.settings,
|
|
8562
9147
|
...options.nodeSettings?.(node)
|
|
9148
|
+
}, (port) => getOrientedPortDirection(expanded.input, expanded.orientation, node, port));
|
|
9149
|
+
if (options.nodeSettings?.(node)?.portConstraints === "FIXED_SIDE") ports = ports?.map((port) => {
|
|
9150
|
+
if ((port.width ?? 8) !== 0 || (port.height ?? 8) !== 0) return port;
|
|
9151
|
+
const anchor = routedPortAnchors.get(`${node.id}\0${port.name}`);
|
|
9152
|
+
if (!anchor || port.x === void 0 || port.y === void 0) return port;
|
|
9153
|
+
const configuredAnchor = (input.portSettings?.(port, node))?.["port.anchor"];
|
|
9154
|
+
const width = port.width ?? 0;
|
|
9155
|
+
const height = port.height ?? 0;
|
|
9156
|
+
const defaultAnchorX = port.x >= rect.width ? width : port.x + width <= 0 ? 0 : width / 2;
|
|
9157
|
+
const defaultAnchorY = port.y >= rect.height ? height : port.y + height <= 0 ? 0 : height / 2;
|
|
9158
|
+
const x = anchor.x - rect.x - (configuredAnchor?.x ?? defaultAnchorX);
|
|
9159
|
+
const y = anchor.y - rect.y - (configuredAnchor?.y ?? defaultAnchorY);
|
|
9160
|
+
return {
|
|
9161
|
+
...port,
|
|
9162
|
+
x: x === 0 && Object.is(port.x, -0) ? port.x : x,
|
|
9163
|
+
y: y === 0 && Object.is(port.y, -0) ? port.y : y
|
|
9164
|
+
};
|
|
8563
9165
|
});
|
|
8564
9166
|
return {
|
|
8565
9167
|
...node,
|
|
@@ -8567,6 +9169,14 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8567
9169
|
...ports === void 0 ? {} : { ports }
|
|
8568
9170
|
};
|
|
8569
9171
|
});
|
|
9172
|
+
const feedbackNodeRects = graph.nodes.flatMap((node) => {
|
|
9173
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
9174
|
+
return rect ? [rect] : [];
|
|
9175
|
+
});
|
|
9176
|
+
const minimumFeedbackNodeX = Math.min(...feedbackNodeRects.map((rect) => rect.x));
|
|
9177
|
+
const maximumFeedbackNodeX = Math.max(...feedbackNodeRects.map((rect) => rect.x + rect.width));
|
|
9178
|
+
const minimumFeedbackNodeY = Math.min(...feedbackNodeRects.map((rect) => rect.y));
|
|
9179
|
+
const maximumFeedbackNodeY = Math.max(...feedbackNodeRects.map((rect) => rect.y + rect.height));
|
|
8570
9180
|
const edges = graph.edges.map((edge) => {
|
|
8571
9181
|
const points = [...routes.pointsByEdgeId.get(edge.id) ?? []];
|
|
8572
9182
|
const midpoint = getPolylineMidpoint(points);
|
|
@@ -8582,11 +9192,56 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8582
9192
|
const labelDummyRect = placement.rectByNodeId.get(expanded.labelDummyIdByEdgeId.get(edge.id) ?? "");
|
|
8583
9193
|
const edgeLabelSideSelection = options.settings?.["edgeLabels.sideSelection"] ?? "SMART_DOWN";
|
|
8584
9194
|
const placeLabelUp = edgeLabelSideSelection === "ALWAYS_UP" || edgeLabelSideSelection === "SMART_UP" || edgeLabelSideSelection === "DIRECTION_UP";
|
|
8585
|
-
const routeX = labelPlacement === "TAIL" ? firstPoint.x + labelSpacing : labelPlacement === "HEAD" ? lastPoint.x - width - labelSpacing : midpoint.x - width / 2;
|
|
8586
|
-
const routeY = 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);
|
|
8587
9195
|
const horizontal = direction === "left" || direction === "right";
|
|
8588
|
-
const
|
|
8589
|
-
|
|
9196
|
+
const verticalTrack = horizontal ? points.find((point, index) => {
|
|
9197
|
+
const next = points[index + 1];
|
|
9198
|
+
return next !== void 0 && point.x === next.x && point.y !== next.y;
|
|
9199
|
+
}) : void 0;
|
|
9200
|
+
const secondPoint = points[1];
|
|
9201
|
+
const beforeLastPoint = points.at(-2);
|
|
9202
|
+
const flowDelta = horizontal ? lastPoint.x - firstPoint.x : lastPoint.y - firstPoint.y;
|
|
9203
|
+
const firstLeadDelta = secondPoint ? horizontal ? secondPoint.x - firstPoint.x : secondPoint.y - firstPoint.y : 0;
|
|
9204
|
+
const lastLeadDelta = beforeLastPoint ? horizontal ? lastPoint.x - beforeLastPoint.x : lastPoint.y - beforeLastPoint.y : 0;
|
|
9205
|
+
const outsideFeedback = routes.outsideFeedbackEdgeIds?.has(edge.id) === true || secondPoint !== void 0 && beforeLastPoint !== void 0 && flowDelta !== 0 && firstLeadDelta * flowDelta < 0 && lastLeadDelta * flowDelta < 0;
|
|
9206
|
+
const horizontalFeedbackCandidate = outsideFeedback ? points.flatMap((point, index) => {
|
|
9207
|
+
const next = points[index + 1];
|
|
9208
|
+
return next !== void 0 && point.y === next.y && (point.y < minimumFeedbackNodeY || point.y > maximumFeedbackNodeY) ? [{
|
|
9209
|
+
start: point,
|
|
9210
|
+
end: next,
|
|
9211
|
+
length: Math.abs(next.x - point.x)
|
|
9212
|
+
}] : [];
|
|
9213
|
+
}).sort((left, right) => right.length - left.length)[0] : void 0;
|
|
9214
|
+
const verticalFeedbackCandidate = outsideFeedback ? points.flatMap((point, index) => {
|
|
9215
|
+
const next = points[index + 1];
|
|
9216
|
+
return next !== void 0 && point.x === next.x && (point.x < minimumFeedbackNodeX || point.x > maximumFeedbackNodeX) ? [{
|
|
9217
|
+
start: point,
|
|
9218
|
+
end: next,
|
|
9219
|
+
length: Math.abs(next.y - point.y)
|
|
9220
|
+
}] : [];
|
|
9221
|
+
}).sort((left, right) => right.length - left.length)[0] : void 0;
|
|
9222
|
+
const sourceNode = graph.nodes.find((node) => node.id === edge.sourceId);
|
|
9223
|
+
const targetNode = graph.nodes.find((node) => node.id === edge.targetId);
|
|
9224
|
+
const sourcePort = sourceNode?.ports?.find((port) => port.name === edge.sourcePort);
|
|
9225
|
+
const targetPort = targetNode?.ports?.find((port) => port.name === edge.targetPort);
|
|
9226
|
+
const sourcePortSide = sourceNode && sourcePort ? options.portSettings?.(sourcePort, sourceNode)?.["port.side"] : void 0;
|
|
9227
|
+
const targetPortSide = sourceNode && targetPort ? options.portSettings?.(targetPort, sourceNode)?.["port.side"] : void 0;
|
|
9228
|
+
const horizontalFeedbackTrack = edge.sourceId === edge.targetId && (sourcePortSide === "EAST" || sourcePortSide === "WEST") && targetPortSide === sourcePortSide || (horizontalFeedbackCandidate?.length ?? -1) >= (verticalFeedbackCandidate?.length ?? -1) ? horizontalFeedbackCandidate : void 0;
|
|
9229
|
+
const verticalFeedbackTrack = horizontalFeedbackTrack ? void 0 : verticalFeedbackCandidate;
|
|
9230
|
+
const trackNearTarget = verticalTrack !== void 0 && Math.abs(lastPoint.x - verticalTrack.x) < Math.abs(verticalTrack.x - firstPoint.x);
|
|
9231
|
+
const edgeNodeSpacing = Number(options.settings?.["spacing.edgeNodeBetweenLayers"] ?? 10);
|
|
9232
|
+
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;
|
|
9233
|
+
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);
|
|
9234
|
+
const sourceRect = placement.rectByNodeId.get(edge.sourceId);
|
|
9235
|
+
const targetRect = placement.rectByNodeId.get(edge.targetId);
|
|
9236
|
+
const hasFlexiblePorts = (node) => {
|
|
9237
|
+
const constraints = node ? options.nodeSettings?.(node)?.portConstraints : void 0;
|
|
9238
|
+
return constraints === void 0 || constraints === "UNDEFINED" || constraints === "FREE";
|
|
9239
|
+
};
|
|
9240
|
+
const flexibleFeedbackLabel = labelPlacement === "CENTER" && inlineLabel && expanded.orientation.reversedEdgeIds.has(edge.id) && hasFlexiblePorts(sourceNode) && hasFlexiblePorts(targetNode) && sourceRect !== void 0 && targetRect !== void 0;
|
|
9241
|
+
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 explicitLabelPosition = antiparallelLabelPositions.get(edge.id) ?? parallelLabelPositions.get(edge.id);
|
|
9243
|
+
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
|
+
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;
|
|
8590
9245
|
return {
|
|
8591
9246
|
...edge,
|
|
8592
9247
|
x,
|