@statelyai/layout 0.4.3 → 0.5.1
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 +32 -3
- package/dist/elkjs/index.cjs +2 -2
- package/dist/elkjs/index.d.cts +1 -1
- package/dist/elkjs/index.d.mts +1 -1
- package/dist/elkjs/index.mjs +2 -2
- package/dist/elkjs/main.cjs +1 -1
- package/dist/elkjs/main.mjs +1 -1
- package/dist/elkjs/worker.cjs +2 -2
- package/dist/elkjs/worker.mjs +2 -2
- package/dist/{elkjs-Cj-lWiK1.cjs → elkjs-Bh_E59N_.cjs} +3 -3
- package/dist/{elkjs-B5kN_zS6.mjs → elkjs-Dk9yOk9U.mjs} +3 -3
- package/dist/{index-B6cWNrMw.d.cts → index-0YVSvMvA.d.mts} +13 -3
- package/dist/{index-CJQLUKD9.d.mts → index-C0J4ttZe.d.cts} +13 -3
- package/dist/{index-DT6lPMfS.d.cts → index-Xc4R6y3z.d.cts} +9 -2
- package/dist/{index-F1U-fCTu.d.mts → index-lSp0oCJC.d.mts} +9 -2
- package/dist/index.cjs +7 -4
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/layered/index.cjs +1 -1
- package/dist/layered/index.d.cts +2 -2
- package/dist/layered/index.d.mts +2 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-BsAdm_u8.mjs → layered-CUwft7on.mjs} +488 -91
- package/dist/{layered-BJu8A21s.cjs → layered-QzYtokgN.cjs} +488 -91
- package/dist/{layout-engine-BFP1TZbF.cjs → layout-engine-CQ5FcBPC.cjs} +1 -1
- package/dist/{layout-engine-DnlmGxHL.mjs → layout-engine-DvDkMQM3.mjs} +1 -1
- package/dist/routing/index.cjs +5 -2
- package/dist/routing/index.d.cts +3 -3
- package/dist/routing/index.d.mts +3 -3
- package/dist/routing/index.mjs +2 -2
- package/dist/routing-D-LfMXOF.mjs +5189 -0
- package/dist/routing-Phxu1R0x.cjs +5423 -0
- package/dist/{types-CT2akdK0.d.mts → types-D8pYSn8I.d.cts} +112 -2
- package/dist/{types-uxPlGRof.d.cts → types-MAUtJfKQ.d.mts} +112 -2
- package/package.json +4 -1
- package/src/authoring/partial.ts +1 -7
- package/src/authoring/routing.ts +64 -19
- package/src/layered/feedback-chains.ts +22 -4
- package/src/layered/index.ts +186 -42
- package/src/layered/loop-envelopes.ts +96 -26
- package/src/layered/north-south-ports.ts +17 -4
- package/src/layered/self-loop-ports.ts +158 -0
- package/src/layered/strategies.ts +183 -32
- package/src/routing/algorithms.ts +1436 -148
- package/src/routing/bezier-fit.ts +605 -0
- package/src/routing/bezier-visibility.ts +107 -0
- package/src/routing/bezier.ts +596 -0
- package/src/routing/engine.ts +65 -13
- package/src/routing/index.ts +14 -0
- package/src/routing/model.ts +185 -1
- package/src/routing/path.ts +1 -1
- package/src/routing/policy-geometry.ts +536 -0
- package/src/routing/search.ts +10 -2
- package/src/routing/types.ts +77 -1
- package/dist/routing-CR8flv8b.mjs +0 -2780
- package/dist/routing-QIdGGFBe.cjs +0 -2996
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_routing = require('./routing-
|
|
1
|
+
const require_routing = require('./routing-Phxu1R0x.cjs');
|
|
2
2
|
let _statelyai_graph_layout = require("@statelyai/graph/layout");
|
|
3
3
|
let _statelyai_graph = require("@statelyai/graph");
|
|
4
4
|
|
|
@@ -1608,8 +1608,174 @@ function findNode(nodes, id) {
|
|
|
1608
1608
|
return position !== void 0 && nodes[position]?.id === id ? nodes[position] : nodes.find((node) => node.id === id);
|
|
1609
1609
|
}
|
|
1610
1610
|
|
|
1611
|
+
//#endregion
|
|
1612
|
+
//#region src/layered/self-loop-ports.ts
|
|
1613
|
+
const hiddenBySettings = /* @__PURE__ */ new WeakMap();
|
|
1614
|
+
/** Sizes of the ports SelfLoopPreProcessor hid from a loop, if any. */
|
|
1615
|
+
function hiddenLoopPorts(settings, edgeId) {
|
|
1616
|
+
return hiddenBySettings.get(settings)?.get(edgeId);
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* ELK's SelfLoopPreProcessor removes ports that carry only self loops from
|
|
1620
|
+
* nodes whose port order is not fixed, and SelfLoopPortRestorer later puts
|
|
1621
|
+
* them on the loop's side. The layered phases then see implicit loop ports;
|
|
1622
|
+
* {@link restoreSelfLoopPorts} places the hidden ports at the loop ends.
|
|
1623
|
+
*/
|
|
1624
|
+
function hideSelfLoopPorts(graph, options) {
|
|
1625
|
+
const uses = /* @__PURE__ */ new Map();
|
|
1626
|
+
const nonLoop = /* @__PURE__ */ new Set();
|
|
1627
|
+
for (const edge of graph.edges) for (const [node, port] of [[edge.sourceId, edge.sourcePort], [edge.targetId, edge.targetPort]]) {
|
|
1628
|
+
if (port === void 0) continue;
|
|
1629
|
+
const key = `${node}\0${port}`;
|
|
1630
|
+
uses.set(key, (uses.get(key) ?? 0) + 1);
|
|
1631
|
+
if (edge.sourceId !== edge.targetId) nonLoop.add(key);
|
|
1632
|
+
}
|
|
1633
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1634
|
+
const hidden = /* @__PURE__ */ new Map();
|
|
1635
|
+
const edges = /* @__PURE__ */ new Map();
|
|
1636
|
+
const hiddenPorts = /* @__PURE__ */ new Set();
|
|
1637
|
+
for (const edge of graph.edges) {
|
|
1638
|
+
if (edge.sourceId !== edge.targetId || edge.sourcePort === void 0 || edge.targetPort === void 0 || edge.sourcePort === edge.targetPort) continue;
|
|
1639
|
+
const node = nodeById.get(edge.sourceId);
|
|
1640
|
+
const constraints = node && options.nodeSettings?.(node)?.portConstraints;
|
|
1641
|
+
if (!node || constraints !== void 0 && constraints !== "FREE" && constraints !== "UNDEFINED") continue;
|
|
1642
|
+
const ports = [edge.sourcePort, edge.targetPort].map((name) => node.ports?.find((port) => port.name === name));
|
|
1643
|
+
if (ports.some((port) => !port || uses.get(`${node.id}\0${port.name}`) !== 1 || nonLoop.has(`${node.id}\0${port.name}`))) continue;
|
|
1644
|
+
const size$1 = (port) => ({
|
|
1645
|
+
width: port.width ?? 0,
|
|
1646
|
+
height: port.height ?? 0
|
|
1647
|
+
});
|
|
1648
|
+
hidden.set(edge.id, {
|
|
1649
|
+
source: size$1(ports[0]),
|
|
1650
|
+
target: size$1(ports[1])
|
|
1651
|
+
});
|
|
1652
|
+
edges.set(edge.id, edge);
|
|
1653
|
+
for (const port of ports) hiddenPorts.add(`${node.id}\0${port.name}`);
|
|
1654
|
+
}
|
|
1655
|
+
if (hidden.size === 0) return void 0;
|
|
1656
|
+
const settings = { ...options.settings };
|
|
1657
|
+
hiddenBySettings.set(settings, hidden);
|
|
1658
|
+
return {
|
|
1659
|
+
graph: {
|
|
1660
|
+
...graph,
|
|
1661
|
+
nodes: graph.nodes.map((node) => node.ports?.some((port) => hiddenPorts.has(`${node.id}\0${port.name}`)) ? {
|
|
1662
|
+
...node,
|
|
1663
|
+
ports: node.ports.filter((port) => !hiddenPorts.has(`${node.id}\0${port.name}`))
|
|
1664
|
+
} : node),
|
|
1665
|
+
edges: graph.edges.map((edge) => hidden.has(edge.id) ? {
|
|
1666
|
+
...edge,
|
|
1667
|
+
sourcePort: void 0,
|
|
1668
|
+
targetPort: void 0
|
|
1669
|
+
} : edge)
|
|
1670
|
+
},
|
|
1671
|
+
options: {
|
|
1672
|
+
...options,
|
|
1673
|
+
settings
|
|
1674
|
+
},
|
|
1675
|
+
edges
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
/** Put hidden loop ports back, anchored on the side their loop leaves from. */
|
|
1679
|
+
function restoreSelfLoopPorts(graph, result, edges) {
|
|
1680
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
1681
|
+
for (const edge of result.edges) {
|
|
1682
|
+
const original = edges.get(edge.id);
|
|
1683
|
+
const points = edge.points;
|
|
1684
|
+
if (!original || !points?.length) continue;
|
|
1685
|
+
anchors.set(`${edge.sourceId}\0${original.sourcePort}`, points[0]);
|
|
1686
|
+
anchors.set(`${edge.targetId}\0${original.targetPort}`, points.at(-1));
|
|
1687
|
+
}
|
|
1688
|
+
const originalById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1689
|
+
return {
|
|
1690
|
+
...result,
|
|
1691
|
+
nodes: result.nodes.map((node) => {
|
|
1692
|
+
const original = originalById.get(node.id)?.ports;
|
|
1693
|
+
if (!original?.some((port) => anchors.has(`${node.id}\0${port.name}`))) return node;
|
|
1694
|
+
const placed = new Map(node.ports?.map((port) => [port.name, port]));
|
|
1695
|
+
return {
|
|
1696
|
+
...node,
|
|
1697
|
+
ports: original.map((port) => {
|
|
1698
|
+
const anchor = anchors.get(`${node.id}\0${port.name}`);
|
|
1699
|
+
const width = port.width ?? 0, height = port.height ?? 0;
|
|
1700
|
+
if (!anchor) return placed.get(port.name) ?? {
|
|
1701
|
+
...port,
|
|
1702
|
+
width,
|
|
1703
|
+
height,
|
|
1704
|
+
x: 0,
|
|
1705
|
+
y: 0
|
|
1706
|
+
};
|
|
1707
|
+
const x = anchor.x - node.x, y = anchor.y - node.y;
|
|
1708
|
+
return {
|
|
1709
|
+
...port,
|
|
1710
|
+
width,
|
|
1711
|
+
height,
|
|
1712
|
+
x: x <= 0 ? x : x >= node.width ? x - width : x - width / 2,
|
|
1713
|
+
y: x <= 0 || x >= node.width ? y - height / 2 : y <= 0 ? y : y - height
|
|
1714
|
+
};
|
|
1715
|
+
})
|
|
1716
|
+
};
|
|
1717
|
+
}),
|
|
1718
|
+
edges: result.edges.map((edge) => {
|
|
1719
|
+
const original = edges.get(edge.id);
|
|
1720
|
+
return original ? {
|
|
1721
|
+
...edge,
|
|
1722
|
+
sourcePort: original.sourcePort,
|
|
1723
|
+
targetPort: original.targetPort
|
|
1724
|
+
} : edge;
|
|
1725
|
+
})
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1611
1729
|
//#endregion
|
|
1612
1730
|
//#region src/layered/loop-envelopes.ts
|
|
1731
|
+
const crossSide = (side) => side === "NORTH" || side === "SOUTH";
|
|
1732
|
+
/**
|
|
1733
|
+
* Extents of a movable loop's hidden ports in the canonical rightward frame:
|
|
1734
|
+
* along its side, and protruding from it.
|
|
1735
|
+
*/
|
|
1736
|
+
function loopPortExtents(input, edge, side) {
|
|
1737
|
+
const ports = hiddenLoopPorts(input.settings, edge.id);
|
|
1738
|
+
const horizontal$1 = input.direction === "right" || input.direction === "left";
|
|
1739
|
+
const along = crossSide(side) === horizontal$1 ? "width" : "height";
|
|
1740
|
+
const normal = along === "width" ? "height" : "width";
|
|
1741
|
+
return {
|
|
1742
|
+
along: ports ? ports.source[along] + ports.target[along] : 0,
|
|
1743
|
+
normal: ports ? Math.max(ports.source[normal], ports.target[normal]) : 0
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
/** A movable loop's label extent away from its side, in the canonical frame. */
|
|
1747
|
+
function movableLoopLabelExtent(input, edge, side) {
|
|
1748
|
+
const horizontal$1 = input.direction === "right" || input.direction === "left";
|
|
1749
|
+
const size$1 = (crossSide(side) === horizontal$1 ? edge.height : edge.width) ?? 0;
|
|
1750
|
+
return size$1 > 0 ? size$1 + Number(input.settings["spacing.edgeLabel"] ?? 2) : 0;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* ELK's OrthogonalSelfLoopRouter routing slot of a movable loop, measured
|
|
1754
|
+
* from its node side in the canonical frame: past the protruding ports and
|
|
1755
|
+
* the self-loop spacing, then one edge spacing plus any label per inner slot.
|
|
1756
|
+
*/
|
|
1757
|
+
function movableLoopTrack(input, sideLoops, edge, side, ordering) {
|
|
1758
|
+
const ordered = ordering === "REVERSE_STACKED" ? [...sideLoops].reverse() : sideLoops;
|
|
1759
|
+
const inner = ordering === "SEQUENCED" ? [] : ordered.slice(0, ordered.indexOf(edge));
|
|
1760
|
+
const edgeSpacing = Number(input.settings["spacing.edgeEdge"] ?? 10);
|
|
1761
|
+
return Math.max(0, ...sideLoops.map((loop) => loopPortExtents(input, loop, side).normal)) + Number(input.settings["spacing.nodeSelfLoop"] ?? 10) + inner.reduce((sum, loop) => sum + edgeSpacing + (crossSide(side) ? movableLoopLabelExtent(input, loop, side) : 0), 0);
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Canonical position of a movable loop port along its side, given its
|
|
1765
|
+
* DISTRIBUTED ratio among the side's implicit ports (ELK's port placement
|
|
1766
|
+
* with equal gaps between hidden ports of their own size).
|
|
1767
|
+
*/
|
|
1768
|
+
function movableLoopPortOffset(input, sideLoops, side, length, ratio) {
|
|
1769
|
+
const count = sideLoops.length * 2;
|
|
1770
|
+
const extents = sideLoops.map((loop) => loopPortExtents(input, loop, side));
|
|
1771
|
+
const total = extents.reduce((sum, extent$2) => sum + extent$2.along, 0);
|
|
1772
|
+
const size$1 = total / count;
|
|
1773
|
+
const index = Math.round(ratio * (count + 1)) - 1;
|
|
1774
|
+
return {
|
|
1775
|
+
along: (length - total) / (count + 1) * (index + 1) + size$1 * index + size$1 / 2,
|
|
1776
|
+
normal: Math.max(0, ...extents.map((extent$2) => extent$2.normal))
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1613
1779
|
/** Unconstrained implicit loop ports are restored on loop faces after the sweep. */
|
|
1614
1780
|
function hasMovableLoopPorts(input, edge) {
|
|
1615
1781
|
if (edge.sourceId !== edge.targetId || edge.sourcePort !== void 0 || edge.targetPort !== void 0) return false;
|
|
@@ -1647,7 +1813,7 @@ function loopEnvelopes(input) {
|
|
|
1647
1813
|
};
|
|
1648
1814
|
const horizontal$1 = input.direction === "right" || input.direction === "left";
|
|
1649
1815
|
const reverse = input.direction === "left" || input.direction === "up";
|
|
1650
|
-
const spacing
|
|
1816
|
+
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
1651
1817
|
let supported = true;
|
|
1652
1818
|
const tracks = selfLoopTracks(allLoops);
|
|
1653
1819
|
for (const edge of allLoops) {
|
|
@@ -1666,7 +1832,7 @@ function loopEnvelopes(input) {
|
|
|
1666
1832
|
}, input.direction, input), getPortPoint(node, edge.targetPort, rect, {
|
|
1667
1833
|
x: 0,
|
|
1668
1834
|
y: 0
|
|
1669
|
-
}, input.direction, input), sourceSide, targetSide, spacing
|
|
1835
|
+
}, input.direction, input), sourceSide, targetSide, spacing * (tracks.get(edge.id) + 1), input.direction);
|
|
1670
1836
|
if (labeled) {
|
|
1671
1837
|
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
1672
1838
|
const { width = 0, height = 0 } = edge;
|
|
@@ -1691,11 +1857,10 @@ function loopEnvelopes(input) {
|
|
|
1691
1857
|
}
|
|
1692
1858
|
}
|
|
1693
1859
|
const loops = allLoops.filter((edge) => hasMovableLoopPorts(input, edge));
|
|
1694
|
-
if (loops.length === 0
|
|
1860
|
+
if (loops.length === 0) continue;
|
|
1695
1861
|
const settings = input.nodeSettings?.(node);
|
|
1696
1862
|
const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
1697
1863
|
const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
1698
|
-
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
1699
1864
|
const envelope = {
|
|
1700
1865
|
before: 0,
|
|
1701
1866
|
after: 0,
|
|
@@ -1708,12 +1873,16 @@ function loopEnvelopes(input) {
|
|
|
1708
1873
|
"flowAfter",
|
|
1709
1874
|
"flowBefore"
|
|
1710
1875
|
] : distribution === "NORTH_SOUTH" ? ["before", "after"] : ["before"];
|
|
1876
|
+
const canonicalSides = {
|
|
1877
|
+
before: "NORTH",
|
|
1878
|
+
after: "SOUTH",
|
|
1879
|
+
flowAfter: "EAST",
|
|
1880
|
+
flowBefore: "WEST"
|
|
1881
|
+
};
|
|
1711
1882
|
for (const side of sides$1) {
|
|
1712
1883
|
const selected = loops.filter((_, index) => sides$1[index % sides$1.length] === side);
|
|
1713
|
-
const
|
|
1714
|
-
|
|
1715
|
-
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
1716
|
-
envelope[side] = spacing * (ordering === "SEQUENCED" ? Math.min(1, selected.length) : selected.length) + selected.reduce((sum, edge) => sum + ((edge[crossSize$3] ?? 0) > 0 ? (edge[crossSize$3] ?? 0) + labelSpacing : 0), 0);
|
|
1884
|
+
const canonical = canonicalSides[side];
|
|
1885
|
+
envelope[side] = Math.max(0, ...selected.map((edge) => movableLoopTrack(input, selected, edge, canonical, ordering) + movableLoopLabelExtent(input, edge, canonical)));
|
|
1717
1886
|
}
|
|
1718
1887
|
envelopes.set(node.id, envelope);
|
|
1719
1888
|
}
|
|
@@ -2881,7 +3050,7 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
2881
3050
|
if (point.x >= rect.x + rect.width - 1e-9) return "EAST";
|
|
2882
3051
|
return point.y <= rect.y ? "NORTH" : "SOUTH";
|
|
2883
3052
|
};
|
|
2884
|
-
const crossSide = (side) => side === "NORTH" || side === "SOUTH";
|
|
3053
|
+
const crossSide$1 = (side) => side === "NORTH" || side === "SOUTH";
|
|
2885
3054
|
const segments = [];
|
|
2886
3055
|
for (const [edgeId, points] of canonicalRoutes) {
|
|
2887
3056
|
const edge = edgeById.get(edgeId);
|
|
@@ -2891,8 +3060,8 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
2891
3060
|
const first = points[index];
|
|
2892
3061
|
const second = points[index + 1];
|
|
2893
3062
|
if (Math.abs(first.x - second.x) > 1e-9 || Math.abs(first.y - second.y) < 1e-9) continue;
|
|
2894
|
-
if (index === 0 && !crossSide(endpointSide(edge.sourceId, edge.sourcePort, first))) continue;
|
|
2895
|
-
if (index === points.length - 2 && !crossSide(endpointSide(edge.targetId, edge.targetPort, second))) continue;
|
|
3063
|
+
if (index === 0 && !crossSide$1(endpointSide(edge.sourceId, edge.sourcePort, first))) continue;
|
|
3064
|
+
if (index === points.length - 2 && !crossSide$1(endpointSide(edge.targetId, edge.targetPort, second))) continue;
|
|
2896
3065
|
const owner = index === 0 ? nodes.get(edge.sourceId) : index === points.length - 2 ? nodes.get(edge.targetId) : void 0;
|
|
2897
3066
|
const ownerSide = owner ? endpointSide(index === 0 ? edge.sourceId : edge.targetId, index === 0 ? edge.sourcePort : edge.targetPort, index === 0 ? first : second) : void 0;
|
|
2898
3067
|
const bend = index === 0 ? second : first;
|
|
@@ -3055,7 +3224,7 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
3055
3224
|
const points = canonicalRoutes.get(edge.id);
|
|
3056
3225
|
const sourceSide = endpointSide(edge.sourceId, edge.sourcePort, points?.[0]);
|
|
3057
3226
|
const targetSide = endpointSide(edge.targetId, edge.targetPort, points?.at(-1));
|
|
3058
|
-
if (crossSide(sourceSide) && crossSide(targetSide)) continue;
|
|
3227
|
+
if (crossSide$1(sourceSide) && crossSide$1(targetSide)) continue;
|
|
3059
3228
|
const reversed = orientation?.reversedEdgeIds.has(edge.id) ?? source.x > target.x;
|
|
3060
3229
|
const from = reversed ? target : source, to = reversed ? source : target;
|
|
3061
3230
|
constraints.push({
|
|
@@ -3149,8 +3318,8 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
3149
3318
|
const start = pointToCanonical(originals[0]);
|
|
3150
3319
|
const next = pointToCanonical(originals[1]);
|
|
3151
3320
|
const previous = pointToCanonical(originals.at(-2));
|
|
3152
|
-
if (!crossSide(endpointSide(edge.sourceId, edge.sourcePort, start)) && Math.abs(start.x - next.x) < 1e-9) points[1].x = points[0].x;
|
|
3153
|
-
if (!crossSide(endpointSide(edge.targetId, edge.targetPort, end)) && Math.abs(end.x - previous.x) < 1e-9) points[points.length - 2].x = points.at(-1).x;
|
|
3321
|
+
if (!crossSide$1(endpointSide(edge.sourceId, edge.sourcePort, start)) && Math.abs(start.x - next.x) < 1e-9) points[1].x = points[0].x;
|
|
3322
|
+
if (!crossSide$1(endpointSide(edge.targetId, edge.targetPort, end)) && Math.abs(end.x - previous.x) < 1e-9) points[points.length - 2].x = points.at(-1).x;
|
|
3154
3323
|
}
|
|
3155
3324
|
}
|
|
3156
3325
|
const extent$2 = Math.max(...items.map((item) => item.x + item.width), ...[...canonicalRoutes.values()].flatMap((points) => points.map((point) => point.x)));
|
|
@@ -3719,6 +3888,19 @@ function assertNorthSouthPortSides(graph) {
|
|
|
3719
3888
|
//#region src/layered/strategies.ts
|
|
3720
3889
|
const modelSweepInputs = /* @__PURE__ */ new WeakSet();
|
|
3721
3890
|
const nodeRelativePortRanksByInput = /* @__PURE__ */ new WeakMap();
|
|
3891
|
+
const labelPortSideFeedbackByInput = /* @__PURE__ */ new WeakMap();
|
|
3892
|
+
/**
|
|
3893
|
+
* @internal Collect the feedback segments routed to a center-label dummy's port
|
|
3894
|
+
* sides. Exempt segments keep the plain feedback detour instead.
|
|
3895
|
+
*/
|
|
3896
|
+
function trackLabelPortSideFeedback(input, exempt) {
|
|
3897
|
+
const routed = /* @__PURE__ */ new Set();
|
|
3898
|
+
labelPortSideFeedbackByInput.set(input, {
|
|
3899
|
+
exempt,
|
|
3900
|
+
routed
|
|
3901
|
+
});
|
|
3902
|
+
return routed;
|
|
3903
|
+
}
|
|
3722
3904
|
function getOrientedEndpoints(edge, orientation) {
|
|
3723
3905
|
return orientation.reversedEdgeIds.has(edge.id) ? [edge.targetId, edge.sourceId] : [edge.sourceId, edge.targetId];
|
|
3724
3906
|
}
|
|
@@ -4923,7 +5105,7 @@ const assignLayersWithCoffmanGraham = (input, orientation) => {
|
|
|
4923
5105
|
seedOrder: [...layerMembers].reverse().flat()
|
|
4924
5106
|
};
|
|
4925
5107
|
};
|
|
4926
|
-
function sortLayerByAdjacentPosition(layer, ranksByNodeId, statistic, random, preOrdered = true, sourceUnknownPlacement = true, perturbSummedWeight = true, units, sameLayerNeighbors = /* @__PURE__ */ new Map(), orderedVisits, forcedCompare) {
|
|
5108
|
+
function sortLayerByAdjacentPosition(layer, ranksByNodeId, statistic, random, preOrdered = true, sourceUnknownPlacement = true, perturbSummedWeight = true, units, sameLayerNeighbors = /* @__PURE__ */ new Map(), orderedVisits, forcedCompare, forcedTies) {
|
|
4927
5109
|
const originalIndex = new Map(layer.map((nodeId, index) => [nodeId, index]));
|
|
4928
5110
|
const adjacentPosition = /* @__PURE__ */ new Map();
|
|
4929
5111
|
if (units && statistic === "mean") for (const [id, score] of associatedBarycenters(layer, ranksByNodeId, sameLayerNeighbors, units.associates, random, orderedVisits)) adjacentPosition.set(id, score);
|
|
@@ -4959,6 +5141,7 @@ function sortLayerByAdjacentPosition(layer, ranksByNodeId, statistic, random, pr
|
|
|
4959
5141
|
}
|
|
4960
5142
|
else for (const nodeId of layer) if (adjacentPosition.get(nodeId) === void 0) adjacentPosition.set(nodeId, originalIndex.get(nodeId) ?? 0);
|
|
4961
5143
|
const compare = (a, b) => {
|
|
5144
|
+
if (forcedTies?.has(a) || forcedTies?.has(b)) return 0;
|
|
4962
5145
|
return forcedCompare?.(a, b) || (adjacentPosition.get(a) ?? 0) - (adjacentPosition.get(b) ?? 0) || (originalIndex.get(a) ?? 0) - (originalIndex.get(b) ?? 0);
|
|
4963
5146
|
};
|
|
4964
5147
|
if (forcedCompare) insertionSort(layer, transitiveComparator((id) => id, compare));
|
|
@@ -5110,6 +5293,10 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5110
5293
|
let initialOrderAttempt = usesInitialModelOrder;
|
|
5111
5294
|
const sourceUnknownPlacement = true;
|
|
5112
5295
|
const forcedCompare = usesInitialModelOrder && input.settings["crossingMinimization.forceNodeModelOrder"] === true ? normalModelComparator(input) : void 0;
|
|
5296
|
+
const forcedTies = forcedCompare ? new Set(input.graph.nodes.filter((node) => {
|
|
5297
|
+
const constraint = input.nodeSettings?.(node)?.["layering.layerConstraint"];
|
|
5298
|
+
return constraint === "FIRST_SEPARATE" || constraint === "LAST_SEPARATE";
|
|
5299
|
+
}).map((node) => node.id)) : void 0;
|
|
5113
5300
|
const medianWeights = /* @__PURE__ */ new Map();
|
|
5114
5301
|
const listedEdgeIds = /* @__PURE__ */ new Map();
|
|
5115
5302
|
const listedEdgeId = (edgeId) => {
|
|
@@ -5251,7 +5438,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5251
5438
|
const shuffleFirstLayer = (forward) => {
|
|
5252
5439
|
const firstLayerIndex = forward ? 0 : Math.max(0, working.length - 1);
|
|
5253
5440
|
const firstLayerWeights = new Map((working[firstLayerIndex] ?? []).map((id) => [id, random.nextDouble()]));
|
|
5254
|
-
const compare = (left, right) => forcedCompare?.(left, right) || (firstLayerWeights.get(left) ?? 0) - (firstLayerWeights.get(right) ?? 0);
|
|
5441
|
+
const compare = (left, right) => forcedTies?.has(left) || forcedTies?.has(right) ? 0 : forcedCompare?.(left, right) || (firstLayerWeights.get(left) ?? 0) - (firstLayerWeights.get(right) ?? 0);
|
|
5255
5442
|
const first = working[firstLayerIndex];
|
|
5256
5443
|
if (first) if (forcedCompare) insertionSort(first, transitiveComparator((id) => id, compare));
|
|
5257
5444
|
else first.sort(compare);
|
|
@@ -5347,7 +5534,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5347
5534
|
if (statistic === "median") sortWithMedianWeights(current, previous);
|
|
5348
5535
|
else {
|
|
5349
5536
|
const adjacent = adjacentRanks(previous, current, true);
|
|
5350
|
-
sortLayerByAdjacentPosition(current, adjacent.ranks, statistic, random, !firstSweep || initialOrderAttempt || canonicalNodes.get(current[0] ?? "")?.type === "EXTERNAL_PORT", sourceUnknownPlacement, true, units, sameLayerNeighbors(isForward), adjacent.visits, forcedCompare);
|
|
5537
|
+
sortLayerByAdjacentPosition(current, adjacent.ranks, statistic, random, !firstSweep || initialOrderAttempt || canonicalNodes.get(current[0] ?? "")?.type === "EXTERNAL_PORT", sourceUnknownPlacement, true, units, sameLayerNeighbors(isForward), adjacent.visits, forcedCompare, forcedTies);
|
|
5351
5538
|
}
|
|
5352
5539
|
groupMixedPortDirections(current, previous, working[layer + 1]);
|
|
5353
5540
|
if (exactPortSweep) distributePorts(layer, true);
|
|
@@ -5361,7 +5548,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5361
5548
|
if (statistic === "median") sortWithMedianWeights(current, next);
|
|
5362
5549
|
else {
|
|
5363
5550
|
const adjacent = adjacentRanks(next, current, false);
|
|
5364
|
-
sortLayerByAdjacentPosition(current, adjacent.ranks, statistic, random, !firstSweep || initialOrderAttempt || canonicalNodes.get(current[0] ?? "")?.type === "EXTERNAL_PORT", sourceUnknownPlacement, true, units, sameLayerNeighbors(isForward), adjacent.visits, forcedCompare);
|
|
5551
|
+
sortLayerByAdjacentPosition(current, adjacent.ranks, statistic, random, !firstSweep || initialOrderAttempt || canonicalNodes.get(current[0] ?? "")?.type === "EXTERNAL_PORT", sourceUnknownPlacement, true, units, sameLayerNeighbors(isForward), adjacent.visits, forcedCompare, forcedTies);
|
|
5365
5552
|
}
|
|
5366
5553
|
groupMixedPortDirections(current, next, working[layer - 1]);
|
|
5367
5554
|
if (exactPortSweep) distributePorts(layer, false);
|
|
@@ -6409,13 +6596,13 @@ function routeEdges(style) {
|
|
|
6409
6596
|
"WEST"
|
|
6410
6597
|
][index % 4] : distribution === "NORTH_SOUTH" && index % 2 === 1 ? "SOUTH" : "NORTH";
|
|
6411
6598
|
};
|
|
6412
|
-
const loopDistance = (loops, edge, side, ordering
|
|
6413
|
-
const axis =
|
|
6599
|
+
const loopDistance = (loops, edge, side, ordering) => {
|
|
6600
|
+
const axis = side === "NORTH" || side === "SOUTH" ? "height" : "width";
|
|
6414
6601
|
const ordered = ordering === "REVERSE_STACKED" ? [...loops].reverse() : loops;
|
|
6415
6602
|
const index = ordered.indexOf(edge);
|
|
6416
6603
|
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
6417
6604
|
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
6418
|
-
const size$1 = (candidate) =>
|
|
6605
|
+
const size$1 = (candidate) => input.edgeSettings?.(candidate)?.["edgeLabels.inline"] === true ? candidate[axis] ?? 0 : 0;
|
|
6419
6606
|
return spacing * (ordering === "SEQUENCED" ? 1 : index + 1) + ordered.slice(0, index).reduce((extent$2, candidate) => extent$2 + (size$1(candidate) > 0 ? size$1(candidate) + labelSpacing : 0), 0) + (size$1(edge) > 0 && input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? size$1(edge) / 2 + labelSpacing : 0);
|
|
6420
6607
|
};
|
|
6421
6608
|
const hasFixedLoopPorts = (node) => {
|
|
@@ -6470,7 +6657,7 @@ function routeEdges(style) {
|
|
|
6470
6657
|
for (const [id, loops] of selfLoopsByNodeId) {
|
|
6471
6658
|
const rect = mutableRects.get(id);
|
|
6472
6659
|
const node = nodeById.get(id);
|
|
6473
|
-
if (!rect || !node) continue;
|
|
6660
|
+
if (!rect || !node || hasPlacementLoopEnvelope(placement, id)) continue;
|
|
6474
6661
|
const settings = input.nodeSettings?.(node);
|
|
6475
6662
|
const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
6476
6663
|
const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
@@ -7381,6 +7568,17 @@ function routeEdges(style) {
|
|
|
7381
7568
|
};
|
|
7382
7569
|
const start$1 = fixedSideFeedback ? getPortPoint(source, edge.sourcePort, sourceRect, sourceFallback$1, input.direction, input) : sourceFallback$1;
|
|
7383
7570
|
const end$1 = fixedSideFeedback ? getPortPoint(target, edge.targetPort, targetRect, targetFallback$1, input.direction, input) : targetFallback$1;
|
|
7571
|
+
const [forwardSide, backwardSide] = {
|
|
7572
|
+
right: ["EAST", "WEST"],
|
|
7573
|
+
left: ["WEST", "EAST"],
|
|
7574
|
+
down: ["SOUTH", "NORTH"],
|
|
7575
|
+
up: ["NORTH", "SOUTH"]
|
|
7576
|
+
}[input.direction];
|
|
7577
|
+
const labelPortSides = labelPortSideFeedbackByInput.get(input);
|
|
7578
|
+
const faces = (node, side, away) => !fixedSideFeedback && !labelPortSides?.exempt.has(edge.id) && node.id.startsWith("__layout_dummy:label:") && side === away;
|
|
7579
|
+
const sourceFace = faces(source, feedbackSourcePortSide, backwardSide) ? -1 : 1;
|
|
7580
|
+
const targetFace = faces(target, feedbackTargetPortSide, forwardSide) ? -1 : 1;
|
|
7581
|
+
if (sourceFace < 0 || targetFace < 0) labelPortSides?.routed.add(edge.id);
|
|
7384
7582
|
if (horizontal$1) {
|
|
7385
7583
|
const sign = fixedSideFeedback ? Math.sign(sourceRect.x - targetRect.x) || 1 : reverse ? -1 : 1;
|
|
7386
7584
|
const outerCross = Math.max(...[...placement.rectByNodeId.values()].map((rect) => rect.y + rect.height)) + crossSpacing + (fixedSideFeedback ? .5 : 0);
|
|
@@ -7388,11 +7586,11 @@ function routeEdges(style) {
|
|
|
7388
7586
|
pointsByEdgeId.set(edge.id, [
|
|
7389
7587
|
start$1,
|
|
7390
7588
|
{
|
|
7391
|
-
x: start$1.x + sign * flowSpacing,
|
|
7589
|
+
x: start$1.x + sourceFace * sign * flowSpacing,
|
|
7392
7590
|
y: start$1.y
|
|
7393
7591
|
},
|
|
7394
7592
|
{
|
|
7395
|
-
x: start$1.x + sign * flowSpacing,
|
|
7593
|
+
x: start$1.x + sourceFace * sign * flowSpacing,
|
|
7396
7594
|
y: outerCross
|
|
7397
7595
|
},
|
|
7398
7596
|
...preserveCenters ? [{
|
|
@@ -7403,11 +7601,11 @@ function routeEdges(style) {
|
|
|
7403
7601
|
y: outerCross
|
|
7404
7602
|
}] : [],
|
|
7405
7603
|
{
|
|
7406
|
-
x: end$1.x - sign * flowSpacing,
|
|
7604
|
+
x: end$1.x - targetFace * sign * flowSpacing,
|
|
7407
7605
|
y: outerCross
|
|
7408
7606
|
},
|
|
7409
7607
|
{
|
|
7410
|
-
x: end$1.x - sign * flowSpacing,
|
|
7608
|
+
x: end$1.x - targetFace * sign * flowSpacing,
|
|
7411
7609
|
y: end$1.y
|
|
7412
7610
|
},
|
|
7413
7611
|
end$1
|
|
@@ -7420,11 +7618,11 @@ function routeEdges(style) {
|
|
|
7420
7618
|
start$1,
|
|
7421
7619
|
{
|
|
7422
7620
|
x: start$1.x,
|
|
7423
|
-
y: start$1.y + sign * flowSpacing
|
|
7621
|
+
y: start$1.y + sourceFace * sign * flowSpacing
|
|
7424
7622
|
},
|
|
7425
7623
|
{
|
|
7426
7624
|
x: outerCross,
|
|
7427
|
-
y: start$1.y + sign * flowSpacing
|
|
7625
|
+
y: start$1.y + sourceFace * sign * flowSpacing
|
|
7428
7626
|
},
|
|
7429
7627
|
...preserveCenters ? [{
|
|
7430
7628
|
x: outerCross,
|
|
@@ -7435,11 +7633,11 @@ function routeEdges(style) {
|
|
|
7435
7633
|
}] : [],
|
|
7436
7634
|
{
|
|
7437
7635
|
x: outerCross,
|
|
7438
|
-
y: end$1.y - sign * flowSpacing
|
|
7636
|
+
y: end$1.y - targetFace * sign * flowSpacing
|
|
7439
7637
|
},
|
|
7440
7638
|
{
|
|
7441
7639
|
x: end$1.x,
|
|
7442
|
-
y: end$1.y - sign * flowSpacing
|
|
7640
|
+
y: end$1.y - targetFace * sign * flowSpacing
|
|
7443
7641
|
},
|
|
7444
7642
|
end$1
|
|
7445
7643
|
]);
|
|
@@ -7574,7 +7772,7 @@ function routeEdges(style) {
|
|
|
7574
7772
|
outsideFeedbackEdgeIds.add(edge.id);
|
|
7575
7773
|
continue;
|
|
7576
7774
|
}
|
|
7577
|
-
const movable = hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id)
|
|
7775
|
+
const movable = hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id);
|
|
7578
7776
|
const loopRect = movable ? {
|
|
7579
7777
|
x: 0,
|
|
7580
7778
|
y: 0,
|
|
@@ -7614,7 +7812,32 @@ function routeEdges(style) {
|
|
|
7614
7812
|
startRatio = (countOnSide + nestingIndex + 1) / denominator;
|
|
7615
7813
|
endRatio = (countOnSide - nestingIndex) / denominator;
|
|
7616
7814
|
}
|
|
7617
|
-
const
|
|
7815
|
+
const sideLoops = loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side);
|
|
7816
|
+
if (movable) {
|
|
7817
|
+
const track$1 = movableLoopTrack(input, sideLoops, edge, side, ordering);
|
|
7818
|
+
const sign = side === "NORTH" ? -1 : 1;
|
|
7819
|
+
const base = side === "NORTH" ? 0 : loopRect.height;
|
|
7820
|
+
const [first, last] = [startRatio, endRatio].map((ratio) => movableLoopPortOffset(input, sideLoops, side, loopRect.width, ratio));
|
|
7821
|
+
return [
|
|
7822
|
+
{
|
|
7823
|
+
x: first.along,
|
|
7824
|
+
y: base + sign * first.normal
|
|
7825
|
+
},
|
|
7826
|
+
{
|
|
7827
|
+
x: first.along,
|
|
7828
|
+
y: base + sign * track$1
|
|
7829
|
+
},
|
|
7830
|
+
{
|
|
7831
|
+
x: last.along,
|
|
7832
|
+
y: base + sign * track$1
|
|
7833
|
+
},
|
|
7834
|
+
{
|
|
7835
|
+
x: last.along,
|
|
7836
|
+
y: base + sign * last.normal
|
|
7837
|
+
}
|
|
7838
|
+
];
|
|
7839
|
+
}
|
|
7840
|
+
const distance = loopDistance(sideLoops, edge, side, ordering);
|
|
7618
7841
|
const y = side === "NORTH" ? loopRect.y - distance : loopRect.y + loopRect.height + distance;
|
|
7619
7842
|
const start$2 = {
|
|
7620
7843
|
x: loopRect.x + loopRect.width * startRatio,
|
|
@@ -7644,7 +7867,32 @@ function routeEdges(style) {
|
|
|
7644
7867
|
const secondRatio = sequenced ? 1 / denominator : 2 / denominator;
|
|
7645
7868
|
const startRatio = side === "EAST" ? firstRatio : secondRatio;
|
|
7646
7869
|
const endRatio = side === "EAST" ? secondRatio : firstRatio;
|
|
7647
|
-
const
|
|
7870
|
+
const sideLoops = loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side);
|
|
7871
|
+
if (movable) {
|
|
7872
|
+
const track$1 = movableLoopTrack(input, sideLoops, edge, side, ordering);
|
|
7873
|
+
const sign = side === "WEST" ? -1 : 1;
|
|
7874
|
+
const base = side === "WEST" ? 0 : loopRect.width;
|
|
7875
|
+
const [first, last] = [startRatio, endRatio].map((ratio) => movableLoopPortOffset(input, sideLoops, side, loopRect.height, ratio));
|
|
7876
|
+
return [
|
|
7877
|
+
{
|
|
7878
|
+
x: base + sign * first.normal,
|
|
7879
|
+
y: first.along
|
|
7880
|
+
},
|
|
7881
|
+
{
|
|
7882
|
+
x: base + sign * track$1,
|
|
7883
|
+
y: first.along
|
|
7884
|
+
},
|
|
7885
|
+
{
|
|
7886
|
+
x: base + sign * track$1,
|
|
7887
|
+
y: last.along
|
|
7888
|
+
},
|
|
7889
|
+
{
|
|
7890
|
+
x: base + sign * last.normal,
|
|
7891
|
+
y: last.along
|
|
7892
|
+
}
|
|
7893
|
+
];
|
|
7894
|
+
}
|
|
7895
|
+
const distance = input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? loopDistance(sideLoops, edge, side, ordering) : spacing * (indexOnSide + 1);
|
|
7648
7896
|
const x = side === "EAST" ? loopRect.x + loopRect.width + distance : loopRect.x - distance;
|
|
7649
7897
|
const start$2 = {
|
|
7650
7898
|
x: side === "EAST" ? loopRect.x + loopRect.width : loopRect.x,
|
|
@@ -7924,6 +8172,55 @@ function routeEdges(style) {
|
|
|
7924
8172
|
const routeEdgesOrthogonally = routeEdges("ORTHOGONAL");
|
|
7925
8173
|
const routeEdgesWithPolylines = routeEdges("POLYLINE");
|
|
7926
8174
|
const routeEdgesWithSplines = routeEdges("SPLINES");
|
|
8175
|
+
const sweptPortRanks = /* @__PURE__ */ new WeakMap();
|
|
8176
|
+
/**
|
|
8177
|
+
* Order movable ports along each physical side as crossing minimization left
|
|
8178
|
+
* them, as ELK places ports from its swept port lists. The lists are
|
|
8179
|
+
* clockwise per canonical side.
|
|
8180
|
+
*/
|
|
8181
|
+
function recordSweptPortOrder(graph, direction, order) {
|
|
8182
|
+
const connected = new Set(graph.edges.flatMap((edge) => [JSON.stringify([edge.sourceId, edge.sourcePort]), JSON.stringify([edge.targetId, edge.targetPort])]));
|
|
8183
|
+
const vertical$1 = direction === "down" || direction === "up";
|
|
8184
|
+
const reverse = direction === "up" || direction === "left";
|
|
8185
|
+
const canonical = direction === "right" ? {
|
|
8186
|
+
NORTH: "NORTH",
|
|
8187
|
+
EAST: "EAST",
|
|
8188
|
+
SOUTH: "SOUTH",
|
|
8189
|
+
WEST: "WEST"
|
|
8190
|
+
} : direction === "left" ? {
|
|
8191
|
+
NORTH: "NORTH",
|
|
8192
|
+
EAST: "WEST",
|
|
8193
|
+
SOUTH: "SOUTH",
|
|
8194
|
+
WEST: "EAST"
|
|
8195
|
+
} : direction === "down" ? {
|
|
8196
|
+
NORTH: "WEST",
|
|
8197
|
+
EAST: "SOUTH",
|
|
8198
|
+
SOUTH: "EAST",
|
|
8199
|
+
WEST: "NORTH"
|
|
8200
|
+
} : {
|
|
8201
|
+
NORTH: "EAST",
|
|
8202
|
+
EAST: "SOUTH",
|
|
8203
|
+
SOUTH: "WEST",
|
|
8204
|
+
WEST: "NORTH"
|
|
8205
|
+
};
|
|
8206
|
+
for (const node of graph.nodes) {
|
|
8207
|
+
if (!node.ports) continue;
|
|
8208
|
+
const swept = order.physicalPortOrderByNodeId?.get(node.id);
|
|
8209
|
+
if (!swept) {
|
|
8210
|
+
sweptPortRanks.delete(node.ports);
|
|
8211
|
+
continue;
|
|
8212
|
+
}
|
|
8213
|
+
const index = new Map(swept.flatMap((id, i) => connected.has(id) ? [[id, i]] : []));
|
|
8214
|
+
const rankByPort = new Map(node.ports.map((port) => [port, index.get(JSON.stringify([node.id, port.name]))]));
|
|
8215
|
+
sweptPortRanks.set(node.ports, (port, side) => {
|
|
8216
|
+
const rank = rankByPort.get(port);
|
|
8217
|
+
if (rank === void 0) return void 0;
|
|
8218
|
+
const alongFlow = (side === "NORTH" || side === "SOUTH") !== vertical$1;
|
|
8219
|
+
const c$1 = canonical[side];
|
|
8220
|
+
return (alongFlow ? (c$1 === "NORTH" ? 1 : -1) * (reverse ? -1 : 1) : c$1 === "EAST" ? 1 : -1) * rank;
|
|
8221
|
+
});
|
|
8222
|
+
}
|
|
8223
|
+
}
|
|
7927
8224
|
function placePorts(ports, rect, direction, portSettings, nodeSettings, portDirection) {
|
|
7928
8225
|
if (!ports) return void 0;
|
|
7929
8226
|
const horizontal$1 = direction === "left" || direction === "right";
|
|
@@ -7950,9 +8247,12 @@ function placePorts(ports, rect, direction, portSettings, nodeSettings, portDire
|
|
|
7950
8247
|
group.push(port);
|
|
7951
8248
|
grouped.set(side, group);
|
|
7952
8249
|
}
|
|
8250
|
+
const swept = constraints === "FIXED_ORDER" || constraints === "FIXED_POS" || constraints === "FIXED_RATIO" ? void 0 : sweptPortRanks.get(ports);
|
|
7953
8251
|
for (const group of grouped.values()) {
|
|
7954
|
-
group.sort((left, right) => Number(portSettings?.(left)?.["port.index"] ?? ports.indexOf(left)) - Number(portSettings?.(right)?.["port.index"] ?? ports.indexOf(right)));
|
|
7955
8252
|
const side = sideByPort.get(group[0]);
|
|
8253
|
+
const ranks = swept && group.map((port) => swept(port, side));
|
|
8254
|
+
const ranked = ranks?.every((rank) => rank !== void 0) && group.every((port) => portSettings?.(port)?.["port.index"] === void 0) ? new Map(group.map((port, i) => [port, ranks[i]])) : void 0;
|
|
8255
|
+
group.sort((left, right) => ranked ? ranked.get(left) - ranked.get(right) : Number(portSettings?.(left)?.["port.index"] ?? ports.indexOf(left)) - Number(portSettings?.(right)?.["port.index"] ?? ports.indexOf(right)));
|
|
7956
8256
|
if (constraints === "FIXED_ORDER" && (side === "SOUTH" || side === "WEST")) group.reverse();
|
|
7957
8257
|
}
|
|
7958
8258
|
return ports.map((port) => {
|
|
@@ -12767,13 +13067,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
12767
13067
|
if (routeAllowed) {
|
|
12768
13068
|
if (options.settings?.edgeRouting === "SPLINES" || options.strategies?.routeEdges) throw new require_routing.UnsupportedLayoutError("Partial layout supports its built-in orthogonal router only");
|
|
12769
13069
|
const ancestorIds = new Set([...ancestors(source.id), ...ancestors(target.id)]);
|
|
12770
|
-
const nodeObstacles = graph.nodes.filter((n) => !ancestorIds.has(n.id))
|
|
12771
|
-
...n,
|
|
12772
|
-
x: n.x - 1,
|
|
12773
|
-
y: n.y - 1,
|
|
12774
|
-
width: n.width + 2,
|
|
12775
|
-
height: n.height + 2
|
|
12776
|
-
});
|
|
13070
|
+
const nodeObstacles = graph.nodes.filter((n) => !ancestorIds.has(n.id));
|
|
12777
13071
|
const labelObstacles = [...edgeById.values()].filter((e) => e.id !== edge.id && e.width > 0 && e.height > 0);
|
|
12778
13072
|
const required = waypoints.filter((c$1) => (c$1.strength ?? "required") === "required");
|
|
12779
13073
|
const accepted = new Set(required.map((c$1) => c$1.id));
|
|
@@ -13803,7 +14097,7 @@ function switchCenterLabelDummies(expansion, labels, order) {
|
|
|
13803
14097
|
//#endregion
|
|
13804
14098
|
//#region src/layered/north-south-ports.ts
|
|
13805
14099
|
/** Detach fixed cross-axis ports into same-layer units before crossing minimization. */
|
|
13806
|
-
function insertNorthSouthPortDummies(expansion) {
|
|
14100
|
+
function insertNorthSouthPortDummies(expansion, sortedPorts) {
|
|
13807
14101
|
const { input, orientation } = expansion;
|
|
13808
14102
|
const vertical$1 = input.direction === "down" || input.direction === "up";
|
|
13809
14103
|
const forward = input.direction === "right" ? "EAST" : input.direction === "left" ? "WEST" : input.direction === "down" ? "SOUTH" : "NORTH";
|
|
@@ -13914,13 +14208,16 @@ function insertNorthSouthPortDummies(expansion) {
|
|
|
13914
14208
|
for (const node of nodes) if (origins.has(node.id)) node.ports?.sort((a, b) => Number(a.name === "output") - Number(b.name === "output"));
|
|
13915
14209
|
const roleOrder = (origin) => origin.input && origin.output ? 2 : origin.input ? 0 : 1;
|
|
13916
14210
|
const negativeFlow = input.direction === "left" || input.direction === "up";
|
|
14211
|
+
const modelOrder = (input.settings["considerModelOrder.strategy"] ?? "NONE") !== "NONE";
|
|
13917
14212
|
const orderedOrigins = input.graph.nodes.flatMap((node) => {
|
|
13918
14213
|
const entries = [...origins].filter(([, origin]) => origin.node.id === node.id);
|
|
13919
14214
|
const constraints = input.nodeSettings?.(node)?.portConstraints;
|
|
14215
|
+
const sorted = sortedPorts?.get(node.id);
|
|
13920
14216
|
const coordinate = (origin) => {
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
14217
|
+
const flip = modelOrder && origin.output && !origin.input ? -1 : 1;
|
|
14218
|
+
if (constraints === "FIXED_POS" || constraints === "FIXED_RATIO") return (vertical$1 ? origin.port.y ?? 0 : origin.port.x ?? 0) * (negativeFlow ? -1 : 1) * flip;
|
|
14219
|
+
const index = sorted ? sorted.indexOf(JSON.stringify([node.id, origin.port.name])) : node.ports?.indexOf(origin.port) ?? 0;
|
|
14220
|
+
return (origin.beforeOwner ? index : -index) * flip;
|
|
13924
14221
|
};
|
|
13925
14222
|
return entries.sort(([, a], [, b]) => Number(!a.beforeOwner) - Number(!b.beforeOwner) || roleOrder(a) - roleOrder(b) || coordinate(a) - coordinate(b));
|
|
13926
14223
|
});
|
|
@@ -16235,9 +16532,10 @@ function opposed(a, b) {
|
|
|
16235
16532
|
* A reversed feedback edge split by label or inverted-port dummies is routed
|
|
16236
16533
|
* one segment at a time, so each segment detours to the outer feedback track
|
|
16237
16534
|
* and back to the next dummy. Rejoin the chain as one track: the label's own
|
|
16238
|
-
* track, or the outer track with the label moved onto it, whichever is clear
|
|
16535
|
+
* track, or the outer track with the label moved onto it, whichever is clear;
|
|
16536
|
+
* failing both, a fresh track past everything else with the label on it.
|
|
16239
16537
|
*/
|
|
16240
|
-
function straightenFeedbackChains(chains, pointsByEdgeId, rectByNodeId, horizontal$1) {
|
|
16538
|
+
function straightenFeedbackChains(chains, pointsByEdgeId, rectByNodeId, horizontal$1, spacing) {
|
|
16241
16539
|
const cross = (p) => horizontal$1 ? p.y : p.x;
|
|
16242
16540
|
const flow = (p) => horizontal$1 ? p.x : p.y;
|
|
16243
16541
|
const at = (f, c$1) => horizontal$1 ? {
|
|
@@ -16256,7 +16554,12 @@ function straightenFeedbackChains(chains, pointsByEdgeId, rectByNodeId, horizont
|
|
|
16256
16554
|
const tail = points.at(-2), end = points.at(-1);
|
|
16257
16555
|
const outer = Math.max(...points.map(cross));
|
|
16258
16556
|
const labelRect = label && rectByNodeId.get(label.id);
|
|
16259
|
-
|
|
16557
|
+
const tracks = label ? [label.cross, outer] : [outer];
|
|
16558
|
+
if (labelRect) {
|
|
16559
|
+
const farthest = Math.max(...[...pointsByEdgeId.values()].flatMap((other) => other.map(cross)), ...[...rectByNodeId.values()].map((rect) => horizontal$1 ? rect.y + rect.height : rect.x + rect.width));
|
|
16560
|
+
tracks.push(farthest + spacing + label.cross - (horizontal$1 ? labelRect.y : labelRect.x));
|
|
16561
|
+
}
|
|
16562
|
+
for (const track of tracks) {
|
|
16260
16563
|
const route = [
|
|
16261
16564
|
start,
|
|
16262
16565
|
lead,
|
|
@@ -16285,7 +16588,7 @@ function straightenFeedbackChains(chains, pointsByEdgeId, rectByNodeId, horizont
|
|
|
16285
16588
|
}
|
|
16286
16589
|
if (simple.at(-1) !== end) continue;
|
|
16287
16590
|
if (simple.length >= points.length) break;
|
|
16288
|
-
if ([...pointsByEdgeId].some(([id, other]) => id !== edgeId && opposed(simple, other))) continue;
|
|
16591
|
+
if ([...pointsByEdgeId].some(([id, other]) => id !== edgeId && opposed(simple, other) && !opposed(points, other))) continue;
|
|
16289
16592
|
pointsByEdgeId.set(edgeId, simple);
|
|
16290
16593
|
if (moved) rectByNodeId.set(label.id, moved);
|
|
16291
16594
|
break;
|
|
@@ -18654,53 +18957,100 @@ function runSeparatedComponents(graph, options, context) {
|
|
|
18654
18957
|
bottom: options.padding?.bottom ?? 12,
|
|
18655
18958
|
left: options.padding?.left ?? 12
|
|
18656
18959
|
};
|
|
18960
|
+
const direction = options.direction ?? graph.direction ?? "right";
|
|
18961
|
+
const drawnRects = (result) => {
|
|
18962
|
+
const rects = [];
|
|
18963
|
+
for (const node of result.nodes) {
|
|
18964
|
+
const x$1 = node.x ?? 0, y$1 = node.y ?? 0;
|
|
18965
|
+
rects.push({
|
|
18966
|
+
x: x$1,
|
|
18967
|
+
y: y$1,
|
|
18968
|
+
width: node.width ?? 0,
|
|
18969
|
+
height: node.height ?? 0
|
|
18970
|
+
});
|
|
18971
|
+
for (const port of node.ports ?? []) rects.push({
|
|
18972
|
+
x: x$1 + (port.x ?? 0),
|
|
18973
|
+
y: y$1 + (port.y ?? 0),
|
|
18974
|
+
width: port.width ?? 0,
|
|
18975
|
+
height: port.height ?? 0
|
|
18976
|
+
});
|
|
18977
|
+
}
|
|
18978
|
+
for (const edge of result.edges) {
|
|
18979
|
+
const points = edge.points ?? [];
|
|
18980
|
+
for (let index = 1; index < points.length; index++) {
|
|
18981
|
+
const from = points[index - 1], to = points[index];
|
|
18982
|
+
const x$1 = Math.min(from.x, to.x), y$1 = Math.min(from.y, to.y);
|
|
18983
|
+
rects.push({
|
|
18984
|
+
x: x$1,
|
|
18985
|
+
y: y$1,
|
|
18986
|
+
width: Math.max(from.x, to.x) - x$1,
|
|
18987
|
+
height: Math.max(from.y, to.y) - y$1
|
|
18988
|
+
});
|
|
18989
|
+
}
|
|
18990
|
+
if ((edge.width ?? 0) > 0 || (edge.height ?? 0) > 0) rects.push({
|
|
18991
|
+
x: edge.x ?? 0,
|
|
18992
|
+
y: edge.y ?? 0,
|
|
18993
|
+
width: edge.width ?? 0,
|
|
18994
|
+
height: edge.height ?? 0
|
|
18995
|
+
});
|
|
18996
|
+
}
|
|
18997
|
+
return rects;
|
|
18998
|
+
};
|
|
18999
|
+
const rows = [];
|
|
18657
19000
|
let x = 0;
|
|
19001
|
+
for (const component of components) {
|
|
19002
|
+
if (x > 0 && x + component.bounds.width > maxRowWidth) x = 0;
|
|
19003
|
+
if (x === 0) rows.push([]);
|
|
19004
|
+
rows.at(-1).push({
|
|
19005
|
+
component,
|
|
19006
|
+
x
|
|
19007
|
+
});
|
|
19008
|
+
x += component.bounds.width + componentSpacing;
|
|
19009
|
+
}
|
|
18658
19010
|
let y = 0;
|
|
18659
|
-
let rowHeight = 0;
|
|
18660
19011
|
const compactConnectedComponents = options.settings?.["compaction.connectedComponents"] === true;
|
|
18661
19012
|
const placedShapes = [];
|
|
18662
19013
|
const nodeResults = /* @__PURE__ */ new Map();
|
|
18663
19014
|
const edgeResults = /* @__PURE__ */ new Map();
|
|
18664
19015
|
const placements = [];
|
|
18665
|
-
for (const
|
|
18666
|
-
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
19016
|
+
for (const row of rows) {
|
|
19017
|
+
const rowWidth = Math.max(...row.map((entry) => entry.x + entry.component.bounds.width));
|
|
19018
|
+
let rowHeight = 0;
|
|
19019
|
+
for (const { component, x: packedX } of row) {
|
|
19020
|
+
const { bounds } = component;
|
|
19021
|
+
const left = direction === "left" ? rowWidth - packedX - bounds.width : packedX;
|
|
19022
|
+
const shapes = drawnRects(component.result).map((rect) => ({
|
|
19023
|
+
left: left + rect.x - bounds.left,
|
|
19024
|
+
top: direction === "up" ? bounds.top + bounds.height - rect.y - rect.height : rect.y - bounds.top,
|
|
19025
|
+
width: rect.width,
|
|
19026
|
+
height: rect.height
|
|
19027
|
+
}));
|
|
19028
|
+
let compactedY = y;
|
|
19029
|
+
if (compactConnectedComponents && y > 0) {
|
|
19030
|
+
let requiredY = 0;
|
|
19031
|
+
for (const shape of shapes) for (const placed of placedShapes) {
|
|
19032
|
+
if (shape.left + shape.width <= placed.left || shape.left >= placed.right) continue;
|
|
19033
|
+
requiredY = Math.max(requiredY, placed.bottom + componentSpacing - shape.top);
|
|
18681
19034
|
}
|
|
19035
|
+
compactedY = Math.min(y, requiredY);
|
|
18682
19036
|
}
|
|
18683
|
-
|
|
19037
|
+
for (const shape of shapes) placedShapes.push({
|
|
19038
|
+
left: shape.left,
|
|
19039
|
+
right: shape.left + shape.width,
|
|
19040
|
+
top: compactedY + shape.top,
|
|
19041
|
+
bottom: compactedY + shape.top + shape.height
|
|
19042
|
+
});
|
|
19043
|
+
placements.push({
|
|
19044
|
+
component,
|
|
19045
|
+
x: left,
|
|
19046
|
+
y: compactedY
|
|
19047
|
+
});
|
|
19048
|
+
rowHeight = Math.max(rowHeight, bounds.height);
|
|
18684
19049
|
}
|
|
18685
|
-
|
|
18686
|
-
left: x + (node.x ?? 0) - component.bounds.left,
|
|
18687
|
-
right: x + (node.x ?? 0) - component.bounds.left + (node.width ?? 0),
|
|
18688
|
-
top: compactedY + (node.y ?? 0) - component.bounds.top,
|
|
18689
|
-
bottom: compactedY + (node.y ?? 0) - component.bounds.top + (node.height ?? 0)
|
|
18690
|
-
});
|
|
18691
|
-
placements.push({
|
|
18692
|
-
component,
|
|
18693
|
-
x,
|
|
18694
|
-
y: compactedY
|
|
18695
|
-
});
|
|
18696
|
-
x += component.bounds.width + componentSpacing;
|
|
18697
|
-
rowHeight = Math.max(rowHeight, component.bounds.height);
|
|
19050
|
+
y += rowHeight + componentSpacing;
|
|
18698
19051
|
}
|
|
18699
|
-
const direction = options.direction ?? graph.direction ?? "right";
|
|
18700
|
-
const packedWidth = Math.max(0, ...placements.map((p) => p.x + p.component.bounds.width));
|
|
18701
19052
|
const packedHeight = Math.max(0, ...placements.map((p) => p.y + p.component.bounds.height));
|
|
18702
|
-
for (const { component, x:
|
|
18703
|
-
const left = direction === "left" ? packedWidth - packedX - component.bounds.width : packedX;
|
|
19053
|
+
for (const { component, x: left, y: packedY } of placements) {
|
|
18704
19054
|
const top = direction === "up" ? packedHeight - packedY - component.bounds.height : packedY;
|
|
18705
19055
|
const dx = padding.left + left - component.bounds.left;
|
|
18706
19056
|
const dy = padding.top + top - component.bounds.top;
|
|
@@ -18751,7 +19101,11 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
18751
19101
|
* coordinator can prepare every scope, share sweep state, then resume each
|
|
18752
19102
|
* scope with its final order. The ordinary entry point drains this same pipeline.
|
|
18753
19103
|
*/
|
|
18754
|
-
function* createLayeredScopePipeline(graph, options, context
|
|
19104
|
+
function* createLayeredScopePipeline(graph, options, context, fallbacks = {
|
|
19105
|
+
antiparallelPairs: /* @__PURE__ */ new Set(),
|
|
19106
|
+
feedbackSegments: /* @__PURE__ */ new Set()
|
|
19107
|
+
}) {
|
|
19108
|
+
const scopeOptions = options;
|
|
18755
19109
|
if ((options.settings?.["spacing.edgeEdge"] ?? 10) < 2) options = {
|
|
18756
19110
|
...options,
|
|
18757
19111
|
settings: {
|
|
@@ -18801,6 +19155,8 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18801
19155
|
const separated$1 = runSeparatedComponents(graph, options, context);
|
|
18802
19156
|
if (separated$1) return separated$1;
|
|
18803
19157
|
}
|
|
19158
|
+
const hiddenLoopPorts$1 = hideSelfLoopPorts(graph, options);
|
|
19159
|
+
if (hiddenLoopPorts$1) return restoreSelfLoopPorts(graph, yield* createLayeredScopePipeline(hiddenLoopPorts$1.graph, hiddenLoopPorts$1.options, context, fallbacks), hiddenLoopPorts$1.edges);
|
|
18804
19160
|
const direction = options.direction ?? graph.direction ?? "right";
|
|
18805
19161
|
const padding = typeof options.padding === "number" ? {
|
|
18806
19162
|
top: options.padding,
|
|
@@ -18989,7 +19345,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18989
19345
|
}
|
|
18990
19346
|
};
|
|
18991
19347
|
}
|
|
18992
|
-
const northSouth = insertNorthSouthPortDummies(expanded);
|
|
19348
|
+
const northSouth = insertNorthSouthPortDummies(expanded, preparedPorts);
|
|
18993
19349
|
expanded = northSouth.expansion;
|
|
18994
19350
|
if (prepareModelOrder) markInitialModelOrderPrepared(expanded.input, preparedPorts);
|
|
18995
19351
|
const associates = /* @__PURE__ */ new Map(), unitMembers = /* @__PURE__ */ new Map();
|
|
@@ -19051,6 +19407,8 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19051
19407
|
minimize: minimizeScope,
|
|
19052
19408
|
finishOrder: finishCrossingOrder
|
|
19053
19409
|
};
|
|
19410
|
+
const crossingOrder = order;
|
|
19411
|
+
recordSweptPortOrder(graph, direction, order);
|
|
19054
19412
|
traceLayeredPhase(() => ({
|
|
19055
19413
|
kind: "crossing-order",
|
|
19056
19414
|
scope: graph.id,
|
|
@@ -19295,6 +19653,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19295
19653
|
const edgeRouting = options.settings?.edgeRouting ?? "ORTHOGONAL";
|
|
19296
19654
|
const edgeRouter = options.strategies?.routeEdges ?? (edgeRouting === "POLYLINE" ? routeEdgesWithPolylines : edgeRouting === "SPLINES" ? routeEdgesWithSplines : routeEdgesOrthogonally);
|
|
19297
19655
|
if (options.strategies?.routeEdges) measure("post-compaction", () => applyPostCompaction(expanded.input, placement));
|
|
19656
|
+
const labelPortSideSegments = trackLabelPortSideFeedback(expanded.input, fallbacks.feedbackSegments);
|
|
19298
19657
|
let expandedRoutes = measure("edge-routing", () => edgeRouter(expanded.input, expanded.orientation, placement));
|
|
19299
19658
|
const restoredJunctionCounts = /* @__PURE__ */ new Map();
|
|
19300
19659
|
expandedRoutes = restoreNorthSouthPortRoutes({
|
|
@@ -19342,6 +19701,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19342
19701
|
const outerAntiparallelLabelIds = /* @__PURE__ */ new Set();
|
|
19343
19702
|
const parallelLabelPositions = /* @__PURE__ */ new Map();
|
|
19344
19703
|
const postCompactionNodeCrossDeltas = /* @__PURE__ */ new Map();
|
|
19704
|
+
const flowOrderedPairs = /* @__PURE__ */ new Map();
|
|
19345
19705
|
const horizontalAntiparallelFlow = direction === "left" || direction === "right";
|
|
19346
19706
|
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") {
|
|
19347
19707
|
const neighbors = new Map(graph.nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
|
|
@@ -19411,6 +19771,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19411
19771
|
const horizontal$1 = horizontalAntiparallelFlow;
|
|
19412
19772
|
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
19413
19773
|
const pairEdges = /* @__PURE__ */ new Map();
|
|
19774
|
+
const flowOrder = direction === "left" || direction === "up";
|
|
19414
19775
|
for (const edge of graph.edges) {
|
|
19415
19776
|
if (edge.sourceId === edge.targetId || (edge.width ?? 0) <= 0 || (edge.height ?? 0) <= 0) continue;
|
|
19416
19777
|
const key = [edge.sourceId, edge.targetId].sort().join("\0");
|
|
@@ -19418,7 +19779,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19418
19779
|
pair.push(edge);
|
|
19419
19780
|
pairEdges.set(key, pair);
|
|
19420
19781
|
}
|
|
19421
|
-
for (const pair of pairEdges
|
|
19782
|
+
for (const [pairKey, pair] of pairEdges) {
|
|
19422
19783
|
if (pair.length !== 2 || pair[0].sourceId !== pair[1].targetId || pair[0].targetId !== pair[1].sourceId || pair.some((edge) => {
|
|
19423
19784
|
const settings = options.edgeSettings?.(edge);
|
|
19424
19785
|
return (settings?.["edgeLabels.placement"] ?? "CENTER") !== "CENTER" || settings?.["edgeLabels.inline"] !== true;
|
|
@@ -19436,11 +19797,13 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19436
19797
|
};
|
|
19437
19798
|
const hasFlexiblePairPorts = hasFlexiblePorts(firstNode) && hasFlexiblePorts(secondNode);
|
|
19438
19799
|
if (isolatedAntiparallelPair && !hasFlexiblePairPorts) continue;
|
|
19439
|
-
const
|
|
19800
|
+
const pairFlowOrder = flowOrder && !fallbacks.antiparallelPairs.has(pairKey);
|
|
19801
|
+
const firstBeforeSecond = (horizontal$1 ? firstRect.x + firstRect.width / 2 < secondRect.x + secondRect.width / 2 : firstRect.y + firstRect.height / 2 < secondRect.y + secondRect.height / 2) !== pairFlowOrder;
|
|
19440
19802
|
const beforeRect = firstBeforeSecond ? firstRect : secondRect;
|
|
19441
19803
|
const afterRect = firstBeforeSecond ? secondRect : firstRect;
|
|
19442
19804
|
const forward = pair.find((edge) => firstBeforeSecond ? edge.sourceId === pair[0].sourceId : edge.sourceId === pair[0].targetId);
|
|
19443
19805
|
if (!forward?.sourcePort || !forward.targetPort) continue;
|
|
19806
|
+
if (pairFlowOrder) flowOrderedPairs.set(pairKey, [firstNode.id, secondNode.id]);
|
|
19444
19807
|
let cross = Math.min(horizontal$1 ? firstRect.y : firstRect.x, horizontal$1 ? secondRect.y : secondRect.x);
|
|
19445
19808
|
const edgeSpacing = Number(options.settings?.["spacing.edgeEdge"] ?? 10);
|
|
19446
19809
|
for (const edge of pair) {
|
|
@@ -19549,7 +19912,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19549
19912
|
cross: horizontal$1 ? anchor.y : anchor.x
|
|
19550
19913
|
}
|
|
19551
19914
|
}];
|
|
19552
|
-
}), routes.pointsByEdgeId, placement.rectByNodeId, horizontal$1);
|
|
19915
|
+
}), routes.pointsByEdgeId, placement.rectByNodeId, horizontal$1, Number(options.settings?.["spacing.edgeEdge"] ?? 10));
|
|
19553
19916
|
}
|
|
19554
19917
|
if (options.settings?.["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT") {
|
|
19555
19918
|
const horizontal$1 = direction === "right" || direction === "left";
|
|
@@ -19651,8 +20014,11 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19651
20014
|
}
|
|
19652
20015
|
}
|
|
19653
20016
|
if ((options.settings?.["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING" && !unzippingFanIn && edgeRouting === "ORTHOGONAL") routes = { pointsByEdgeId: adjustUnzippedSinkRoutes(graph, routes.pointsByEdgeId, direction, Number(options.settings?.["spacing.edgeNodeBetweenLayers"] ?? 10), Number(options.settings?.["spacing.edgeEdgeBetweenLayers"] ?? 10)) };
|
|
20017
|
+
const connectsOnlyTo = (nodeId, otherId) => graph.edges.every((edge) => edge.sourceId === edge.targetId || edge.sourceId !== nodeId && edge.targetId !== nodeId || edge.sourceId === otherId || edge.targetId === otherId);
|
|
19654
20018
|
for (const pair of labeledEdgesByPair.values()) {
|
|
19655
20019
|
if (pair.length < 2) continue;
|
|
20020
|
+
const { sourceId, targetId } = pair[0];
|
|
20021
|
+
if (!connectsOnlyTo(sourceId, targetId) || !connectsOnlyTo(targetId, sourceId)) continue;
|
|
19656
20022
|
const sourceRect = mutableRects.get(pair[0].sourceId);
|
|
19657
20023
|
const targetRect = mutableRects.get(pair[0].targetId);
|
|
19658
20024
|
if (!sourceRect || !targetRect) continue;
|
|
@@ -19872,7 +20238,7 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19872
20238
|
y: (points[1].y + points[2].y - height) / 2
|
|
19873
20239
|
} : void 0;
|
|
19874
20240
|
const movableExteriorLoopLabelPosition = (() => {
|
|
19875
|
-
if (edgeRouting !== "ORTHOGONAL" || labelPlacement !== "CENTER" ||
|
|
20241
|
+
if (edgeRouting !== "ORTHOGONAL" || labelPlacement !== "CENTER" || !hasMovableLoopPorts(input, edge) || !sourceRect || points.length !== 4) return void 0;
|
|
19876
20242
|
const first = points[1], last = points[2];
|
|
19877
20243
|
if (first.y === last.y) {
|
|
19878
20244
|
const low = first.y < sourceRect.y;
|
|
@@ -19950,6 +20316,28 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19950
20316
|
};
|
|
19951
20317
|
}
|
|
19952
20318
|
});
|
|
20319
|
+
if (edgeRouting === "ORTHOGONAL") {
|
|
20320
|
+
const diagonalEdges = edges.filter((edge) => countDiagonals(edge.points) > 0);
|
|
20321
|
+
const antiparallelPairs = [...flowOrderedPairs].flatMap(([key, nodeIds]) => diagonalEdges.some((edge) => nodeIds.includes(edge.sourceId) || nodeIds.includes(edge.targetId)) ? [key] : []);
|
|
20322
|
+
const feedbackSegments = diagonalEdges.flatMap((edge) => {
|
|
20323
|
+
const segments = expanded.segmentIdsByEdgeId.get(edge.id) ?? [edge.id];
|
|
20324
|
+
return segments.some((id) => labelPortSideSegments.has(id)) ? segments : [];
|
|
20325
|
+
});
|
|
20326
|
+
if (antiparallelPairs.length > 0 || feedbackSegments.length > 0) {
|
|
20327
|
+
const retry = createLayeredScopePipeline(graph, scopeOptions, context && {
|
|
20328
|
+
...context,
|
|
20329
|
+
diagnostics: []
|
|
20330
|
+
}, {
|
|
20331
|
+
antiparallelPairs: new Set([...fallbacks.antiparallelPairs, ...antiparallelPairs]),
|
|
20332
|
+
feedbackSegments: new Set([...fallbacks.feedbackSegments, ...feedbackSegments])
|
|
20333
|
+
});
|
|
20334
|
+
const prepared = retry.next();
|
|
20335
|
+
const resumed = prepared.done ? prepared : retry.next(crossingOrder);
|
|
20336
|
+
if (!resumed.done) throw new Error("Layered scope yielded more than one crossing phase");
|
|
20337
|
+
const diagonals = (routed) => routed.reduce((sum, edge) => sum + countDiagonals(edge.points), 0);
|
|
20338
|
+
if (diagonals(resumed.value.edges) < diagonals(edges)) return resumed.value;
|
|
20339
|
+
}
|
|
20340
|
+
}
|
|
19953
20341
|
return setElkjs0111ResultPolicy({
|
|
19954
20342
|
...graph,
|
|
19955
20343
|
direction,
|
|
@@ -19963,6 +20351,15 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
19963
20351
|
junctionPointsByEdgeId: routes.junctionPointsByEdgeId
|
|
19964
20352
|
});
|
|
19965
20353
|
}
|
|
20354
|
+
function countDiagonals(points) {
|
|
20355
|
+
let count = 0;
|
|
20356
|
+
for (let index = 1; index < (points?.length ?? 0); index++) {
|
|
20357
|
+
const a = points[index - 1];
|
|
20358
|
+
const b = points[index];
|
|
20359
|
+
if (Math.abs(a.x - b.x) > 1e-6 && Math.abs(a.y - b.y) > 1e-6) count++;
|
|
20360
|
+
}
|
|
20361
|
+
return count;
|
|
20362
|
+
}
|
|
19966
20363
|
/**
|
|
19967
20364
|
* Deterministic native layered layout for an `@statelyai/graph` graph.
|
|
19968
20365
|
*
|