@statelyai/layout 0.4.0 → 0.4.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/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-UciYx7lD.mjs → elkjs-BBfKN4Dw.mjs} +165 -63
- package/dist/{elkjs-ggjsBuVP.cjs → elkjs-DNfXDPup.cjs} +165 -63
- 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-CBPg6_6_.cjs → layered-5RxQhryb.cjs} +245 -39
- package/dist/{layered-C7ERzZjR.mjs → layered-DSesbctm.mjs} +240 -34
- package/dist/{layout-engine-DAw06RhN.cjs → layout-engine-CLFySz3R.cjs} +1 -1
- package/dist/{layout-engine-DC3IYkIy.mjs → layout-engine-GpBIX6ng.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +112 -28
- package/src/elkjs/layout-quality.ts +81 -33
- package/src/layered/crossing-counter.ts +17 -5
- package/src/layered/hierarchy-sweepiness.ts +37 -41
- package/src/layered/index.ts +0 -11
- package/src/layered/opposing-tracks.ts +108 -8
- package/src/layered/orthogonal-hypersegments.ts +69 -7
- package/src/layered/strategies.ts +115 -18
|
@@ -506,10 +506,18 @@ function createOrthogonalHypersegments(ports, connections, listRank) {
|
|
|
506
506
|
segmentByPort
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
|
-
/**
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
509
|
+
/**
|
|
510
|
+
* {@link countOrthogonalHypersegmentCrossings}, optionally separating shared
|
|
511
|
+
* tracks. ELK routes a hyperedge on one shared track. When it joins two or
|
|
512
|
+
* more ports on each side, its edges would run opposite ways along that
|
|
513
|
+
* track, which routing never allows; `separateShared` counts such a
|
|
514
|
+
* hyperedge's edges one by one instead. `shared` reports whether one exists.
|
|
515
|
+
*/
|
|
516
|
+
function scoreOrthogonalHypersegments(ports, connections, separateShared = false) {
|
|
517
|
+
const { segments: merged, segmentByPort } = createOrthogonalHypersegments(ports, connections);
|
|
518
|
+
const sharedSegments = new Set(merged.flatMap((s, i) => s.incoming.length > 1 && s.outgoing.length > 1 ? [i] : []));
|
|
519
|
+
const separating = separateShared && sharedSegments.size > 0;
|
|
520
|
+
const spans = merged.filter((_, i) => !separating || !sharedSegments.has(i)).map((s) => ({
|
|
513
521
|
leftStart: Math.min(...s.incoming),
|
|
514
522
|
leftEnd: Math.max(...s.incoming),
|
|
515
523
|
rightStart: Math.min(...s.outgoing),
|
|
@@ -522,6 +530,43 @@ function countOrthogonalHypersegmentCrossings(ports, connections) {
|
|
|
522
530
|
if (a.leftStart < b.leftEnd && b.leftStart < a.leftEnd) count++;
|
|
523
531
|
if (a.rightStart < b.rightEnd && b.rightStart < a.rightEnd) count++;
|
|
524
532
|
}
|
|
533
|
+
if (separating) {
|
|
534
|
+
const position = new Map(ports.map((p) => [p.id, p.position]));
|
|
535
|
+
const edges = connections.filter((c$1) => sharedSegments.has(segmentByPort.get(c$1.source))).map((c$1) => [position.get(c$1.source), position.get(c$1.target)]);
|
|
536
|
+
for (const [left, right] of edges) for (const b of spans) {
|
|
537
|
+
if ((left - b.leftStart) * (right - b.rightStart) < 0) count++;
|
|
538
|
+
if (left < b.leftEnd && b.leftStart < left) count++;
|
|
539
|
+
if (right < b.rightEnd && b.rightStart < right) count++;
|
|
540
|
+
}
|
|
541
|
+
count += strictInversions(edges);
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
count,
|
|
545
|
+
shared: sharedSegments.size > 0
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/** Pairs whose ends are strictly inverted: two separated edges that cross, in O(n log n). */
|
|
549
|
+
function strictInversions(pairs) {
|
|
550
|
+
const ranks = [...new Set(pairs.map(([, right]) => right))].sort((a, b) => a - b);
|
|
551
|
+
const rankOf = new Map(ranks.map((value, index) => [value, index + 1]));
|
|
552
|
+
const tree = new Array(ranks.length + 1).fill(0);
|
|
553
|
+
const atMost = (rank) => {
|
|
554
|
+
let sum = 0;
|
|
555
|
+
for (let i = rank; i > 0; i -= i & -i) sum += tree[i];
|
|
556
|
+
return sum;
|
|
557
|
+
};
|
|
558
|
+
const sorted = [...pairs].sort((a, b) => a[0] - b[0]);
|
|
559
|
+
let inserted = 0, count = 0;
|
|
560
|
+
for (let start = 0; start < sorted.length;) {
|
|
561
|
+
let end = start;
|
|
562
|
+
while (end < sorted.length && sorted[end][0] === sorted[start][0]) end++;
|
|
563
|
+
for (let i = start; i < end; i++) count += inserted - atMost(rankOf.get(sorted[i][1]));
|
|
564
|
+
for (let i = start; i < end; i++) {
|
|
565
|
+
for (let r = rankOf.get(sorted[i][1]); r <= ranks.length; r += r & -r) tree[r]++;
|
|
566
|
+
inserted++;
|
|
567
|
+
}
|
|
568
|
+
start = end;
|
|
569
|
+
}
|
|
525
570
|
return count;
|
|
526
571
|
}
|
|
527
572
|
|
|
@@ -650,21 +695,22 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
650
695
|
betweenLayers: 0,
|
|
651
696
|
inLayer: 0,
|
|
652
697
|
northSouth: 0,
|
|
653
|
-
total: 0
|
|
698
|
+
total: 0,
|
|
699
|
+
sharedTracks: false
|
|
654
700
|
};
|
|
655
701
|
if (!graph.layers.length) return score;
|
|
656
702
|
score.inLayer += countPorts(face(graph.layers[0], "WEST"), true);
|
|
657
703
|
score.inLayer += countPorts(face(graph.layers.at(-1), "EAST"), true);
|
|
658
704
|
for (const [index, layer] of graph.layers.entries()) {
|
|
659
705
|
const next = graph.layers[index + 1];
|
|
660
|
-
if (next) if ((layer.some((node) => node.ports.some((port) => port.side === "EAST" && degree(port.id) > 1)) || next.some((node) => node.ports.some((port) => port.side === "WEST" && degree(port.id) > 1))) && boundaryMode
|
|
706
|
+
if (next) if ((layer.some((node) => node.ports.some((port) => port.side === "EAST" && degree(port.id) > 1)) || next.some((node) => node.ports.some((port) => port.side === "WEST" && degree(port.id) > 1))) && boundaryMode !== "edges") {
|
|
661
707
|
const sourcePorts = layer.flatMap((node) => node.ports).filter((port) => outgoing.get(port.id).some((peer) => layerByPort.get(peer) !== index));
|
|
662
708
|
const targetPorts = next.flatMap((node) => {
|
|
663
709
|
const connectedPorts = node.ports.filter((port) => incoming.get(port.id).some((peer) => layerByPort.get(peer) !== index + 1));
|
|
664
710
|
return [...connectedPorts.filter((port) => port.side === "NORTH").reverse(), ...connectedPorts.filter((port) => port.side !== "NORTH").reverse()];
|
|
665
711
|
});
|
|
666
712
|
const sources = new Set(sourcePorts.map((port) => port.id)), targets = new Set(targetPorts.map((port) => port.id));
|
|
667
|
-
|
|
713
|
+
const hyperedges = scoreOrthogonalHypersegments([...sourcePorts.map((port, position) => ({
|
|
668
714
|
id: port.id,
|
|
669
715
|
side: "source",
|
|
670
716
|
position
|
|
@@ -672,7 +718,9 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
672
718
|
id: port.id,
|
|
673
719
|
side: "target",
|
|
674
720
|
position
|
|
675
|
-
}))], graph.edges.filter((edge) => sources.has(edge.source) && targets.has(edge.target)));
|
|
721
|
+
}))], graph.edges.filter((edge) => sources.has(edge.source) && targets.has(edge.target)), boundaryMode === "separated");
|
|
722
|
+
score.betweenLayers += hyperedges.count;
|
|
723
|
+
score.sharedTracks ||= hyperedges.shared;
|
|
676
724
|
score.inLayer += countPorts(face(layer, "EAST"), true) + countPorts(face(next, "WEST"), true);
|
|
677
725
|
} else score.betweenLayers += countPorts([...face(layer, "EAST"), ...face(next, "WEST", false)], false);
|
|
678
726
|
if (layer.some((node) => node.type === "NORTH_SOUTH_PORT")) score.northSouth += countNorthSouth(layer);
|
|
@@ -3641,6 +3689,18 @@ function cycleModelOrder(input) {
|
|
|
3641
3689
|
const count = Math.max(1, input.graph.nodes.length);
|
|
3642
3690
|
return new Map(input.graph.nodes.map((node, index) => [node.id, enforced ? Number(input.nodeSettings?.(node)?.["considerModelOrder.groupModelOrder.cycleBreakingId"] ?? 0) * count + index : index]));
|
|
3643
3691
|
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Internal setting: how crossing minimization models ports and tracks.
|
|
3694
|
+
* `mixedPorts` (default true) counts a port carrying both directions as two
|
|
3695
|
+
* ports and groups its neighbours by direction. `separateSharedTracks`
|
|
3696
|
+
* (default false) counts a hyperedge joining two or more ports on each side
|
|
3697
|
+
* of its gap edge by edge, since routing never lets edges run opposite ways
|
|
3698
|
+
* along one track. A `probe` records whether either choice can change the
|
|
3699
|
+
* order. Each helps on average but steers the sweeps into worse orders on
|
|
3700
|
+
* some graphs, so the elkjs facade also lays such graphs out with the
|
|
3701
|
+
* alternatives and keeps the best result.
|
|
3702
|
+
*/
|
|
3703
|
+
const CROSSING_MODEL = "internal.crossingModel";
|
|
3644
3704
|
const breakCyclesWithDepthFirstSearch = (input) => {
|
|
3645
3705
|
const outgoing = /* @__PURE__ */ new Map();
|
|
3646
3706
|
for (const node of input.graph.nodes) outgoing.set(node.id, []);
|
|
@@ -4128,8 +4188,7 @@ function applyLayerConstraintOrder(input, order) {
|
|
|
4128
4188
|
return {
|
|
4129
4189
|
...order,
|
|
4130
4190
|
layers: order.layers.map((layer) => {
|
|
4131
|
-
|
|
4132
|
-
return [...input.settings.mergeEdges === true ? [...unconstrained.filter((id) => id.startsWith("__layout_dummy:")), ...unconstrained.filter((id) => !id.startsWith("__layout_dummy:"))] : unconstrained, ...layer.filter((id) => constrained.has(id))];
|
|
4191
|
+
return [...layer.filter((id) => !constrained.has(id)), ...layer.filter((id) => constrained.has(id))];
|
|
4133
4192
|
})
|
|
4134
4193
|
};
|
|
4135
4194
|
}
|
|
@@ -4896,6 +4955,18 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
4896
4955
|
}
|
|
4897
4956
|
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
4898
4957
|
const edgeById = new Map(input.graph.edges.map((edge) => [edge.id, edge]));
|
|
4958
|
+
const boundaryPortIndex = (nodeId, edgeId, incoming) => {
|
|
4959
|
+
const edge = edgeById.get(edgeId);
|
|
4960
|
+
const name = incoming !== orientation.reversedEdgeIds.has(edgeId) ? edge.targetPort : edge.sourcePort;
|
|
4961
|
+
if (!name?.startsWith("__native_hierarchy")) return -1;
|
|
4962
|
+
return nodeById.get(nodeId).ports?.findIndex((port) => port.name === name) ?? -1;
|
|
4963
|
+
};
|
|
4964
|
+
for (const [orders, incoming] of [[outputPortOrder, false], [inputPortOrder, true]]) for (const [nodeId, edgeIds] of orders) {
|
|
4965
|
+
if (edgeIds.length < 2) continue;
|
|
4966
|
+
const slots = edgeIds.flatMap((id, slot) => boundaryPortIndex(nodeId, id, incoming) < 0 ? [] : [slot]);
|
|
4967
|
+
const sorted = slots.map((slot) => edgeIds[slot]).sort((a, b) => boundaryPortIndex(nodeId, a, incoming) - boundaryPortIndex(nodeId, b, incoming));
|
|
4968
|
+
slots.forEach((slot, index) => edgeIds[slot] = sorted[index]);
|
|
4969
|
+
}
|
|
4899
4970
|
const edgeModelOrder = new Map(input.graph.edges.map((edge, index) => [edge.id, index]));
|
|
4900
4971
|
const hierarchicalNodes = /* @__PURE__ */ new Set();
|
|
4901
4972
|
const fixedOrderNodes = new Set(input.graph.nodes.filter((node) => {
|
|
@@ -4933,10 +5004,24 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
4933
5004
|
if (edgeIds.length >= 4) edgeIds.splice(0, edgeIds.length - 1, ...edgeIds.slice(0, -1).reverse());
|
|
4934
5005
|
}
|
|
4935
5006
|
const buildCrossingGraph$1 = createCrossingGraphBuilder(input);
|
|
5007
|
+
const crossingModel = input.settings[CROSSING_MODEL] ?? {};
|
|
5008
|
+
const mixedPorts = crossingModel.mixedPorts !== false, probe = crossingModel.probe;
|
|
4936
5009
|
const countCrossings = (candidateLayers) => {
|
|
4937
5010
|
if (exactPortSweep) {
|
|
4938
5011
|
const graph = buildCrossingGraph$1(orientation, candidateLayers, inputPortOrder, outputPortOrder);
|
|
4939
|
-
|
|
5012
|
+
for (const layer of graph.layers) for (const node of layer) {
|
|
5013
|
+
const physical = node.ports.length > 1 ? canonicalNodes.get(node.id)?.ports : void 0;
|
|
5014
|
+
if (physical?.length !== node.ports.length) continue;
|
|
5015
|
+
const slot = new Map(physical.map((port, index) => [port.id, index]));
|
|
5016
|
+
if (node.ports.every((port) => slot.has(port.id))) node.ports.sort((a, b) => slot.get(a.id) - slot.get(b.id));
|
|
5017
|
+
}
|
|
5018
|
+
const split = mixedPorts ? splitMixedCrossingPorts(graph, canonicalReversed) : graph;
|
|
5019
|
+
const score = countAllCrossings(split, crossingModel.separateSharedTracks ? "separated" : "hyperedges");
|
|
5020
|
+
if (probe) {
|
|
5021
|
+
if (split !== graph) probe.mixedPorts = true;
|
|
5022
|
+
if (score.sharedTracks) probe.sharedTracks = true;
|
|
5023
|
+
}
|
|
5024
|
+
let total = score.total;
|
|
4940
5025
|
const nodeInfluence = Number(input.settings["considerModelOrder.crossingCounterNodeInfluence"] ?? 0);
|
|
4941
5026
|
if ((input.settings["considerModelOrder.strategy"] ?? "NONE") !== "NONE" && nodeInfluence !== 0) {
|
|
4942
5027
|
const changes = countModelOrderChanges(input, orientation, graph);
|
|
@@ -4986,11 +5071,12 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
4986
5071
|
const nodes = fixedLayer.map((id) => canonicalNodes.get(id));
|
|
4987
5072
|
canonicalGraph.layers[fixedIndex] = nodes;
|
|
4988
5073
|
canonicalDistributor.calculatePortRanks(canonicalGraph, nodes, !forward, nodeRelativePortRanks);
|
|
4989
|
-
const
|
|
4990
|
-
|
|
5074
|
+
const byEnd = forward ? canonicalEdgesBySource : canonicalEdgesByTarget;
|
|
5075
|
+
const indices = fixedLayer.flatMap((id) => byEnd.get(id) ?? []).sort((a, b) => a - b);
|
|
5076
|
+
for (const index of indices) {
|
|
4991
5077
|
const edge = canonicalEdges[index];
|
|
4992
5078
|
const [source, target] = canonicalOrientedEndpoints[index];
|
|
4993
|
-
if (!
|
|
5079
|
+
if (!freeIds.has(forward ? target : source)) continue;
|
|
4994
5080
|
const endpoint = canonicalEndpoints.get(edge.id);
|
|
4995
5081
|
const rank = canonicalDistributor.state.ranks[forward ? endpoint.source : endpoint.target];
|
|
4996
5082
|
ranks.get(forward ? target : source).push(rank);
|
|
@@ -5072,6 +5158,14 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5072
5158
|
const canonicalEdges = input.graph.edges.filter((edge) => edge.sourceId !== edge.targetId);
|
|
5073
5159
|
const canonicalReversed = canonicalEdges.map((edge) => orientation.reversedEdgeIds.has(edge.id));
|
|
5074
5160
|
const canonicalOrientedEndpoints = canonicalEdges.map((edge) => getOrientedEndpoints(edge, orientation));
|
|
5161
|
+
const canonicalEdgesBySource = /* @__PURE__ */ new Map(), canonicalEdgesByTarget = /* @__PURE__ */ new Map();
|
|
5162
|
+
canonicalOrientedEndpoints.forEach(([source, target], index) => {
|
|
5163
|
+
for (const [byEnd, id] of [[canonicalEdgesBySource, source], [canonicalEdgesByTarget, target]]) {
|
|
5164
|
+
const list = byEnd.get(id);
|
|
5165
|
+
if (list) list.push(index);
|
|
5166
|
+
else byEnd.set(id, [index]);
|
|
5167
|
+
}
|
|
5168
|
+
});
|
|
5075
5169
|
const canonicalEndpoints = new Map(canonicalEdges.map((edge, index) => [edge.id, canonicalGraph.edges[index]]));
|
|
5076
5170
|
const canonicalFixedOrder = new Set(input.graph.nodes.filter((node) => {
|
|
5077
5171
|
const constraint = input.nodeSettings?.(node)?.portConstraints;
|
|
@@ -5121,6 +5215,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5121
5215
|
*/
|
|
5122
5216
|
const constrained = new Set([...units?.units.keys() ?? [], ...[...units?.successors ?? []].flatMap(([id, after]) => [id, ...after])]);
|
|
5123
5217
|
const groupMixedPortDirections = (free, fixed, beyond = []) => {
|
|
5218
|
+
if (!mixedPorts) return;
|
|
5124
5219
|
const fixedIds = new Set(fixed);
|
|
5125
5220
|
const groups = /* @__PURE__ */ new Map();
|
|
5126
5221
|
for (const id of free) {
|
|
@@ -5143,6 +5238,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5143
5238
|
groups.set(key, group);
|
|
5144
5239
|
}
|
|
5145
5240
|
if (![...groups.values()].some((g) => g.some((m) => m.reversed) && g.some((m) => !m.reversed))) return;
|
|
5241
|
+
if (probe) probe.mixedPorts = true;
|
|
5146
5242
|
const index = new Map(free.map((id, i) => [id, i]));
|
|
5147
5243
|
const far = new Map(beyond.map((id, i) => [id, i]));
|
|
5148
5244
|
const reach = (members) => {
|
|
@@ -5311,6 +5407,10 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5311
5407
|
restore,
|
|
5312
5408
|
finish,
|
|
5313
5409
|
currentLayers: () => working,
|
|
5410
|
+
crossingGraph: () => ({
|
|
5411
|
+
layers: working.map((layer) => layer.map((id) => canonicalNodes.get(id))),
|
|
5412
|
+
edges: canonicalGraph.edges
|
|
5413
|
+
}),
|
|
5314
5414
|
physicalPortOrder: (id) => canonicalNodes.get(id)?.ports.map((port) => port.id) ?? [],
|
|
5315
5415
|
edgeOrders: (id) => ({
|
|
5316
5416
|
incoming: inputPortOrder.get(id) ?? [],
|
|
@@ -6014,6 +6114,16 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6014
6114
|
}
|
|
6015
6115
|
const result = /* @__PURE__ */ new Map();
|
|
6016
6116
|
const edgeModelOrder = new Map(input.graph.edges.map((edge, index) => [edge.id, index]));
|
|
6117
|
+
const orderIndexes = /* @__PURE__ */ new Map();
|
|
6118
|
+
const orderIndex = (list, id) => {
|
|
6119
|
+
let positions = orderIndexes.get(list);
|
|
6120
|
+
if (!positions) {
|
|
6121
|
+
positions = /* @__PURE__ */ new Map();
|
|
6122
|
+
for (const [index, item] of list.entries()) if (!positions.has(item)) positions.set(item, index);
|
|
6123
|
+
orderIndexes.set(list, positions);
|
|
6124
|
+
}
|
|
6125
|
+
return positions.get(id) ?? -1;
|
|
6126
|
+
};
|
|
6017
6127
|
const placedPorts = /* @__PURE__ */ new Map();
|
|
6018
6128
|
const portSideCounts = /* @__PURE__ */ new Map();
|
|
6019
6129
|
const portSideCount = (node, side) => {
|
|
@@ -6055,7 +6165,7 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6055
6165
|
const leftIncoming = left.endpoint === "target" !== (orientation?.reversedEdgeIds.has(left.edge.id) ?? false);
|
|
6056
6166
|
if (leftIncoming === (right.endpoint === "target" !== (orientation?.reversedEdgeIds.has(right.edge.id) ?? false))) {
|
|
6057
6167
|
const selected = (leftIncoming ? sweptOrder?.inputPortOrderByNodeId : sweptOrder?.outputPortOrderByNodeId)?.get(nodeId);
|
|
6058
|
-
const a = selected
|
|
6168
|
+
const a = selected ? orderIndex(selected, left.edge.id) : -1, b = selected ? orderIndex(selected, right.edge.id) : -1;
|
|
6059
6169
|
if (a >= 0 && b >= 0 && a !== b) return (leftIncoming ? -1 : 1) * (a - b);
|
|
6060
6170
|
}
|
|
6061
6171
|
if (interactiveLeftSourceOrder) {
|
|
@@ -6753,8 +6863,9 @@ function routeEdges(style) {
|
|
|
6753
6863
|
candidate.slot = increasing ? segment.slot : maximumSlot - segment.slot;
|
|
6754
6864
|
if (segment.partner !== void 0) {
|
|
6755
6865
|
const partner = result.segments[segment.partner];
|
|
6756
|
-
|
|
6757
|
-
|
|
6866
|
+
const [first, second] = physicalPort(candidate, "source").graphSource ? [segment, partner] : [partner, segment];
|
|
6867
|
+
candidate.slot = increasing ? first.slot : maximumSlot - first.slot;
|
|
6868
|
+
candidate.secondSlot = increasing ? second.slot : maximumSlot - second.slot;
|
|
6758
6869
|
const sourcePort = portByCandidate.get(candidate).source;
|
|
6759
6870
|
const offset = (increasing ? candidate.sourceCross : candidate.targetCross) - byPort.get(sourcePort).position + (runShift.get(sourcePort) ?? 0);
|
|
6760
6871
|
candidate.crossover = segment.outgoing[0] + offset;
|
|
@@ -9654,6 +9765,10 @@ function crossing(s, t) {
|
|
|
9654
9765
|
const v = h === s ? t : s;
|
|
9655
9766
|
return v.a.x > Math.min(h.a.x, h.b.x) + EPS && v.a.x < Math.max(h.a.x, h.b.x) - EPS && h.a.y > Math.min(v.a.y, v.b.y) + EPS && h.a.y < Math.max(v.a.y, v.b.y) - EPS;
|
|
9656
9767
|
}
|
|
9768
|
+
const onBorder = (p, r) => {
|
|
9769
|
+
const inX = p.x >= r.x - EPS && p.x <= r.x + r.width + EPS;
|
|
9770
|
+
return p.y >= r.y - EPS && p.y <= r.y + r.height + EPS && (Math.abs(p.x - r.x) <= EPS || Math.abs(p.x - r.x - r.width) <= EPS) || inX && (Math.abs(p.y - r.y) <= EPS || Math.abs(p.y - r.y - r.height) <= EPS);
|
|
9771
|
+
};
|
|
9657
9772
|
const segmentsOf = (points) => points.slice(1).flatMap((b, i) => {
|
|
9658
9773
|
const s = {
|
|
9659
9774
|
a: points[i],
|
|
@@ -9686,7 +9801,34 @@ function separateOpposingTracks(scene) {
|
|
|
9686
9801
|
if (!rects) terminalCache.set(key, rects = terminals(a.route, b.route));
|
|
9687
9802
|
return rects;
|
|
9688
9803
|
};
|
|
9804
|
+
const boxes = /* @__PURE__ */ new WeakMap();
|
|
9805
|
+
const boxOf = (segments) => {
|
|
9806
|
+
let box = boxes.get(segments);
|
|
9807
|
+
if (!box) {
|
|
9808
|
+
box = [
|
|
9809
|
+
Infinity,
|
|
9810
|
+
Infinity,
|
|
9811
|
+
-Infinity,
|
|
9812
|
+
-Infinity
|
|
9813
|
+
];
|
|
9814
|
+
for (const { a, b } of segments) {
|
|
9815
|
+
box[0] = Math.min(box[0], a.x, b.x);
|
|
9816
|
+
box[1] = Math.min(box[1], a.y, b.y);
|
|
9817
|
+
box[2] = Math.max(box[2], a.x, b.x);
|
|
9818
|
+
box[3] = Math.max(box[3], a.y, b.y);
|
|
9819
|
+
}
|
|
9820
|
+
boxes.set(segments, box);
|
|
9821
|
+
}
|
|
9822
|
+
return box;
|
|
9823
|
+
};
|
|
9824
|
+
const none = {
|
|
9825
|
+
opposing: 0,
|
|
9826
|
+
overlap: 0,
|
|
9827
|
+
crossings: 0
|
|
9828
|
+
};
|
|
9689
9829
|
const pair = (mine, self, other) => {
|
|
9830
|
+
const [ax0, ay0, ax1, ay1] = boxOf(mine), [bx0, by0, bx1, by1] = boxOf(other.measured);
|
|
9831
|
+
if (ax1 < bx0 - EPS || bx1 < ax0 - EPS || ay1 < by0 - EPS || by1 < ay0 - EPS) return none;
|
|
9690
9832
|
const rects = terminalsOf(self, other);
|
|
9691
9833
|
const as = rects.length ? clip(mine, rects, true) : mine, bs = rects.length ? clip(other.measured, rects, true) : other.measured;
|
|
9692
9834
|
let opposing = 0, overlap = 0, crossings$1 = 0;
|
|
@@ -9899,8 +10041,8 @@ function separateOpposingTracks(scene) {
|
|
|
9899
10041
|
y: p.y
|
|
9900
10042
|
};
|
|
9901
10043
|
if (k >= 1 && k <= raw.length - 2) {
|
|
9902
|
-
const before = raw[k - 1], after = raw[k + 1];
|
|
9903
|
-
if (h ? !vertical(before) || !vertical(after) : !horizontal(before) || !horizontal(after)) continue;
|
|
10044
|
+
const before = raw[k - 1], after$1 = raw[k + 1];
|
|
10045
|
+
if (h ? !vertical(before) || !vertical(after$1) : !horizontal(before) || !horizontal(after$1)) continue;
|
|
9904
10046
|
for (const delta of offsets) {
|
|
9905
10047
|
const points = state.points.map((p, i) => i === k || i === k + 1 ? shift(p, delta) : p);
|
|
9906
10048
|
const next = [
|
|
@@ -9911,11 +10053,11 @@ function separateOpposingTracks(scene) {
|
|
|
9911
10053
|
a: points[i],
|
|
9912
10054
|
b: points[i + 1]
|
|
9913
10055
|
}));
|
|
9914
|
-
if (!keeps(before, next[0]) || !keeps(after, next[2])) continue;
|
|
10056
|
+
if (!keeps(before, next[0]) || !keeps(after$1, next[2])) continue;
|
|
9915
10057
|
consider(points, [
|
|
9916
10058
|
before,
|
|
9917
10059
|
segment,
|
|
9918
|
-
after
|
|
10060
|
+
after$1
|
|
9919
10061
|
], next, next[1], 0, delta);
|
|
9920
10062
|
}
|
|
9921
10063
|
continue;
|
|
@@ -9938,6 +10080,32 @@ function separateOpposingTracks(scene) {
|
|
|
9938
10080
|
a: port,
|
|
9939
10081
|
b: corner
|
|
9940
10082
|
}], [inflate(rect, TERMINAL_MARGIN)], true).reduce((sum, part) => sum + size(part), 0) : along;
|
|
10083
|
+
const after = {
|
|
10084
|
+
a: corner,
|
|
10085
|
+
b: beyond
|
|
10086
|
+
};
|
|
10087
|
+
const against = (s) => states.reduce((sum, other) => other === state ? sum : other.measured.reduce((n, t) => n + (opposite(s, t) ? shared(s, t) : 0), sum), 0);
|
|
10088
|
+
if (state.route.ends.length === 2 && state.route.ports?.[reversed ? 1 : 0] === void 0 && rect && onBorder(port, rect) && (h ? vertical(after) : horizontal(after))) {
|
|
10089
|
+
const lo = h ? rect.y : rect.x, hi = lo + (h ? rect.height : rect.width);
|
|
10090
|
+
for (const delta of offsets) {
|
|
10091
|
+
const moved = shift(port, delta);
|
|
10092
|
+
if ((h ? moved.y : moved.x) < lo - EPS || (h ? moved.y : moved.x) > hi + EPS) continue;
|
|
10093
|
+
const next = [{
|
|
10094
|
+
a: moved,
|
|
10095
|
+
b: shift(corner, delta)
|
|
10096
|
+
}, {
|
|
10097
|
+
a: shift(corner, delta),
|
|
10098
|
+
b: beyond
|
|
10099
|
+
}];
|
|
10100
|
+
if (!keeps(after, next[1]) || against(next[1]) > against(after) + EPS) continue;
|
|
10101
|
+
const replaced = [
|
|
10102
|
+
moved,
|
|
10103
|
+
shift(corner, delta),
|
|
10104
|
+
...path.slice(2)
|
|
10105
|
+
];
|
|
10106
|
+
consider(reversed ? replaced.reverse() : replaced, [segment, after], next, next[0], 0, delta);
|
|
10107
|
+
}
|
|
10108
|
+
}
|
|
9941
10109
|
for (const stub of new Set([along - clearance, spacing])) for (const delta of offsets) {
|
|
9942
10110
|
if (stub < 1 || along - stub < spacing) continue;
|
|
9943
10111
|
const q = {
|
|
@@ -9995,14 +10163,34 @@ function separateOpposingTracks(scene) {
|
|
|
9995
10163
|
* half a spacing either side of the port's centre.
|
|
9996
10164
|
*/
|
|
9997
10165
|
function splitSharedPorts(tolerant) {
|
|
9998
|
-
const ports = scene.ports;
|
|
9999
|
-
if (!ports?.size) return;
|
|
10000
10166
|
const groups = /* @__PURE__ */ new Map();
|
|
10167
|
+
const implicit = /* @__PURE__ */ new Map();
|
|
10001
10168
|
for (const state of states) for (const end of [0, 1]) {
|
|
10002
|
-
|
|
10003
|
-
const port = id === void 0 ? void 0 : ports.get(id);
|
|
10004
|
-
if (!port || state.points.length < 2) continue;
|
|
10169
|
+
if (state.points.length < 2) continue;
|
|
10005
10170
|
const [p, q] = end ? [...state.points].reverse() : state.points;
|
|
10171
|
+
let id = state.route.ports?.[end];
|
|
10172
|
+
let port = id === void 0 ? void 0 : scene.ports?.get(id);
|
|
10173
|
+
const node = state.route.ends.length === 2 ? state.route.ends[end] : void 0;
|
|
10174
|
+
const rect = node === void 0 ? void 0 : nodes.get(node);
|
|
10175
|
+
if (id === void 0 && rect && onBorder(p, rect)) {
|
|
10176
|
+
const points = implicit.get(node) ?? [];
|
|
10177
|
+
implicit.set(node, points);
|
|
10178
|
+
let at = points.find((o) => Math.abs(o.x - p.x) <= EPS && Math.abs(o.y - p.y) <= EPS);
|
|
10179
|
+
if (!at) points.push(at = {
|
|
10180
|
+
x: p.x,
|
|
10181
|
+
y: p.y
|
|
10182
|
+
});
|
|
10183
|
+
id = `${node}\0${points.indexOf(at)}`;
|
|
10184
|
+
port = {
|
|
10185
|
+
node,
|
|
10186
|
+
rect: {
|
|
10187
|
+
...at,
|
|
10188
|
+
width: 0,
|
|
10189
|
+
height: 0
|
|
10190
|
+
}
|
|
10191
|
+
};
|
|
10192
|
+
}
|
|
10193
|
+
if (!port) continue;
|
|
10006
10194
|
const s = {
|
|
10007
10195
|
a: p,
|
|
10008
10196
|
b: q
|
|
@@ -10024,7 +10212,13 @@ function separateOpposingTracks(scene) {
|
|
|
10024
10212
|
end
|
|
10025
10213
|
});
|
|
10026
10214
|
}
|
|
10027
|
-
for (const
|
|
10215
|
+
for (const group of groups.values()) {
|
|
10216
|
+
const { port, normal } = group;
|
|
10217
|
+
const ends = group.ends.filter(({ state, end }) => {
|
|
10218
|
+
if (state.route.ports?.[end] !== void 0) return true;
|
|
10219
|
+
const p = end ? state.points.at(-1) : state.points[0];
|
|
10220
|
+
return Math.abs(p.x - port.rect.x) <= EPS && Math.abs(p.y - port.rect.y) <= EPS;
|
|
10221
|
+
});
|
|
10028
10222
|
const incoming = ends.filter((e) => e.end === 1), outgoing = ends.filter((e) => e.end === 0);
|
|
10029
10223
|
if (!incoming.length || !outgoing.length) continue;
|
|
10030
10224
|
const stub = ({ state, end }) => {
|
|
@@ -10043,9 +10237,25 @@ function separateOpposingTracks(scene) {
|
|
|
10043
10237
|
const sideLo = node[lateral], sideHi = node[lateral] + (lateral === "x" ? node.width : node.height);
|
|
10044
10238
|
const wide = extent$2 >= spacing - EPS;
|
|
10045
10239
|
const half = spacing / 2;
|
|
10046
|
-
const
|
|
10240
|
+
const slid = (state, points, end, offset) => {
|
|
10241
|
+
if (state.route.ports?.some((id) => id !== void 0)) return void 0;
|
|
10242
|
+
if (Math.abs(points[0][across] - points[1][across]) <= EPS) return void 0;
|
|
10243
|
+
const far = nodes.get(state.route.ends[1 - end] ?? "");
|
|
10244
|
+
const moved = points.map((p) => ({
|
|
10245
|
+
...p,
|
|
10246
|
+
[lateral]: p[lateral] + offset
|
|
10247
|
+
}));
|
|
10248
|
+
const within = (value$1, rect) => {
|
|
10249
|
+
const lo = rect[lateral];
|
|
10250
|
+
return value$1 >= lo - EPS && value$1 <= lo + (lateral === "x" ? rect.width : rect.height) + EPS;
|
|
10251
|
+
};
|
|
10252
|
+
const value = moved[0][lateral];
|
|
10253
|
+
return within(value, node) && far && within(value, far) ? moved : void 0;
|
|
10254
|
+
};
|
|
10255
|
+
const shifted = (state, points, end, offset) => {
|
|
10047
10256
|
if (Math.abs(offset) <= EPS) return points;
|
|
10048
10257
|
const path = end ? [...points].reverse() : [...points];
|
|
10258
|
+
if (path.length === 2) return slid(state, points, end, offset);
|
|
10049
10259
|
if (path.length < 3) return void 0;
|
|
10050
10260
|
const [p, q, r] = path;
|
|
10051
10261
|
const along = r[lateral] - q[lateral];
|
|
@@ -10109,7 +10319,7 @@ function separateOpposingTracks(scene) {
|
|
|
10109
10319
|
const next = /* @__PURE__ */ new Map();
|
|
10110
10320
|
let feasible = true;
|
|
10111
10321
|
for (const e of ends) {
|
|
10112
|
-
const points = shifted(next.get(e.state) ?? e.state.points, e.end, offsetOf(e));
|
|
10322
|
+
const points = shifted(e.state, next.get(e.state) ?? e.state.points, e.end, offsetOf(e));
|
|
10113
10323
|
if (!points) {
|
|
10114
10324
|
feasible = false;
|
|
10115
10325
|
break;
|
|
@@ -18811,10 +19021,6 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18811
19021
|
switchedSideByPort.set(`${origin.node.id}\0${origin.port.name}`, opposite$2);
|
|
18812
19022
|
}
|
|
18813
19023
|
}
|
|
18814
|
-
if (expanded.input.settings.mergeEdges === true) order = {
|
|
18815
|
-
...order,
|
|
18816
|
-
layers: order.layers.map((layer) => [...layer.filter((id) => id.startsWith("__layout_dummy:")), ...layer.filter((id) => !id.startsWith("__layout_dummy:"))])
|
|
18817
|
-
};
|
|
18818
19024
|
const unzippingFanIn = graph.edges.length === graph.nodes.length - 1 && graph.nodes.some((node) => graph.edges.filter((edge) => edge.targetId === node.id).length === graph.nodes.length - 1);
|
|
18819
19025
|
if ((options.settings?.["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING") if (unzippingFanIn) order = applyLayerUnzipping(expanded.input, order);
|
|
18820
19026
|
else {
|
|
@@ -19717,4 +19923,4 @@ const elkjs0111LayeredAlgorithm = {
|
|
|
19717
19923
|
};
|
|
19718
19924
|
|
|
19719
19925
|
//#endregion
|
|
19720
|
-
export {
|
|
19926
|
+
export { attachExternalPortDummy as $, breakCyclesByStronglyConnectedConnectivity as A, minimizeCrossingsInteractively as B, assignLayersByBreadthFirstModelOrder as C, assignLayersInteractively as D, assignLayersByLongestPathToSink as E, breakCyclesWithDepthFirstSearch as F, placeNodesInteractively as G, minimizeCrossingsWithMedian as H, breakCyclesWithModelOrderBreadthFirstSearch as I, routeEdgesWithPolylines as J, placePorts as K, breakCyclesWithModelOrderDepthFirstSearch as L, breakCyclesGreedily as M, breakCyclesGreedilyByModelOrder as N, assignLayersWithCoffmanGraham as O, breakCyclesInteractively as P, assignLayersWithNetworkSimplex as Q, createLayerSweepSession as R, CROSSING_MODEL as S, assignLayersByLongestPath as T, minimizeCrossingsWithModelOrder as U, minimizeCrossingsWithBarycenter as V, placeNodesInLayers as W, JavaRandom as X, routeEdgesWithSplines as Y, minimizeHierarchyCrossings as Z, statechartHints as _, fromElkLayeredOptionId as a, elkjs0111ResultPolicy as b, elkLayeredOptionDefinitions as c, placeNodesWithBrandesKoepf as d, createExternalPortDummy as et, assignLayersWithStretchWidth as f, hint as g, separateOpposingTracks as h, layeredAlgorithm as i, breakCyclesByStronglyConnectedNodeType as j, breakCyclesByModelOrder as k, placeNodesWithNetworkSimplex as l, c as m, elkjs0111LayeredAlgorithm as n, createOrthogonalHypersegments as nt, toElkLayeredOptions as o, assignLayersWithMinWidth as p, routeEdgesOrthogonally as q, getLayeredLayout as r, elkLayeredEnumValues as s, createLayeredScopePipeline as t, externalPortDummyOf as tt, placeNodesWithLinearSegments as u, isPerpendicularPortRestored as v, assignLayersByDepthFirstModelOrder as w, setElkjs0111ResultPolicy as x, hierarchicalPortSides as y, getPolylineMidpoint as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { K as placePorts, X as JavaRandom, i as layeredAlgorithm, n as elkjs0111LayeredAlgorithm, x as setElkjs0111ResultPolicy, z as getPolylineMidpoint } from "./layered-DSesbctm.mjs";
|
|
2
2
|
import { getNodeSize } from "@statelyai/graph/layout";
|
|
3
3
|
|
|
4
4
|
//#region src/fixed.ts
|