@statelyai/layout 0.0.1 → 0.0.2
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 +10 -7
- 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-QwJn2gb2.mjs} +399 -53
- package/dist/{spore-fTgSoRLP.mjs → spore-D15xIQKj.mjs} +1 -1
- package/package.json +16 -6
- package/src/box.ts +135 -0
- package/src/elkjs/index.ts +1825 -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 +2138 -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 +405 -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 +5343 -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
|
@@ -1721,6 +1721,14 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1721
1721
|
input.settings["compaction.postCompaction.constraints"];
|
|
1722
1722
|
if (strategy === "NONE") return placement;
|
|
1723
1723
|
const rects = placement.rectByNodeId;
|
|
1724
|
+
const originalEndpointsByEdgeId = new Map([...routes?.pointsByEdgeId ?? []].flatMap(([edgeId, points]) => {
|
|
1725
|
+
const start = points[0];
|
|
1726
|
+
const end = points.at(-1);
|
|
1727
|
+
return start && end ? [[edgeId, {
|
|
1728
|
+
start: { ...start },
|
|
1729
|
+
end: { ...end }
|
|
1730
|
+
}]] : [];
|
|
1731
|
+
}));
|
|
1724
1732
|
const compactables = [...rects].map(([id, rect]) => {
|
|
1725
1733
|
const longEdgeDummy = id.startsWith("__layout_dummy:") && rect.width === 0 && rect.height === 0;
|
|
1726
1734
|
const incidentEdge = longEdgeDummy ? input.graph.edges.find((edge) => edge.sourceId === id || edge.targetId === id) : void 0;
|
|
@@ -1736,6 +1744,7 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1736
1744
|
};
|
|
1737
1745
|
});
|
|
1738
1746
|
if (routes) for (const [edgeId, readonlyPoints] of routes.pointsByEdgeId) {
|
|
1747
|
+
if (routes.outsideFeedbackEdgeIds?.has(edgeId)) continue;
|
|
1739
1748
|
const points = readonlyPoints;
|
|
1740
1749
|
for (let index = 0; index + 1 < points.length; index++) {
|
|
1741
1750
|
const first = points[index];
|
|
@@ -1756,7 +1765,10 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1756
1765
|
}
|
|
1757
1766
|
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
1767
|
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);
|
|
1768
|
+
const centerLabeledEdgeIds = new Set(input.graph.edges.filter((edge) => (edge.width ?? 0) > 0 && (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER").map((edge) => edge.id));
|
|
1769
|
+
const labelFlowLocked = new Set(compactables.filter((item) => item.kind === "segment" && item.edgeId !== void 0 && centerLabeledEdgeIds.has(item.edgeId)).map((item) => item.id));
|
|
1759
1770
|
const compact$1 = (direction, locked = /* @__PURE__ */ new Set()) => {
|
|
1771
|
+
locked = new Set([...labelFlowLocked, ...locked]);
|
|
1760
1772
|
const nodes = compactables.map((item) => ({
|
|
1761
1773
|
...item,
|
|
1762
1774
|
x: direction === "RIGHT" ? -item.x - item.width : item.x,
|
|
@@ -1823,6 +1835,7 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1823
1835
|
for (const node of input.graph.nodes) {
|
|
1824
1836
|
const targets = outgoing.get(node.id) ?? [];
|
|
1825
1837
|
if (targets.length <= (incomingDegree.get(node.id) ?? 0) || targets.length === 0) continue;
|
|
1838
|
+
if (input.graph.edges.some((edge) => edge.sourceId === node.id && centerLabeledEdgeIds.has(edge.id))) continue;
|
|
1826
1839
|
const item = itemByNodeId.get(node.id);
|
|
1827
1840
|
if (!item) continue;
|
|
1828
1841
|
const upper = Math.min(...targets.map((targetId) => (itemByNodeId.get(targetId)?.x ?? item.x) - item.width - input.spacing.node));
|
|
@@ -1830,6 +1843,49 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1830
1843
|
}
|
|
1831
1844
|
}
|
|
1832
1845
|
}
|
|
1846
|
+
if (strategy === "EDGE_LENGTH" && input.settings["nodePlacement.favorStraightEdges"] === true && (input.direction === "down" || input.direction === "up")) {
|
|
1847
|
+
const itemByNodeId = new Map(compactables.flatMap((item) => item.kind === "node" && item.nodeId ? [[item.nodeId, item]] : []));
|
|
1848
|
+
const incomingDegree = new Map(input.graph.nodes.map((node) => [node.id, 0]));
|
|
1849
|
+
for (const edge of input.graph.edges) if (edge.sourceId !== edge.targetId) incomingDegree.set(edge.targetId, (incomingDegree.get(edge.targetId) ?? 0) + 1);
|
|
1850
|
+
for (const edge of input.graph.edges) {
|
|
1851
|
+
if (edge.sourceId === edge.targetId || (incomingDegree.get(edge.targetId) ?? 0) !== 1) continue;
|
|
1852
|
+
const endpoints$2 = originalEndpointsByEdgeId.get(edge.id);
|
|
1853
|
+
const source = itemByNodeId.get(edge.sourceId);
|
|
1854
|
+
const target = itemByNodeId.get(edge.targetId);
|
|
1855
|
+
if (!endpoints$2 || !source || !target) continue;
|
|
1856
|
+
if (!(input.direction === "down" ? endpoints$2.start.y < endpoints$2.end.y : endpoints$2.start.y > endpoints$2.end.y)) continue;
|
|
1857
|
+
const desiredTargetX = source.x + (endpoints$2.start.x - source.originalX) - (endpoints$2.end.x - target.originalX);
|
|
1858
|
+
if (compactables.some((candidate) => {
|
|
1859
|
+
if (candidate.kind !== "node" || candidate === target) return false;
|
|
1860
|
+
if (!(candidate.y < target.y + target.height && candidate.y + candidate.height > target.y)) return false;
|
|
1861
|
+
const spacing = input.spacing.node;
|
|
1862
|
+
return desiredTargetX < candidate.x + candidate.width + spacing && desiredTargetX + target.width + spacing > candidate.x;
|
|
1863
|
+
})) continue;
|
|
1864
|
+
target.x = desiredTargetX;
|
|
1865
|
+
const alignedX = source.x + (endpoints$2.start.x - source.originalX);
|
|
1866
|
+
for (const segment of compactables) if (segment.kind === "segment" && segment.edgeId === edge.id) segment.x = alignedX;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (input.direction === "right" && centerLabeledEdgeIds.size > 0) {
|
|
1870
|
+
const itemByNodeId = new Map(compactables.flatMap((item) => item.kind === "node" && item.nodeId ? [[item.nodeId, item]] : []));
|
|
1871
|
+
const edgeNodeSpacing = Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10);
|
|
1872
|
+
for (const edge of input.graph.edges) {
|
|
1873
|
+
if (!centerLabeledEdgeIds.has(edge.id)) continue;
|
|
1874
|
+
const source = itemByNodeId.get(edge.sourceId);
|
|
1875
|
+
const target = itemByNodeId.get(edge.targetId);
|
|
1876
|
+
const originalEndpoints = originalEndpointsByEdgeId.get(edge.id);
|
|
1877
|
+
if (!source || !target || !originalEndpoints || originalEndpoints.start.x >= originalEndpoints.end.x) continue;
|
|
1878
|
+
const tracks = compactables.filter((item) => item.kind === "segment" && item.edgeId === edge.id);
|
|
1879
|
+
const track = tracks.length > 0 ? Math.max(...tracks.map((item) => item.x)) : void 0;
|
|
1880
|
+
const targetNode = input.graph.nodes.find((node) => node.id === edge.targetId);
|
|
1881
|
+
const targetPort = targetNode?.ports?.find((port) => port.name === edge.targetPort);
|
|
1882
|
+
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);
|
|
1883
|
+
else {
|
|
1884
|
+
const routeStart = track !== void 0 ? track + edgeNodeSpacing : source.x + source.width + input.spacing.layer;
|
|
1885
|
+
target.x = Math.max(target.x, routeStart + (edge.width ?? 0) + input.spacing.layer);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1833
1889
|
const offset = input.padding.left - Math.min(...compactables.map((item) => item.x));
|
|
1834
1890
|
for (const item of compactables) item.x += offset;
|
|
1835
1891
|
const nodeDeltaById = /* @__PURE__ */ new Map();
|
|
@@ -1846,14 +1902,71 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1846
1902
|
item.points[1].x += delta;
|
|
1847
1903
|
}
|
|
1848
1904
|
if (routes) {
|
|
1905
|
+
const edgeRouting = input.settings.edgeRouting ?? "ORTHOGONAL";
|
|
1906
|
+
const compactedSplineControls = routes.splineNubControlsByEdgeId ? new Map(routes.splineNubControlsByEdgeId) : void 0;
|
|
1849
1907
|
const edgeById = new Map(input.graph.edges.map((edge) => [edge.id, edge]));
|
|
1850
1908
|
for (const [edgeId, readonlyPoints] of routes.pointsByEdgeId) {
|
|
1851
|
-
const points = readonlyPoints;
|
|
1909
|
+
const points = [...readonlyPoints];
|
|
1852
1910
|
const edge = edgeById.get(edgeId);
|
|
1853
1911
|
if (!edge || points.length === 0) continue;
|
|
1854
|
-
|
|
1855
|
-
|
|
1912
|
+
const originalEndpoints = originalEndpointsByEdgeId.get(edgeId);
|
|
1913
|
+
if (originalEndpoints) {
|
|
1914
|
+
points[0] = {
|
|
1915
|
+
...originalEndpoints.start,
|
|
1916
|
+
x: originalEndpoints.start.x + (nodeDeltaById.get(edge.sourceId) ?? 0)
|
|
1917
|
+
};
|
|
1918
|
+
points[points.length - 1] = {
|
|
1919
|
+
...originalEndpoints.end,
|
|
1920
|
+
x: originalEndpoints.end.x + (nodeDeltaById.get(edge.targetId) ?? 0)
|
|
1921
|
+
};
|
|
1922
|
+
if (routes.outsideFeedbackEdgeIds?.has(edgeId)) {
|
|
1923
|
+
const horizontal = input.direction === "left" || input.direction === "right";
|
|
1924
|
+
const startDelta = points[0].x - originalEndpoints.start.x;
|
|
1925
|
+
const endDelta = points.at(-1).x - originalEndpoints.end.x;
|
|
1926
|
+
if (horizontal && points.length >= 6) {
|
|
1927
|
+
for (const index of [1, 2]) points[index] = {
|
|
1928
|
+
...points[index],
|
|
1929
|
+
x: points[index].x + startDelta
|
|
1930
|
+
};
|
|
1931
|
+
for (const index of [points.length - 3, points.length - 2]) points[index] = {
|
|
1932
|
+
...points[index],
|
|
1933
|
+
x: points[index].x + endDelta
|
|
1934
|
+
};
|
|
1935
|
+
} else if (!horizontal && points.length >= 4) {
|
|
1936
|
+
points[1] = {
|
|
1937
|
+
...points[1],
|
|
1938
|
+
x: points[1].x + startDelta
|
|
1939
|
+
};
|
|
1940
|
+
points[points.length - 2] = {
|
|
1941
|
+
...points[points.length - 2],
|
|
1942
|
+
x: points[points.length - 2].x + endDelta
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
let compactedPoints = points;
|
|
1948
|
+
if (edgeRouting === "ORTHOGONAL") {
|
|
1949
|
+
const horizontal = input.direction === "left" || input.direction === "right";
|
|
1950
|
+
const orthogonal = [];
|
|
1951
|
+
for (const [index, point] of points.entries()) {
|
|
1952
|
+
const previous = orthogonal.at(-1);
|
|
1953
|
+
if (previous && Math.abs(previous.x - point.x) > 1e-9 && Math.abs(previous.y - point.y) > 1e-9) {
|
|
1954
|
+
const approachingTarget = index === points.length - 1;
|
|
1955
|
+
orthogonal.push(horizontal === approachingTarget ? {
|
|
1956
|
+
x: previous.x,
|
|
1957
|
+
y: point.y
|
|
1958
|
+
} : {
|
|
1959
|
+
x: point.x,
|
|
1960
|
+
y: previous.y
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
orthogonal.push(point);
|
|
1964
|
+
}
|
|
1965
|
+
compactedPoints = simplifyRoute$1(orthogonal);
|
|
1966
|
+
} else if (edgeRouting === "SPLINES") compactedSplineControls?.delete(edgeId);
|
|
1967
|
+
routes.pointsByEdgeId.set(edgeId, compactedPoints);
|
|
1856
1968
|
}
|
|
1969
|
+
if (compactedSplineControls) routes.splineNubControlsByEdgeId = compactedSplineControls;
|
|
1857
1970
|
}
|
|
1858
1971
|
return placement;
|
|
1859
1972
|
}
|
|
@@ -2046,8 +2159,16 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2046
2159
|
const forward = input.settings["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT" || sourceFlow <= targetFlow;
|
|
2047
2160
|
const feedback = input.settings.feedbackEdges === true && orientation?.reversedEdgeIds.has(edge.id) === true;
|
|
2048
2161
|
const directionReversed = input.direction === "left" || input.direction === "up";
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2162
|
+
const endpointSide = (nodeId, portName) => {
|
|
2163
|
+
const node = nodeById.get(nodeId);
|
|
2164
|
+
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
2165
|
+
const constraints = node ? input.nodeSettings?.(node)?.portConstraints : void 0;
|
|
2166
|
+
if (!node || !port || constraints !== "FIXED_SIDE" && constraints !== "FIXED_ORDER" && constraints !== "FIXED_RATIO" && constraints !== "FIXED_POS") return;
|
|
2167
|
+
const configured = input.portSettings?.(port, node)?.["port.side"];
|
|
2168
|
+
return configured === "WEST" || configured === "NORTH" ? "before" : configured === "EAST" || configured === "SOUTH" ? "after" : void 0;
|
|
2169
|
+
};
|
|
2170
|
+
const sourceSide = endpointSide(edge.sourceId, edge.sourcePort) ?? (feedback ? directionReversed ? "before" : "after" : forward ? "after" : "before");
|
|
2171
|
+
const targetSide = endpointSide(edge.targetId, edge.targetPort) ?? (feedback ? directionReversed ? "after" : "before" : forward ? "before" : "after");
|
|
2051
2172
|
const sourceKey = `${edge.sourceId}:${sourceSide}`;
|
|
2052
2173
|
const targetKey = `${edge.targetId}:${targetSide}`;
|
|
2053
2174
|
const sourceGroup = groups.get(sourceKey) ?? [];
|
|
@@ -2169,8 +2290,14 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2169
2290
|
function routeEdges(style) {
|
|
2170
2291
|
return (input, orientation, placement) => {
|
|
2171
2292
|
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
2293
|
+
const hasZeroFixedSideTarget = (edge) => {
|
|
2294
|
+
const target = nodeById.get(edge.targetId);
|
|
2295
|
+
const port = target?.ports?.find((candidate) => candidate.name === edge.targetPort);
|
|
2296
|
+
return target !== void 0 && port !== void 0 && (port.width ?? 8) === 0 && (port.height ?? 8) === 0 && input.nodeSettings?.(target)?.portConstraints === "FIXED_SIDE";
|
|
2297
|
+
};
|
|
2172
2298
|
const pointsByEdgeId = /* @__PURE__ */ new Map();
|
|
2173
2299
|
const splineNubControlsByEdgeId = /* @__PURE__ */ new Map();
|
|
2300
|
+
const outsideFeedbackEdgeIds = /* @__PURE__ */ new Set();
|
|
2174
2301
|
const horizontal = input.direction === "left" || input.direction === "right";
|
|
2175
2302
|
const reverse = input.direction === "up" || input.direction === "left";
|
|
2176
2303
|
const mutableRects = placement.rectByNodeId;
|
|
@@ -2181,6 +2308,35 @@ function routeEdges(style) {
|
|
|
2181
2308
|
loops.push(edge);
|
|
2182
2309
|
selfLoopsByNodeId.set(edge.sourceId, loops);
|
|
2183
2310
|
}
|
|
2311
|
+
const selfLoopEntriesByX = [...selfLoopsByNodeId].sort(([leftId], [rightId]) => (mutableRects.get(leftId)?.x ?? 0) - (mutableRects.get(rightId)?.x ?? 0));
|
|
2312
|
+
for (const [id, loops] of selfLoopEntriesByX) {
|
|
2313
|
+
const rect = mutableRects.get(id);
|
|
2314
|
+
const node = nodeById.get(id);
|
|
2315
|
+
if (!rect || !node) continue;
|
|
2316
|
+
const eastLoops = loops.filter((edge) => {
|
|
2317
|
+
const sourcePort = node.ports?.find((port) => port.name === edge.sourcePort);
|
|
2318
|
+
const targetPort = node.ports?.find((port) => port.name === edge.targetPort);
|
|
2319
|
+
return sourcePort !== void 0 && targetPort !== void 0 && input.portSettings?.(sourcePort, node)?.["port.side"] === "EAST" && input.portSettings?.(targetPort, node)?.["port.side"] === "EAST";
|
|
2320
|
+
});
|
|
2321
|
+
if (eastLoops.length === 0) continue;
|
|
2322
|
+
const selfLoopSpacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
2323
|
+
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
2324
|
+
const exteriorWidth = selfLoopSpacing + eastLoops.reduce((sum, edge) => sum + (edge.width ?? 0) + labelSpacing, 1);
|
|
2325
|
+
const nodeRight = rect.x + rect.width;
|
|
2326
|
+
const rightCandidates = [...mutableRects.entries()].filter(([candidateId, candidateRect]) => candidateId !== id && candidateRect.x >= nodeRight - 1e-9);
|
|
2327
|
+
if (rightCandidates.length === 0) continue;
|
|
2328
|
+
const nextX = Math.min(...rightCandidates.map(([, candidateRect]) => candidateRect.x));
|
|
2329
|
+
const shift = nodeRight + exteriorWidth + input.spacing.node - nextX;
|
|
2330
|
+
if (shift <= 0) continue;
|
|
2331
|
+
for (const [candidateId, candidateRect] of mutableRects) {
|
|
2332
|
+
if (candidateId === id || candidateRect.x < nextX - 1e-9) continue;
|
|
2333
|
+
mutableRects.set(candidateId, {
|
|
2334
|
+
...candidateRect,
|
|
2335
|
+
x: candidateRect.x + shift
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
const northReserveByLayer = /* @__PURE__ */ new Map();
|
|
2184
2340
|
for (const [id, loops] of selfLoopsByNodeId) {
|
|
2185
2341
|
const rect = mutableRects.get(id);
|
|
2186
2342
|
if (!rect) continue;
|
|
@@ -2198,12 +2354,17 @@ function routeEdges(style) {
|
|
|
2198
2354
|
else {
|
|
2199
2355
|
const sideLoopCount = distribution === "NORTH_SOUTH" ? Math.ceil(loops.length / 2) : loops.length;
|
|
2200
2356
|
const reserve = (ordering === "SEQUENCED" ? Math.min(1, sideLoopCount) : sideLoopCount) * spacing + splineOffset;
|
|
2201
|
-
|
|
2202
|
-
...rect,
|
|
2203
|
-
y: rect.y + reserve
|
|
2204
|
-
});
|
|
2357
|
+
northReserveByLayer.set(rect.y, Math.max(northReserveByLayer.get(rect.y) ?? 0, reserve));
|
|
2205
2358
|
}
|
|
2206
2359
|
}
|
|
2360
|
+
const northReserves = [...northReserveByLayer].sort(([left], [right]) => left - right);
|
|
2361
|
+
for (const [candidateId, candidateRect] of mutableRects) {
|
|
2362
|
+
const reserve = northReserves.reduce((total, [layerY, layerReserve]) => candidateRect.y + 1e-9 >= layerY ? total + layerReserve : total, 0);
|
|
2363
|
+
if (reserve > 0) mutableRects.set(candidateId, {
|
|
2364
|
+
...candidateRect,
|
|
2365
|
+
y: candidateRect.y + reserve
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2207
2368
|
const edgeLabelSideSelection = input.settings["edgeLabels.sideSelection"] ?? "SMART_DOWN";
|
|
2208
2369
|
if (edgeLabelSideSelection === "ALWAYS_UP" || edgeLabelSideSelection === "SMART_UP" || edgeLabelSideSelection === "DIRECTION_UP") {
|
|
2209
2370
|
let crossShift = 0;
|
|
@@ -2243,12 +2404,13 @@ function routeEdges(style) {
|
|
|
2243
2404
|
}
|
|
2244
2405
|
const labelExtraByGap = flowLayers.slice(0, -1).map(() => 0);
|
|
2245
2406
|
for (const edge of input.graph.edges) {
|
|
2246
|
-
|
|
2407
|
+
const labelFlowSize = horizontal ? edge.width ?? 0 : edge.height ?? 0;
|
|
2408
|
+
if (labelFlowSize <= 0) continue;
|
|
2247
2409
|
const sourceLayer = flowLayerByNodeId.get(edge.sourceId);
|
|
2248
2410
|
const targetLayer = flowLayerByNodeId.get(edge.targetId);
|
|
2249
2411
|
if (sourceLayer === void 0 || targetLayer === void 0) continue;
|
|
2250
2412
|
if (Math.abs(sourceLayer - targetLayer) !== 1) continue;
|
|
2251
|
-
const extra = (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER" ?
|
|
2413
|
+
const extra = (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER" ? labelFlowSize + input.spacing.layer : labelFlowSize + Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
2252
2414
|
const gap = Math.min(sourceLayer, targetLayer);
|
|
2253
2415
|
labelExtraByGap[gap] = Math.max(labelExtraByGap[gap] ?? 0, extra);
|
|
2254
2416
|
}
|
|
@@ -2493,13 +2655,15 @@ function routeEdges(style) {
|
|
|
2493
2655
|
bounds.start = nextStart;
|
|
2494
2656
|
bounds.end = nextStart + size;
|
|
2495
2657
|
const slots = slotsByGap[layerNo] ?? 0;
|
|
2496
|
-
const
|
|
2658
|
+
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);
|
|
2659
|
+
const preservedGap = (existingGapByLayer[layerNo] ?? input.spacing.layer) - (routesNearTarget ? edgeEdgeSpacing : 0);
|
|
2660
|
+
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
2661
|
nextStart = bounds.end + gapSpacing;
|
|
2498
2662
|
}
|
|
2499
2663
|
implicitEndpoints = implicitEdgeEndpoints(input, placement, orientation);
|
|
2500
2664
|
for (const [gap, candidates] of candidatesByGap.entries()) for (const candidate of candidates) {
|
|
2501
2665
|
if (candidate.straight) continue;
|
|
2502
|
-
const firstTrack = (flowLayers[gap]?.end ?? 0) + edgeNodeSpacing + (candidate.slot ?? 0) * edgeEdgeSpacing;
|
|
2666
|
+
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
2667
|
if (candidate.secondSlot !== void 0 && candidate.crossover !== void 0) orthogonalDetourByEdgeId.set(candidate.edge.id, {
|
|
2504
2668
|
firstTrack,
|
|
2505
2669
|
secondTrack: (flowLayers[gap]?.end ?? 0) + edgeNodeSpacing + candidate.secondSlot * edgeEdgeSpacing,
|
|
@@ -2663,7 +2827,7 @@ function routeEdges(style) {
|
|
|
2663
2827
|
}] : [];
|
|
2664
2828
|
});
|
|
2665
2829
|
const incoming = segments.map(() => 0);
|
|
2666
|
-
for (const dependency of acyclicDependencies) incoming[dependency.target]
|
|
2830
|
+
for (const dependency of acyclicDependencies) incoming[dependency.target] = (incoming[dependency.target] ?? 0) + 1;
|
|
2667
2831
|
const queue = incoming.flatMap((count, index) => count === 0 ? [index] : []);
|
|
2668
2832
|
const rank = segments.map(() => 0);
|
|
2669
2833
|
for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
|
|
@@ -2745,67 +2909,88 @@ function routeEdges(style) {
|
|
|
2745
2909
|
const sourceRect = placement.rectByNodeId.get(edge.sourceId);
|
|
2746
2910
|
const targetRect = placement.rectByNodeId.get(edge.targetId);
|
|
2747
2911
|
if (!source || !target || !sourceRect || !targetRect) continue;
|
|
2748
|
-
|
|
2749
|
-
|
|
2912
|
+
const reversedEdge = orientation.reversedEdgeIds.has(edge.id);
|
|
2913
|
+
const feedbackSourcePort = source.ports?.find((port) => port.name === edge.sourcePort);
|
|
2914
|
+
const feedbackTargetPort = target.ports?.find((port) => port.name === edge.targetPort);
|
|
2915
|
+
const feedbackSourcePortSide = feedbackSourcePort ? input.portSettings?.(feedbackSourcePort, source)?.["port.side"] : void 0;
|
|
2916
|
+
const feedbackTargetPortSide = feedbackTargetPort ? input.portSettings?.(feedbackTargetPort, target)?.["port.side"] : void 0;
|
|
2917
|
+
const sourceAwaySide = horizontal ? sourceRect.x < targetRect.x ? "WEST" : "EAST" : sourceRect.y < targetRect.y ? "NORTH" : "SOUTH";
|
|
2918
|
+
const targetAwaySide = horizontal ? sourceRect.x < targetRect.x ? "EAST" : "WEST" : sourceRect.y < targetRect.y ? "SOUTH" : "NORTH";
|
|
2919
|
+
const sameSideSelfLoop = source.id === target.id && feedbackSourcePortSide !== void 0 && feedbackSourcePortSide === feedbackTargetPortSide;
|
|
2920
|
+
const fixedSideFeedback = style === "ORTHOGONAL" && !sameSideSelfLoop && (feedbackSourcePortSide === sourceAwaySide || feedbackTargetPortSide === targetAwaySide);
|
|
2921
|
+
if (input.settings.feedbackEdges === true && reversedEdge || fixedSideFeedback) {
|
|
2922
|
+
if (fixedSideFeedback) outsideFeedbackEdgeIds.add(edge.id);
|
|
2923
|
+
const crossSpacing = Number(input.settings["spacing.edgeNode"] ?? 10);
|
|
2924
|
+
const flowSpacing = fixedSideFeedback ? Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10) + (source.id === target.id ? Number(input.settings["spacing.nodeSelfLoop"] ?? 10) : 0) : crossSpacing;
|
|
2925
|
+
const sourceFallback$1 = implicitEndpoints.get(edge.id)?.source ?? {
|
|
2926
|
+
x: sourceRect.x + sourceRect.width / 2,
|
|
2927
|
+
y: sourceRect.y + sourceRect.height / 2
|
|
2928
|
+
};
|
|
2929
|
+
const targetFallback$1 = implicitEndpoints.get(edge.id)?.target ?? {
|
|
2930
|
+
x: targetRect.x + targetRect.width / 2,
|
|
2931
|
+
y: targetRect.y + targetRect.height / 2
|
|
2932
|
+
};
|
|
2933
|
+
const start$1 = fixedSideFeedback ? getPortPoint(source, edge.sourcePort, sourceRect, sourceFallback$1, input.direction, input) : sourceFallback$1;
|
|
2934
|
+
const end$1 = fixedSideFeedback ? getPortPoint(target, edge.targetPort, targetRect, targetFallback$1, input.direction, input) : targetFallback$1;
|
|
2750
2935
|
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;
|
|
2936
|
+
const sign = fixedSideFeedback ? Math.sign(sourceRect.x - targetRect.x) || 1 : reverse ? -1 : 1;
|
|
2937
|
+
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.y + rect.height)) + crossSpacing + (fixedSideFeedback ? .5 : 0);
|
|
2938
|
+
const preserveCenters = fixedSideFeedback && input.settings.unnecessaryBendpoints === true;
|
|
2761
2939
|
pointsByEdgeId.set(edge.id, [
|
|
2762
2940
|
start$1,
|
|
2763
2941
|
{
|
|
2764
|
-
x: start$1.x + sign *
|
|
2942
|
+
x: start$1.x + sign * flowSpacing,
|
|
2765
2943
|
y: start$1.y
|
|
2766
2944
|
},
|
|
2767
2945
|
{
|
|
2768
|
-
x: start$1.x + sign *
|
|
2946
|
+
x: start$1.x + sign * flowSpacing,
|
|
2769
2947
|
y: outerCross
|
|
2770
2948
|
},
|
|
2949
|
+
...preserveCenters ? [{
|
|
2950
|
+
x: sourceRect.x + sourceRect.width / 2,
|
|
2951
|
+
y: outerCross
|
|
2952
|
+
}, {
|
|
2953
|
+
x: targetRect.x + targetRect.width / 2,
|
|
2954
|
+
y: outerCross
|
|
2955
|
+
}] : [],
|
|
2771
2956
|
{
|
|
2772
|
-
x: end$1.x - sign *
|
|
2957
|
+
x: end$1.x - sign * flowSpacing,
|
|
2773
2958
|
y: outerCross
|
|
2774
2959
|
},
|
|
2775
2960
|
{
|
|
2776
|
-
x: end$1.x - sign *
|
|
2961
|
+
x: end$1.x - sign * flowSpacing,
|
|
2777
2962
|
y: end$1.y
|
|
2778
2963
|
},
|
|
2779
2964
|
end$1
|
|
2780
2965
|
]);
|
|
2781
2966
|
} 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;
|
|
2967
|
+
const sign = fixedSideFeedback ? Math.sign(sourceRect.y - targetRect.y) || 1 : reverse ? -1 : 1;
|
|
2968
|
+
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.x + rect.width)) + crossSpacing + (fixedSideFeedback ? .5 : 0);
|
|
2969
|
+
const preserveCenters = fixedSideFeedback && input.settings.unnecessaryBendpoints === true;
|
|
2792
2970
|
pointsByEdgeId.set(edge.id, [
|
|
2793
2971
|
start$1,
|
|
2794
2972
|
{
|
|
2795
2973
|
x: start$1.x,
|
|
2796
|
-
y: start$1.y + sign *
|
|
2974
|
+
y: start$1.y + sign * flowSpacing
|
|
2797
2975
|
},
|
|
2798
2976
|
{
|
|
2799
2977
|
x: outerCross,
|
|
2800
|
-
y: start$1.y + sign *
|
|
2978
|
+
y: start$1.y + sign * flowSpacing
|
|
2801
2979
|
},
|
|
2980
|
+
...preserveCenters ? [{
|
|
2981
|
+
x: outerCross,
|
|
2982
|
+
y: sourceRect.y + sourceRect.height / 2
|
|
2983
|
+
}, {
|
|
2984
|
+
x: outerCross,
|
|
2985
|
+
y: targetRect.y + targetRect.height / 2
|
|
2986
|
+
}] : [],
|
|
2802
2987
|
{
|
|
2803
2988
|
x: outerCross,
|
|
2804
|
-
y: end$1.y - sign *
|
|
2989
|
+
y: end$1.y - sign * flowSpacing
|
|
2805
2990
|
},
|
|
2806
2991
|
{
|
|
2807
2992
|
x: end$1.x,
|
|
2808
|
-
y: end$1.y - sign *
|
|
2993
|
+
y: end$1.y - sign * flowSpacing
|
|
2809
2994
|
},
|
|
2810
2995
|
end$1
|
|
2811
2996
|
]);
|
|
@@ -2870,6 +3055,75 @@ function routeEdges(style) {
|
|
|
2870
3055
|
const distribution = nodeSettings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
2871
3056
|
const ordering = nodeSettings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
2872
3057
|
if (style === "ORTHOGONAL") {
|
|
3058
|
+
if (sameSideSelfLoop) {
|
|
3059
|
+
const endpoints$2 = implicitEndpoints.get(edge.id);
|
|
3060
|
+
const start$2 = getPortPoint(source, edge.sourcePort, sourceRect, endpoints$2?.source ?? {
|
|
3061
|
+
x: sourceRect.x + sourceRect.width / 2,
|
|
3062
|
+
y: sourceRect.y + sourceRect.height / 2
|
|
3063
|
+
}, input.direction, input);
|
|
3064
|
+
const end$2 = getPortPoint(target, edge.targetPort, targetRect, endpoints$2?.target ?? {
|
|
3065
|
+
x: targetRect.x + targetRect.width / 2,
|
|
3066
|
+
y: targetRect.y + targetRect.height / 2
|
|
3067
|
+
}, input.direction, input);
|
|
3068
|
+
const side = feedbackSourcePortSide;
|
|
3069
|
+
const sameSideLoops = loops.filter((candidate) => {
|
|
3070
|
+
const candidateSourcePort = source.ports?.find((port) => port.name === candidate.sourcePort);
|
|
3071
|
+
const candidateTargetPort = source.ports?.find((port) => port.name === candidate.targetPort);
|
|
3072
|
+
const candidateSourceSide = candidateSourcePort ? input.portSettings?.(candidateSourcePort, source)?.["port.side"] : void 0;
|
|
3073
|
+
const candidateTargetSide = candidateTargetPort ? input.portSettings?.(candidateTargetPort, source)?.["port.side"] : void 0;
|
|
3074
|
+
return candidateSourceSide === side && candidateTargetSide === side;
|
|
3075
|
+
});
|
|
3076
|
+
const sameSideLoopIndex = sameSideLoops.findIndex((candidate) => candidate.id === edge.id);
|
|
3077
|
+
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
3078
|
+
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);
|
|
3079
|
+
if (side === "EAST" || side === "WEST") {
|
|
3080
|
+
const nodeRects = [...placement.rectByNodeId.values()];
|
|
3081
|
+
const minimumNodeX = Math.min(...nodeRects.map((rect) => rect.x));
|
|
3082
|
+
const maximumNodeX = Math.max(...nodeRects.map((rect) => rect.x + rect.width));
|
|
3083
|
+
const maximumNodeY = Math.max(...nodeRects.map((rect) => rect.y + rect.height));
|
|
3084
|
+
const precedingLabelHeight = sameSideLoops.slice(0, Math.max(0, sameSideLoopIndex)).reduce((extent$1, candidate) => extent$1 + (candidate.height ?? 0) + labelSpacing, 0);
|
|
3085
|
+
const nearTrack = side === "EAST" ? sourceRect.x + sourceRect.width + spacing : sourceRect.x - spacing;
|
|
3086
|
+
const farTrack = side === "EAST" ? maximumNodeX + trackDistance + (edge.width ?? 0) : minimumNodeX - trackDistance - (edge.width ?? 0);
|
|
3087
|
+
const exteriorY = maximumNodeY + spacing + precedingLabelHeight + (edge.height ?? 0) / 2 + .5;
|
|
3088
|
+
pointsByEdgeId.set(edge.id, [
|
|
3089
|
+
start$2,
|
|
3090
|
+
{
|
|
3091
|
+
x: nearTrack,
|
|
3092
|
+
y: start$2.y
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
x: nearTrack,
|
|
3096
|
+
y: exteriorY
|
|
3097
|
+
},
|
|
3098
|
+
{
|
|
3099
|
+
x: farTrack,
|
|
3100
|
+
y: exteriorY
|
|
3101
|
+
},
|
|
3102
|
+
{
|
|
3103
|
+
x: farTrack,
|
|
3104
|
+
y: end$2.y
|
|
3105
|
+
},
|
|
3106
|
+
end$2
|
|
3107
|
+
]);
|
|
3108
|
+
outsideFeedbackEdgeIds.add(edge.id);
|
|
3109
|
+
continue;
|
|
3110
|
+
}
|
|
3111
|
+
const track$1 = side === "SOUTH" ? sourceRect.y + sourceRect.height + trackDistance : sourceRect.y - trackDistance;
|
|
3112
|
+
pointsByEdgeId.set(edge.id, [
|
|
3113
|
+
start$2,
|
|
3114
|
+
{
|
|
3115
|
+
x: start$2.x,
|
|
3116
|
+
y: track$1
|
|
3117
|
+
},
|
|
3118
|
+
{
|
|
3119
|
+
x: end$2.x,
|
|
3120
|
+
y: track$1
|
|
3121
|
+
},
|
|
3122
|
+
end$2
|
|
3123
|
+
]);
|
|
3124
|
+
outsideFeedbackEdgeIds.add(edge.id);
|
|
3125
|
+
continue;
|
|
3126
|
+
}
|
|
2873
3127
|
const routeHorizontalSide = (side, indexOnSide, countOnSide) => {
|
|
2874
3128
|
const denominator = countOnSide * 2 + 1;
|
|
2875
3129
|
const sequenced = ordering === "SEQUENCED";
|
|
@@ -3035,8 +3289,15 @@ function routeEdges(style) {
|
|
|
3035
3289
|
x: targetRect.x + targetRect.width / 2,
|
|
3036
3290
|
y: targetRect.y + (reverse ? targetRect.height : 0)
|
|
3037
3291
|
});
|
|
3038
|
-
const
|
|
3039
|
-
const
|
|
3292
|
+
const sourcePort = source.ports?.find((port) => port.name === edge.sourcePort);
|
|
3293
|
+
const targetPort = target.ports?.find((port) => port.name === edge.targetPort);
|
|
3294
|
+
const sourcePortSide = sourcePort ? input.portSettings?.(sourcePort, source)?.["port.side"] : void 0;
|
|
3295
|
+
const targetPortSide = targetPort ? input.portSettings?.(targetPort, target)?.["port.side"] : void 0;
|
|
3296
|
+
const fallbackMatchesPortSide = (fallback, rect, side) => side === void 0 || side === "UNDEFINED" || side === "EAST" && Math.abs(fallback.x - rect.x - rect.width) < 1e-9 || side === "WEST" && Math.abs(fallback.x - rect.x) < 1e-9 || side === "SOUTH" && Math.abs(fallback.y - rect.y - rect.height) < 1e-9 || side === "NORTH" && Math.abs(fallback.y - rect.y) < 1e-9;
|
|
3297
|
+
const sourceFixedSide = sourcePort !== void 0 && fallbackMatchesPortSide(sourceFallback, sourceRect, sourcePortSide) && (sourcePort.width ?? 8) === 0 && (sourcePort.height ?? 8) === 0 && input.nodeSettings?.(source)?.portConstraints === "FIXED_SIDE" && input.graph.edges.filter((candidate) => candidate.sourceId === edge.sourceId && candidate.sourcePort === edge.sourcePort).length === 1;
|
|
3298
|
+
const targetFixedSide = targetPort !== void 0 && fallbackMatchesPortSide(targetFallback, targetRect, targetPortSide) && (targetPort.width ?? 8) === 0 && (targetPort.height ?? 8) === 0 && input.nodeSettings?.(target)?.portConstraints === "FIXED_SIDE" && input.graph.edges.filter((candidate) => candidate.targetId === edge.targetId && candidate.targetPort === edge.targetPort).length === 1;
|
|
3299
|
+
const start = sourceFixedSide ? sourceFallback : getPortPoint(source, edge.sourcePort, sourceRect, sourceFallback, input.direction, input);
|
|
3300
|
+
const end = targetFixedSide ? targetFallback : getPortPoint(target, edge.targetPort, targetRect, targetFallback, input.direction, input);
|
|
3040
3301
|
const sourceLayer = flowLayerByNodeId.get(edge.sourceId) ?? 0;
|
|
3041
3302
|
const targetLayer = flowLayerByNodeId.get(edge.targetId) ?? 0;
|
|
3042
3303
|
const earlierLayer = Math.min(sourceLayer, targetLayer);
|
|
@@ -3181,7 +3442,8 @@ function routeEdges(style) {
|
|
|
3181
3442
|
}
|
|
3182
3443
|
return {
|
|
3183
3444
|
pointsByEdgeId,
|
|
3184
|
-
splineNubControlsByEdgeId
|
|
3445
|
+
splineNubControlsByEdgeId,
|
|
3446
|
+
outsideFeedbackEdgeIds
|
|
3185
3447
|
};
|
|
3186
3448
|
};
|
|
3187
3449
|
}
|
|
@@ -3616,7 +3878,7 @@ function normalizeAndBalance(nodes, previousLayerCounts) {
|
|
|
3616
3878
|
}
|
|
3617
3879
|
return filling;
|
|
3618
3880
|
}
|
|
3619
|
-
function runNetworkSimplex(nodes,
|
|
3881
|
+
function runNetworkSimplex(nodes, _edges, iterationLimit, previousLayerCounts, balance = true) {
|
|
3620
3882
|
const orderedEdges = nodes.flatMap((node) => node.outgoing);
|
|
3621
3883
|
orderedEdges.forEach((edge, index) => edge.order = index);
|
|
3622
3884
|
assignInitialLayers(nodes);
|
|
@@ -3890,10 +4152,21 @@ function splitLongEdges(input, orientation, assignment) {
|
|
|
3890
4152
|
const originalEdgeBySegmentId = /* @__PURE__ */ new Map();
|
|
3891
4153
|
const usedNodeIds = new Set(nodes.map((node) => node.id));
|
|
3892
4154
|
const originalNodeIds = new Set(usedNodeIds);
|
|
4155
|
+
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
4156
|
+
const forwardSourceSide = input.direction === "right" ? "EAST" : input.direction === "left" ? "WEST" : input.direction === "down" ? "SOUTH" : "NORTH";
|
|
4157
|
+
const forwardTargetSide = input.direction === "right" ? "WEST" : input.direction === "left" ? "EAST" : input.direction === "down" ? "NORTH" : "SOUTH";
|
|
3893
4158
|
for (const edge of input.graph.edges) {
|
|
3894
4159
|
const sourceLayer = layerByNodeId.get(edge.sourceId) ?? 0;
|
|
3895
4160
|
const targetLayer = layerByNodeId.get(edge.targetId) ?? 0;
|
|
3896
|
-
|
|
4161
|
+
const span = Math.abs(targetLayer - sourceLayer);
|
|
4162
|
+
const source = nodeById.get(edge.sourceId);
|
|
4163
|
+
const target = nodeById.get(edge.targetId);
|
|
4164
|
+
const sourcePort = source?.ports?.find((port) => port.name === edge.sourcePort);
|
|
4165
|
+
const targetPort = target?.ports?.find((port) => port.name === edge.targetPort);
|
|
4166
|
+
const sourceSide = source && sourcePort ? input.portSettings?.(sourcePort, source)?.["port.side"] : void 0;
|
|
4167
|
+
const targetSide = target && targetPort ? input.portSettings?.(targetPort, target)?.["port.side"] : void 0;
|
|
4168
|
+
const fixedSideFeedback = sourceLayer > targetLayer && (source !== void 0 && input.nodeSettings?.(source)?.portConstraints === "FIXED_SIDE" && sourceSide === forwardSourceSide || target !== void 0 && input.nodeSettings?.(target)?.portConstraints === "FIXED_SIDE" && targetSide === forwardTargetSide);
|
|
4169
|
+
if (span <= 1 || edge.sourceId === edge.targetId || input.settings.feedbackEdges === true && orientation.reversedEdgeIds.has(edge.id) || fixedSideFeedback) {
|
|
3897
4170
|
edges.push(edge);
|
|
3898
4171
|
originalEdgeBySegmentId.set(edge.id, edge);
|
|
3899
4172
|
if (orientation.reversedEdgeIds.has(edge.id)) reversedEdgeIds.add(edge.id);
|
|
@@ -4055,7 +4328,9 @@ function joinLongEdgeRoutes(routes, segmentIdsByEdgeId, preserveInternalDuplicat
|
|
|
4055
4328
|
return result;
|
|
4056
4329
|
};
|
|
4057
4330
|
const pointsByEdgeId = /* @__PURE__ */ new Map();
|
|
4331
|
+
const outsideFeedbackEdgeIds = /* @__PURE__ */ new Set();
|
|
4058
4332
|
for (const [edgeId, segmentIds] of segmentIdsByEdgeId) {
|
|
4333
|
+
if (segmentIds.some((segmentId) => routes.outsideFeedbackEdgeIds?.has(segmentId))) outsideFeedbackEdgeIds.add(edgeId);
|
|
4059
4334
|
if (convertLongSplines && segmentIds.length > 1) {
|
|
4060
4335
|
const segments = segmentIds.map((segmentId) => routes.pointsByEdgeId.get(segmentId) ?? []).filter((points$1) => points$1.length >= 2);
|
|
4061
4336
|
if (segments.length > 1) {
|
|
@@ -4128,7 +4403,10 @@ function joinLongEdgeRoutes(routes, segmentIdsByEdgeId, preserveInternalDuplicat
|
|
|
4128
4403
|
} else for (const segmentId of segmentIds) appendPoints(points, routes.pointsByEdgeId.get(segmentId) ?? [], preserveInternalDuplicates || segmentIds.length === 1);
|
|
4129
4404
|
pointsByEdgeId.set(edgeId, preserveInternalDuplicates || segmentIds.length === 1 ? points : simplify(points));
|
|
4130
4405
|
}
|
|
4131
|
-
return {
|
|
4406
|
+
return {
|
|
4407
|
+
pointsByEdgeId,
|
|
4408
|
+
outsideFeedbackEdgeIds
|
|
4409
|
+
};
|
|
4132
4410
|
}
|
|
4133
4411
|
|
|
4134
4412
|
//#endregion
|
|
@@ -8554,19 +8832,52 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8554
8832
|
};
|
|
8555
8833
|
routes.pointsByEdgeId.set(edge.id, points);
|
|
8556
8834
|
}
|
|
8835
|
+
const routedPortAnchors = /* @__PURE__ */ new Map();
|
|
8836
|
+
for (const edge of graph.edges) {
|
|
8837
|
+
const points = routes.pointsByEdgeId.get(edge.id);
|
|
8838
|
+
const first = points?.[0];
|
|
8839
|
+
const last = points?.at(-1);
|
|
8840
|
+
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);
|
|
8841
|
+
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);
|
|
8842
|
+
}
|
|
8557
8843
|
const nodes = graph.nodes.map((node) => {
|
|
8558
8844
|
const rect = placement.rectByNodeId.get(node.id);
|
|
8559
8845
|
if (!rect) throw new Error(`Node placement missing for ${node.id}`);
|
|
8560
|
-
|
|
8846
|
+
let ports = placePorts(node.ports, rect, direction, (port) => input.portSettings?.(port, node), {
|
|
8561
8847
|
...options.settings,
|
|
8562
8848
|
...options.nodeSettings?.(node)
|
|
8563
8849
|
});
|
|
8850
|
+
if (options.nodeSettings?.(node)?.portConstraints === "FIXED_SIDE") ports = ports?.map((port) => {
|
|
8851
|
+
if ((port.width ?? 8) !== 0 || (port.height ?? 8) !== 0) return port;
|
|
8852
|
+
const anchor = routedPortAnchors.get(`${node.id}\0${port.name}`);
|
|
8853
|
+
if (!anchor || port.x === void 0 || port.y === void 0) return port;
|
|
8854
|
+
const configuredAnchor = (input.portSettings?.(port, node))?.["port.anchor"];
|
|
8855
|
+
const width = port.width ?? 0;
|
|
8856
|
+
const height = port.height ?? 0;
|
|
8857
|
+
const defaultAnchorX = port.x >= rect.width ? width : port.x + width <= 0 ? 0 : width / 2;
|
|
8858
|
+
const defaultAnchorY = port.y >= rect.height ? height : port.y + height <= 0 ? 0 : height / 2;
|
|
8859
|
+
const x = anchor.x - rect.x - (configuredAnchor?.x ?? defaultAnchorX);
|
|
8860
|
+
const y = anchor.y - rect.y - (configuredAnchor?.y ?? defaultAnchorY);
|
|
8861
|
+
return {
|
|
8862
|
+
...port,
|
|
8863
|
+
x: x === 0 && Object.is(port.x, -0) ? port.x : x,
|
|
8864
|
+
y: y === 0 && Object.is(port.y, -0) ? port.y : y
|
|
8865
|
+
};
|
|
8866
|
+
});
|
|
8564
8867
|
return {
|
|
8565
8868
|
...node,
|
|
8566
8869
|
...rect,
|
|
8567
8870
|
...ports === void 0 ? {} : { ports }
|
|
8568
8871
|
};
|
|
8569
8872
|
});
|
|
8873
|
+
const feedbackNodeRects = graph.nodes.flatMap((node) => {
|
|
8874
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
8875
|
+
return rect ? [rect] : [];
|
|
8876
|
+
});
|
|
8877
|
+
const minimumFeedbackNodeX = Math.min(...feedbackNodeRects.map((rect) => rect.x));
|
|
8878
|
+
const maximumFeedbackNodeX = Math.max(...feedbackNodeRects.map((rect) => rect.x + rect.width));
|
|
8879
|
+
const minimumFeedbackNodeY = Math.min(...feedbackNodeRects.map((rect) => rect.y));
|
|
8880
|
+
const maximumFeedbackNodeY = Math.max(...feedbackNodeRects.map((rect) => rect.y + rect.height));
|
|
8570
8881
|
const edges = graph.edges.map((edge) => {
|
|
8571
8882
|
const points = [...routes.pointsByEdgeId.get(edge.id) ?? []];
|
|
8572
8883
|
const midpoint = getPolylineMidpoint(points);
|
|
@@ -8582,9 +8893,44 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8582
8893
|
const labelDummyRect = placement.rectByNodeId.get(expanded.labelDummyIdByEdgeId.get(edge.id) ?? "");
|
|
8583
8894
|
const edgeLabelSideSelection = options.settings?.["edgeLabels.sideSelection"] ?? "SMART_DOWN";
|
|
8584
8895
|
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
8896
|
const horizontal = direction === "left" || direction === "right";
|
|
8897
|
+
const verticalTrack = horizontal ? points.find((point, index) => {
|
|
8898
|
+
const next = points[index + 1];
|
|
8899
|
+
return next !== void 0 && point.x === next.x && point.y !== next.y;
|
|
8900
|
+
}) : void 0;
|
|
8901
|
+
const secondPoint = points[1];
|
|
8902
|
+
const beforeLastPoint = points.at(-2);
|
|
8903
|
+
const flowDelta = horizontal ? lastPoint.x - firstPoint.x : lastPoint.y - firstPoint.y;
|
|
8904
|
+
const firstLeadDelta = secondPoint ? horizontal ? secondPoint.x - firstPoint.x : secondPoint.y - firstPoint.y : 0;
|
|
8905
|
+
const lastLeadDelta = beforeLastPoint ? horizontal ? lastPoint.x - beforeLastPoint.x : lastPoint.y - beforeLastPoint.y : 0;
|
|
8906
|
+
const outsideFeedback = routes.outsideFeedbackEdgeIds?.has(edge.id) === true || secondPoint !== void 0 && beforeLastPoint !== void 0 && flowDelta !== 0 && firstLeadDelta * flowDelta < 0 && lastLeadDelta * flowDelta < 0;
|
|
8907
|
+
const horizontalFeedbackCandidate = outsideFeedback ? points.flatMap((point, index) => {
|
|
8908
|
+
const next = points[index + 1];
|
|
8909
|
+
return next !== void 0 && point.y === next.y && (point.y < minimumFeedbackNodeY || point.y > maximumFeedbackNodeY) ? [{
|
|
8910
|
+
start: point,
|
|
8911
|
+
end: next,
|
|
8912
|
+
length: Math.abs(next.x - point.x)
|
|
8913
|
+
}] : [];
|
|
8914
|
+
}).sort((left, right) => right.length - left.length)[0] : void 0;
|
|
8915
|
+
const verticalFeedbackCandidate = outsideFeedback ? points.flatMap((point, index) => {
|
|
8916
|
+
const next = points[index + 1];
|
|
8917
|
+
return next !== void 0 && point.x === next.x && (point.x < minimumFeedbackNodeX || point.x > maximumFeedbackNodeX) ? [{
|
|
8918
|
+
start: point,
|
|
8919
|
+
end: next,
|
|
8920
|
+
length: Math.abs(next.y - point.y)
|
|
8921
|
+
}] : [];
|
|
8922
|
+
}).sort((left, right) => right.length - left.length)[0] : void 0;
|
|
8923
|
+
const sourceNode = graph.nodes.find((node) => node.id === edge.sourceId);
|
|
8924
|
+
const sourcePort = sourceNode?.ports?.find((port) => port.name === edge.sourcePort);
|
|
8925
|
+
const targetPort = sourceNode?.ports?.find((port) => port.name === edge.targetPort);
|
|
8926
|
+
const sourcePortSide = sourceNode && sourcePort ? options.portSettings?.(sourcePort, sourceNode)?.["port.side"] : void 0;
|
|
8927
|
+
const targetPortSide = sourceNode && targetPort ? options.portSettings?.(targetPort, sourceNode)?.["port.side"] : void 0;
|
|
8928
|
+
const horizontalFeedbackTrack = edge.sourceId === edge.targetId && (sourcePortSide === "EAST" || sourcePortSide === "WEST") && targetPortSide === sourcePortSide || (horizontalFeedbackCandidate?.length ?? -1) >= (verticalFeedbackCandidate?.length ?? -1) ? horizontalFeedbackCandidate : void 0;
|
|
8929
|
+
const verticalFeedbackTrack = horizontalFeedbackTrack ? void 0 : verticalFeedbackCandidate;
|
|
8930
|
+
const trackNearTarget = verticalTrack !== void 0 && Math.abs(lastPoint.x - verticalTrack.x) < Math.abs(verticalTrack.x - firstPoint.x);
|
|
8931
|
+
const edgeNodeSpacing = Number(options.settings?.["spacing.edgeNodeBetweenLayers"] ?? 10);
|
|
8932
|
+
const routeX = labelPlacement === "TAIL" ? firstPoint.x + labelSpacing : labelPlacement === "HEAD" ? lastPoint.x - width - labelSpacing : inlineLabel && horizontalFeedbackTrack ? (horizontalFeedbackTrack.start.x + horizontalFeedbackTrack.end.x - width) / 2 : inlineLabel && verticalFeedbackTrack ? verticalFeedbackTrack.start.x > (minimumFeedbackNodeX + maximumFeedbackNodeX) / 2 ? verticalFeedbackTrack.start.x + labelSpacing + 1 : verticalFeedbackTrack.start.x - labelSpacing - width - 1 : inlineLabel && verticalTrack ? (direction === "right" ? trackNearTarget : !trackNearTarget) ? verticalTrack.x - edgeNodeSpacing - width : verticalTrack.x + edgeNodeSpacing : inlineLabel ? horizontal ? Math.floor(midpoint.x - width / 2) : Math.ceil(midpoint.x - width / 2) : midpoint.x - width / 2;
|
|
8933
|
+
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);
|
|
8588
8934
|
const x = labelPlacement === "CENTER" && horizontal && labelDummyRect ? labelDummyRect.x : routeX;
|
|
8589
8935
|
const y = labelPlacement === "CENTER" && !horizontal && labelDummyRect ? labelDummyRect.y : routeY;
|
|
8590
8936
|
return {
|