@statelyai/layout 0.5.0 → 0.5.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/dist/elkjs/index.cjs +2 -2
- 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-DonMqGjf.cjs → elkjs--K57ydUJ.cjs} +2 -2
- package/dist/{elkjs-DBkalvt1.mjs → elkjs-C243vQwg.mjs} +2 -2
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/layered/index.cjs +1 -1
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-SB2mlAqt.cjs → layered-B1AE7Zpr.cjs} +278 -37
- package/dist/{layered-CmL7oynw.mjs → layered-BtnmL9_b.mjs} +278 -37
- package/dist/{layout-engine-BXoIpYL4.mjs → layout-engine-CHkJkgG6.mjs} +1 -1
- package/dist/{layout-engine-CAyJ-I-u.cjs → layout-engine-Dk5EklIn.cjs} +1 -1
- package/package.json +1 -1
- package/src/layered/index.ts +38 -7
- package/src/layered/loop-envelopes.ts +107 -26
- package/src/layered/self-loop-ports.ts +158 -0
- package/src/layered/strategies.ts +40 -17
|
@@ -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;
|
|
@@ -1630,14 +1796,14 @@ function loopEnvelopes(input) {
|
|
|
1630
1796
|
const allLoops = loopsByNodeId.get(node.id)?.slice() ?? [];
|
|
1631
1797
|
const constraints = input.nodeSettings?.(node)?.portConstraints;
|
|
1632
1798
|
if (allLoops.length && constraints && constraints !== "FREE" && constraints !== "UNDEFINED" && allLoops.every((edge) => edge.sourcePort !== void 0 && edge.targetPort !== void 0 && input.edgeSettings?.(edge)?.["edgeLabels.inline"] !== true && ((edge.width ?? 0) === 0 && (edge.height ?? 0) === 0 || (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER"))) {
|
|
1633
|
-
const size$
|
|
1799
|
+
const size$2 = input.sizes.get(node.id) ?? {
|
|
1634
1800
|
width: 0,
|
|
1635
1801
|
height: 0
|
|
1636
1802
|
};
|
|
1637
1803
|
const rect = {
|
|
1638
1804
|
x: 0,
|
|
1639
1805
|
y: 0,
|
|
1640
|
-
...size$
|
|
1806
|
+
...size$2
|
|
1641
1807
|
};
|
|
1642
1808
|
const envelope$1 = {
|
|
1643
1809
|
before: 0,
|
|
@@ -1645,9 +1811,9 @@ function loopEnvelopes(input) {
|
|
|
1645
1811
|
flowBefore: 0,
|
|
1646
1812
|
flowAfter: 0
|
|
1647
1813
|
};
|
|
1648
|
-
const horizontal$
|
|
1814
|
+
const horizontal$2 = 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;
|
|
@@ -1676,13 +1842,13 @@ function loopEnvelopes(input) {
|
|
|
1676
1842
|
});
|
|
1677
1843
|
}
|
|
1678
1844
|
for (const point of route) {
|
|
1679
|
-
const cross = horizontal$
|
|
1680
|
-
const flow = horizontal$
|
|
1681
|
-
const flowSize = horizontal$
|
|
1845
|
+
const cross = horizontal$2 ? point.y : point.x;
|
|
1846
|
+
const flow = horizontal$2 ? point.x : point.y;
|
|
1847
|
+
const flowSize$1 = horizontal$2 ? size$2.width : size$2.height;
|
|
1682
1848
|
envelope$1.before = Math.max(envelope$1.before, -cross);
|
|
1683
|
-
envelope$1.after = Math.max(envelope$1.after, cross - (horizontal$
|
|
1684
|
-
envelope$1.flowBefore = Math.max(envelope$1.flowBefore, reverse ? flow - flowSize : -flow);
|
|
1685
|
-
envelope$1.flowAfter = Math.max(envelope$1.flowAfter, reverse ? -flow : flow - flowSize);
|
|
1849
|
+
envelope$1.after = Math.max(envelope$1.after, cross - (horizontal$2 ? size$2.height : size$2.width));
|
|
1850
|
+
envelope$1.flowBefore = Math.max(envelope$1.flowBefore, reverse ? flow - flowSize$1 : -flow);
|
|
1851
|
+
envelope$1.flowAfter = Math.max(envelope$1.flowAfter, reverse ? -flow : flow - flowSize$1);
|
|
1686
1852
|
}
|
|
1687
1853
|
}
|
|
1688
1854
|
if (supported) {
|
|
@@ -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,28 @@ 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
|
+
};
|
|
1882
|
+
const horizontal$1 = input.direction === "right" || input.direction === "left";
|
|
1883
|
+
const size$1 = input.sizes.get(node.id) ?? {
|
|
1884
|
+
width: 0,
|
|
1885
|
+
height: 0
|
|
1886
|
+
};
|
|
1887
|
+
const flowSize = horizontal$1 ? size$1.width : size$1.height;
|
|
1711
1888
|
for (const side of sides$1) {
|
|
1712
1889
|
const selected = loops.filter((_, index) => sides$1[index % sides$1.length] === side);
|
|
1713
|
-
const
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1890
|
+
const canonical = canonicalSides[side];
|
|
1891
|
+
envelope[side] = Math.max(envelope[side], ...selected.map((edge) => movableLoopTrack(input, selected, edge, canonical, ordering) + movableLoopLabelExtent(input, edge, canonical)));
|
|
1892
|
+
if (!crossSide(canonical)) continue;
|
|
1893
|
+
for (const edge of selected) {
|
|
1894
|
+
const overhang = ((horizontal$1 ? edge.width : edge.height) ?? 0) - flowSize;
|
|
1895
|
+
envelope.flowBefore = Math.max(envelope.flowBefore, overhang / 2);
|
|
1896
|
+
envelope.flowAfter = Math.max(envelope.flowAfter, overhang / 2);
|
|
1897
|
+
}
|
|
1717
1898
|
}
|
|
1718
1899
|
envelopes.set(node.id, envelope);
|
|
1719
1900
|
}
|
|
@@ -2881,7 +3062,7 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
2881
3062
|
if (point.x >= rect.x + rect.width - 1e-9) return "EAST";
|
|
2882
3063
|
return point.y <= rect.y ? "NORTH" : "SOUTH";
|
|
2883
3064
|
};
|
|
2884
|
-
const crossSide = (side) => side === "NORTH" || side === "SOUTH";
|
|
3065
|
+
const crossSide$1 = (side) => side === "NORTH" || side === "SOUTH";
|
|
2885
3066
|
const segments = [];
|
|
2886
3067
|
for (const [edgeId, points] of canonicalRoutes) {
|
|
2887
3068
|
const edge = edgeById.get(edgeId);
|
|
@@ -2891,8 +3072,8 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
2891
3072
|
const first = points[index];
|
|
2892
3073
|
const second = points[index + 1];
|
|
2893
3074
|
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;
|
|
3075
|
+
if (index === 0 && !crossSide$1(endpointSide(edge.sourceId, edge.sourcePort, first))) continue;
|
|
3076
|
+
if (index === points.length - 2 && !crossSide$1(endpointSide(edge.targetId, edge.targetPort, second))) continue;
|
|
2896
3077
|
const owner = index === 0 ? nodes.get(edge.sourceId) : index === points.length - 2 ? nodes.get(edge.targetId) : void 0;
|
|
2897
3078
|
const ownerSide = owner ? endpointSide(index === 0 ? edge.sourceId : edge.targetId, index === 0 ? edge.sourcePort : edge.targetPort, index === 0 ? first : second) : void 0;
|
|
2898
3079
|
const bend = index === 0 ? second : first;
|
|
@@ -3055,7 +3236,7 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
3055
3236
|
const points = canonicalRoutes.get(edge.id);
|
|
3056
3237
|
const sourceSide = endpointSide(edge.sourceId, edge.sourcePort, points?.[0]);
|
|
3057
3238
|
const targetSide = endpointSide(edge.targetId, edge.targetPort, points?.at(-1));
|
|
3058
|
-
if (crossSide(sourceSide) && crossSide(targetSide)) continue;
|
|
3239
|
+
if (crossSide$1(sourceSide) && crossSide$1(targetSide)) continue;
|
|
3059
3240
|
const reversed = orientation?.reversedEdgeIds.has(edge.id) ?? source.x > target.x;
|
|
3060
3241
|
const from = reversed ? target : source, to = reversed ? source : target;
|
|
3061
3242
|
constraints.push({
|
|
@@ -3149,8 +3330,8 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
|
|
|
3149
3330
|
const start = pointToCanonical(originals[0]);
|
|
3150
3331
|
const next = pointToCanonical(originals[1]);
|
|
3151
3332
|
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;
|
|
3333
|
+
if (!crossSide$1(endpointSide(edge.sourceId, edge.sourcePort, start)) && Math.abs(start.x - next.x) < 1e-9) points[1].x = points[0].x;
|
|
3334
|
+
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
3335
|
}
|
|
3155
3336
|
}
|
|
3156
3337
|
const extent$2 = Math.max(...items.map((item) => item.x + item.width), ...[...canonicalRoutes.values()].flatMap((points) => points.map((point) => point.x)));
|
|
@@ -6427,13 +6608,13 @@ function routeEdges(style) {
|
|
|
6427
6608
|
"WEST"
|
|
6428
6609
|
][index % 4] : distribution === "NORTH_SOUTH" && index % 2 === 1 ? "SOUTH" : "NORTH";
|
|
6429
6610
|
};
|
|
6430
|
-
const loopDistance = (loops, edge, side, ordering
|
|
6431
|
-
const axis =
|
|
6611
|
+
const loopDistance = (loops, edge, side, ordering) => {
|
|
6612
|
+
const axis = side === "NORTH" || side === "SOUTH" ? "height" : "width";
|
|
6432
6613
|
const ordered = ordering === "REVERSE_STACKED" ? [...loops].reverse() : loops;
|
|
6433
6614
|
const index = ordered.indexOf(edge);
|
|
6434
6615
|
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
6435
6616
|
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
6436
|
-
const size$1 = (candidate) =>
|
|
6617
|
+
const size$1 = (candidate) => input.edgeSettings?.(candidate)?.["edgeLabels.inline"] === true ? candidate[axis] ?? 0 : 0;
|
|
6437
6618
|
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);
|
|
6438
6619
|
};
|
|
6439
6620
|
const hasFixedLoopPorts = (node) => {
|
|
@@ -6488,7 +6669,7 @@ function routeEdges(style) {
|
|
|
6488
6669
|
for (const [id, loops] of selfLoopsByNodeId) {
|
|
6489
6670
|
const rect = mutableRects.get(id);
|
|
6490
6671
|
const node = nodeById.get(id);
|
|
6491
|
-
if (!rect || !node) continue;
|
|
6672
|
+
if (!rect || !node || hasPlacementLoopEnvelope(placement, id)) continue;
|
|
6492
6673
|
const settings = input.nodeSettings?.(node);
|
|
6493
6674
|
const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
6494
6675
|
const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
@@ -7603,7 +7784,7 @@ function routeEdges(style) {
|
|
|
7603
7784
|
outsideFeedbackEdgeIds.add(edge.id);
|
|
7604
7785
|
continue;
|
|
7605
7786
|
}
|
|
7606
|
-
const movable = hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id)
|
|
7787
|
+
const movable = hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id);
|
|
7607
7788
|
const loopRect = movable ? {
|
|
7608
7789
|
x: 0,
|
|
7609
7790
|
y: 0,
|
|
@@ -7643,7 +7824,32 @@ function routeEdges(style) {
|
|
|
7643
7824
|
startRatio = (countOnSide + nestingIndex + 1) / denominator;
|
|
7644
7825
|
endRatio = (countOnSide - nestingIndex) / denominator;
|
|
7645
7826
|
}
|
|
7646
|
-
const
|
|
7827
|
+
const sideLoops = loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side);
|
|
7828
|
+
if (movable) {
|
|
7829
|
+
const track$1 = movableLoopTrack(input, sideLoops, edge, side, ordering);
|
|
7830
|
+
const sign = side === "NORTH" ? -1 : 1;
|
|
7831
|
+
const base = side === "NORTH" ? 0 : loopRect.height;
|
|
7832
|
+
const [first, last] = [startRatio, endRatio].map((ratio) => movableLoopPortOffset(input, sideLoops, side, loopRect.width, ratio));
|
|
7833
|
+
return [
|
|
7834
|
+
{
|
|
7835
|
+
x: first.along,
|
|
7836
|
+
y: base + sign * first.normal
|
|
7837
|
+
},
|
|
7838
|
+
{
|
|
7839
|
+
x: first.along,
|
|
7840
|
+
y: base + sign * track$1
|
|
7841
|
+
},
|
|
7842
|
+
{
|
|
7843
|
+
x: last.along,
|
|
7844
|
+
y: base + sign * track$1
|
|
7845
|
+
},
|
|
7846
|
+
{
|
|
7847
|
+
x: last.along,
|
|
7848
|
+
y: base + sign * last.normal
|
|
7849
|
+
}
|
|
7850
|
+
];
|
|
7851
|
+
}
|
|
7852
|
+
const distance = loopDistance(sideLoops, edge, side, ordering);
|
|
7647
7853
|
const y = side === "NORTH" ? loopRect.y - distance : loopRect.y + loopRect.height + distance;
|
|
7648
7854
|
const start$2 = {
|
|
7649
7855
|
x: loopRect.x + loopRect.width * startRatio,
|
|
@@ -7673,7 +7879,32 @@ function routeEdges(style) {
|
|
|
7673
7879
|
const secondRatio = sequenced ? 1 / denominator : 2 / denominator;
|
|
7674
7880
|
const startRatio = side === "EAST" ? firstRatio : secondRatio;
|
|
7675
7881
|
const endRatio = side === "EAST" ? secondRatio : firstRatio;
|
|
7676
|
-
const
|
|
7882
|
+
const sideLoops = loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side);
|
|
7883
|
+
if (movable) {
|
|
7884
|
+
const track$1 = movableLoopTrack(input, sideLoops, edge, side, ordering);
|
|
7885
|
+
const sign = side === "WEST" ? -1 : 1;
|
|
7886
|
+
const base = side === "WEST" ? 0 : loopRect.width;
|
|
7887
|
+
const [first, last] = [startRatio, endRatio].map((ratio) => movableLoopPortOffset(input, sideLoops, side, loopRect.height, ratio));
|
|
7888
|
+
return [
|
|
7889
|
+
{
|
|
7890
|
+
x: base + sign * first.normal,
|
|
7891
|
+
y: first.along
|
|
7892
|
+
},
|
|
7893
|
+
{
|
|
7894
|
+
x: base + sign * track$1,
|
|
7895
|
+
y: first.along
|
|
7896
|
+
},
|
|
7897
|
+
{
|
|
7898
|
+
x: base + sign * track$1,
|
|
7899
|
+
y: last.along
|
|
7900
|
+
},
|
|
7901
|
+
{
|
|
7902
|
+
x: base + sign * last.normal,
|
|
7903
|
+
y: last.along
|
|
7904
|
+
}
|
|
7905
|
+
];
|
|
7906
|
+
}
|
|
7907
|
+
const distance = input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? loopDistance(sideLoops, edge, side, ordering) : spacing * (indexOnSide + 1);
|
|
7677
7908
|
const x = side === "EAST" ? loopRect.x + loopRect.width + distance : loopRect.x - distance;
|
|
7678
7909
|
const start$2 = {
|
|
7679
7910
|
x: side === "EAST" ? loopRect.x + loopRect.width : loopRect.x,
|
|
@@ -18936,6 +19167,8 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
|
|
|
18936
19167
|
const separated$1 = runSeparatedComponents(graph, options, context);
|
|
18937
19168
|
if (separated$1) return separated$1;
|
|
18938
19169
|
}
|
|
19170
|
+
const hiddenLoopPorts$1 = hideSelfLoopPorts(graph, options);
|
|
19171
|
+
if (hiddenLoopPorts$1) return restoreSelfLoopPorts(graph, yield* createLayeredScopePipeline(hiddenLoopPorts$1.graph, hiddenLoopPorts$1.options, context, fallbacks), hiddenLoopPorts$1.edges);
|
|
18939
19172
|
const direction = options.direction ?? graph.direction ?? "right";
|
|
18940
19173
|
const padding = typeof options.padding === "number" ? {
|
|
18941
19174
|
top: options.padding,
|
|
@@ -19609,16 +19842,20 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
|
|
|
19609
19842
|
const forwardDummyRect = forwardDummy && placement.rectByNodeId.get(forwardDummy.id);
|
|
19610
19843
|
const forwardDummyPort = forwardDummy?.ports?.[0];
|
|
19611
19844
|
const forwardCross = forwardDummyRect && forwardDummyPort ? horizontal$1 ? forwardDummyRect.y + (forwardDummyPort.y ?? 0) : forwardDummyRect.x + (forwardDummyPort.x ?? 0) : horizontal$1 ? Math.round(forwardLabel.y + (forward.height ?? 0) / 2) : Math.round(forwardLabel.x + (forward.width ?? 0) / 2);
|
|
19612
|
-
const
|
|
19845
|
+
const alignedCross = (node, portName) => {
|
|
19613
19846
|
const rect = placement.rectByNodeId.get(node.id);
|
|
19614
19847
|
const port = node.ports?.find((candidate) => candidate.name === portName);
|
|
19615
|
-
if (!rect || !port) return;
|
|
19848
|
+
if (!rect || !port) return void 0;
|
|
19616
19849
|
const placedPort = placePorts(node.ports, rect, direction, (candidate) => input.portSettings?.(candidate, node), {
|
|
19617
19850
|
...options.settings,
|
|
19618
19851
|
...options.nodeSettings?.(node)
|
|
19619
19852
|
}, (candidate) => getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate))?.find((candidate) => candidate.name === portName);
|
|
19620
|
-
if (!placedPort) return;
|
|
19621
|
-
|
|
19853
|
+
if (!placedPort) return void 0;
|
|
19854
|
+
return forwardCross - (horizontal$1 ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2);
|
|
19855
|
+
};
|
|
19856
|
+
const alignPort = (node, nextCross) => {
|
|
19857
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
19858
|
+
if (!rect || nextCross === void 0) return;
|
|
19622
19859
|
const currentCross = horizontal$1 ? rect.y : rect.x;
|
|
19623
19860
|
postCompactionNodeCrossDeltas.set(node.id, (postCompactionNodeCrossDeltas.get(node.id) ?? 0) + nextCross - currentCross);
|
|
19624
19861
|
mutableRects.set(node.id, horizontal$1 ? {
|
|
@@ -19630,8 +19867,12 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
|
|
|
19630
19867
|
});
|
|
19631
19868
|
};
|
|
19632
19869
|
if (isolatedAntiparallelPair) {
|
|
19633
|
-
|
|
19634
|
-
|
|
19870
|
+
const ends = [[nodesById.get(forward.sourceId), forward.sourcePort], [nodesById.get(forward.targetId), forward.targetPort]];
|
|
19871
|
+
const crosses$1 = ends.map(([node, port]) => alignedCross(node, port));
|
|
19872
|
+
if (!ends.some(([node], index) => {
|
|
19873
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
19874
|
+
return rect !== void 0 && crosses$1[index] !== void 0 && Math.abs(crosses$1[index] - (horizontal$1 ? rect.y : rect.x)) > 1e-9 && graph.edges.some((edge) => edge.sourceId === node.id && edge.targetId === node.id);
|
|
19875
|
+
})) ends.forEach(([node], index) => alignPort(node, crosses$1[index]));
|
|
19635
19876
|
}
|
|
19636
19877
|
}
|
|
19637
19878
|
}
|
|
@@ -20017,7 +20258,7 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
|
|
|
20017
20258
|
y: (points[1].y + points[2].y - height) / 2
|
|
20018
20259
|
} : void 0;
|
|
20019
20260
|
const movableExteriorLoopLabelPosition = (() => {
|
|
20020
|
-
if (edgeRouting !== "ORTHOGONAL" || labelPlacement !== "CENTER" ||
|
|
20261
|
+
if (edgeRouting !== "ORTHOGONAL" || labelPlacement !== "CENTER" || !hasMovableLoopPorts(input, edge) || !sourceRect || points.length !== 4) return void 0;
|
|
20021
20262
|
const first = points[1], last = points[2];
|
|
20022
20263
|
if (first.y === last.y) {
|
|
20023
20264
|
const low = first.y < sourceRect.y;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { K as placePorts, X as JavaRandom, i as layeredAlgorithm, n as elkjs0111LayeredAlgorithm, x as setElkjs0111ResultPolicy, z as getPolylineMidpoint } from "./layered-
|
|
1
|
+
import { K as placePorts, X as JavaRandom, i as layeredAlgorithm, n as elkjs0111LayeredAlgorithm, x as setElkjs0111ResultPolicy, z as getPolylineMidpoint } from "./layered-BtnmL9_b.mjs";
|
|
2
2
|
import { getNodeSize } from "@statelyai/graph/layout";
|
|
3
3
|
|
|
4
4
|
//#region src/fixed.ts
|
package/package.json
CHANGED
package/src/layered/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { countAllCrossings } from "./crossing-counter";
|
|
|
8
8
|
import { crossingGraph } from "./crossing-graph";
|
|
9
9
|
import { sortInitialModelOrder, markInitialModelOrderPrepared } from "./initial-model-order";
|
|
10
10
|
import { hasMovableLoopPorts } from "./loop-envelopes";
|
|
11
|
+
import { hideSelfLoopPorts, restoreSelfLoopPorts } from "./self-loop-ports";
|
|
11
12
|
import { fixedSelfLoopLabels, fixedSelfLoopSide } from "./fixed-self-loop";
|
|
12
13
|
import { compactionBounds, placementCrossBounds } from "./compaction-bounds";
|
|
13
14
|
import { setElkjs0111ResultPolicy } from "../internal/elkjs-compatibility";
|
|
@@ -1424,6 +1425,18 @@ export function* createLayeredScopePipeline<N, E, G, P>(
|
|
|
1424
1425
|
const separated = runSeparatedComponents(graph, options, context);
|
|
1425
1426
|
if (separated) return separated;
|
|
1426
1427
|
}
|
|
1428
|
+
const hiddenLoopPorts = hideSelfLoopPorts(graph as Graph<N, E, G, P>, options);
|
|
1429
|
+
if (hiddenLoopPorts)
|
|
1430
|
+
return restoreSelfLoopPorts(
|
|
1431
|
+
graph as Graph<N, E, G, P>,
|
|
1432
|
+
yield* createLayeredScopePipeline(
|
|
1433
|
+
hiddenLoopPorts.graph,
|
|
1434
|
+
hiddenLoopPorts.options,
|
|
1435
|
+
context,
|
|
1436
|
+
fallbacks,
|
|
1437
|
+
),
|
|
1438
|
+
hiddenLoopPorts.edges,
|
|
1439
|
+
);
|
|
1427
1440
|
const direction = options.direction ?? graph.direction ?? "right";
|
|
1428
1441
|
const padding =
|
|
1429
1442
|
typeof options.padding === "number"
|
|
@@ -2507,10 +2520,10 @@ export function* createLayeredScopePipeline<N, E, G, P>(
|
|
|
2507
2520
|
: horizontal
|
|
2508
2521
|
? Math.round(forwardLabel.y + (forward.height ?? 0) / 2)
|
|
2509
2522
|
: Math.round(forwardLabel.x + (forward.width ?? 0) / 2);
|
|
2510
|
-
const
|
|
2523
|
+
const alignedCross = (node: GraphNode, portName: string): number | undefined => {
|
|
2511
2524
|
const rect = placement.rectByNodeId.get(node.id);
|
|
2512
2525
|
const port = node.ports?.find((candidate) => candidate.name === portName);
|
|
2513
|
-
if (!rect || !port) return;
|
|
2526
|
+
if (!rect || !port) return undefined;
|
|
2514
2527
|
const placedPort = placePorts(
|
|
2515
2528
|
node.ports,
|
|
2516
2529
|
rect,
|
|
@@ -2520,11 +2533,15 @@ export function* createLayeredScopePipeline<N, E, G, P>(
|
|
|
2520
2533
|
(candidate) =>
|
|
2521
2534
|
getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate),
|
|
2522
2535
|
)?.find((candidate) => candidate.name === portName);
|
|
2523
|
-
if (!placedPort) return;
|
|
2536
|
+
if (!placedPort) return undefined;
|
|
2524
2537
|
const localCross = horizontal
|
|
2525
2538
|
? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2
|
|
2526
2539
|
: (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2;
|
|
2527
|
-
|
|
2540
|
+
return forwardCross - localCross;
|
|
2541
|
+
};
|
|
2542
|
+
const alignPort = (node: GraphNode, nextCross: number | undefined): void => {
|
|
2543
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
2544
|
+
if (!rect || nextCross === undefined) return;
|
|
2528
2545
|
const currentCross = horizontal ? rect.y : rect.x;
|
|
2529
2546
|
postCompactionNodeCrossDeltas.set(
|
|
2530
2547
|
node.id,
|
|
@@ -2536,8 +2553,23 @@ export function* createLayeredScopePipeline<N, E, G, P>(
|
|
|
2536
2553
|
);
|
|
2537
2554
|
};
|
|
2538
2555
|
if (isolatedAntiparallelPair) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2556
|
+
const ends = [
|
|
2557
|
+
[nodesById.get(forward.sourceId)!, forward.sourcePort],
|
|
2558
|
+
[nodesById.get(forward.targetId)!, forward.targetPort],
|
|
2559
|
+
] as const;
|
|
2560
|
+
const crosses = ends.map(([node, port]) => alignedCross(node, port));
|
|
2561
|
+
// ELK keeps a loop owner where BK placed it, and moving it here would
|
|
2562
|
+
// leave its loop and label behind, so such a pair stays as placed.
|
|
2563
|
+
const movesLoopOwner = ends.some(([node], index) => {
|
|
2564
|
+
const rect = placement.rectByNodeId.get(node.id);
|
|
2565
|
+
return (
|
|
2566
|
+
rect !== undefined &&
|
|
2567
|
+
crosses[index] !== undefined &&
|
|
2568
|
+
Math.abs(crosses[index]! - (horizontal ? rect.y : rect.x)) > 1e-9 &&
|
|
2569
|
+
graph.edges.some((edge) => edge.sourceId === node.id && edge.targetId === node.id)
|
|
2570
|
+
);
|
|
2571
|
+
});
|
|
2572
|
+
if (!movesLoopOwner) ends.forEach(([node], index) => alignPort(node, crosses[index]));
|
|
2541
2573
|
}
|
|
2542
2574
|
}
|
|
2543
2575
|
}
|
|
@@ -3142,7 +3174,6 @@ export function* createLayeredScopePipeline<N, E, G, P>(
|
|
|
3142
3174
|
if (
|
|
3143
3175
|
edgeRouting !== "ORTHOGONAL" ||
|
|
3144
3176
|
labelPlacement !== "CENTER" ||
|
|
3145
|
-
inlineLabel ||
|
|
3146
3177
|
!hasMovableLoopPorts(input, edge) ||
|
|
3147
3178
|
!sourceRect ||
|
|
3148
3179
|
points.length !== 4
|