@statelyai/layout 0.4.0 → 0.4.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-UciYx7lD.mjs → elkjs-CSi7oB5J.mjs} +181 -69
- package/dist/{elkjs-ggjsBuVP.cjs → elkjs-CnBvdyBu.cjs} +181 -69
- 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-BJu8A21s.cjs} +474 -158
- package/dist/{layered-C7ERzZjR.mjs → layered-BsAdm_u8.mjs} +469 -153
- package/dist/{layout-engine-DAw06RhN.cjs → layout-engine-BFP1TZbF.cjs} +1 -1
- package/dist/{layout-engine-DC3IYkIy.mjs → layout-engine-DnlmGxHL.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +135 -33
- package/src/elkjs/layout-quality.ts +81 -33
- package/src/layered/bk-node-placement.ts +10 -7
- package/src/layered/crossing-counter.ts +27 -9
- package/src/layered/crossing-graph.ts +2 -2
- package/src/layered/greedy-switch-decider.ts +44 -17
- package/src/layered/hierarchy-sweepiness.ts +37 -41
- package/src/layered/index.ts +15 -21
- package/src/layered/loop-envelopes.ts +10 -4
- package/src/layered/node-lookup.ts +29 -0
- package/src/layered/opposing-tracks.ts +108 -8
- package/src/layered/orthogonal-hypersegments.ts +69 -7
- package/src/layered/port-distributor.ts +26 -14
- package/src/layered/port-edge-order.ts +2 -1
- package/src/layered/spacing.ts +4 -3
- package/src/layered/strategies.ts +288 -111
|
@@ -311,6 +311,12 @@ const restoreInitialModelOrder = (input, graph) => {
|
|
|
311
311
|
//#endregion
|
|
312
312
|
//#region src/layered/port-distributor.ts
|
|
313
313
|
/** Stateful clockwise port distribution across successive layer sweeps. */
|
|
314
|
+
const sideRank = {
|
|
315
|
+
NORTH: 0,
|
|
316
|
+
EAST: 1,
|
|
317
|
+
SOUTH: 2,
|
|
318
|
+
WEST: 3
|
|
319
|
+
};
|
|
314
320
|
var CanonicalPortDistributor = class {
|
|
315
321
|
state;
|
|
316
322
|
topologyGraph;
|
|
@@ -359,25 +365,33 @@ var CanonicalPortDistributor = class {
|
|
|
359
365
|
calculatePortRanks(graph, layer, input, nodeRelative) {
|
|
360
366
|
this.prepareTopology(graph);
|
|
361
367
|
let consumed = 0;
|
|
368
|
+
const ends = input ? this.incoming : this.outgoing;
|
|
369
|
+
const ranked = (port) => ends.get(port.id).length > 0;
|
|
362
370
|
for (const node of layer) {
|
|
363
|
-
|
|
364
|
-
const
|
|
371
|
+
let count = 0, northCount = 0;
|
|
372
|
+
for (const port of node.ports) if (ranked(port)) {
|
|
373
|
+
count++;
|
|
374
|
+
if (port.side === "NORTH") northCount++;
|
|
375
|
+
}
|
|
376
|
+
const increment = nodeRelative ? 1 / (count + 1) : 1;
|
|
365
377
|
if (input) {
|
|
366
|
-
let north = consumed +
|
|
367
|
-
let rest = nodeRelative ? consumed + 1 - increment : consumed +
|
|
368
|
-
for (const port of ports) {
|
|
378
|
+
let north = consumed + northCount * increment;
|
|
379
|
+
let rest = nodeRelative ? consumed + 1 - increment : consumed + count;
|
|
380
|
+
for (const port of node.ports) {
|
|
381
|
+
if (!ranked(port)) continue;
|
|
369
382
|
this.state.ranks[port.id] = port.side === "NORTH" ? north : rest;
|
|
370
383
|
if (port.side === "NORTH") north -= increment;
|
|
371
384
|
else rest -= increment;
|
|
372
385
|
}
|
|
373
386
|
} else {
|
|
374
387
|
let position = consumed + increment;
|
|
375
|
-
for (const port of ports) {
|
|
388
|
+
for (const port of node.ports) {
|
|
389
|
+
if (!ranked(port)) continue;
|
|
376
390
|
this.state.ranks[port.id] = position;
|
|
377
391
|
position += increment;
|
|
378
392
|
}
|
|
379
393
|
}
|
|
380
|
-
consumed += nodeRelative ? 1 :
|
|
394
|
+
consumed += nodeRelative ? 1 : count;
|
|
381
395
|
}
|
|
382
396
|
}
|
|
383
397
|
distribute(graph, index, forward, options) {
|
|
@@ -386,7 +400,7 @@ var CanonicalPortDistributor = class {
|
|
|
386
400
|
const degree = (port) => incoming.get(port).length + outgoing.get(port).length;
|
|
387
401
|
const ranks = (layer, input) => this.calculatePortRanks(graph, layer, input, options.nodeRelative);
|
|
388
402
|
const distributeNode = (node, side$1) => {
|
|
389
|
-
if (options.fixedOrder.has(node.id)) return;
|
|
403
|
+
if (options.fixedOrder.has(node.id) || !node.ports.length) return;
|
|
390
404
|
const layerIndex = node.ports.length ? layerByPort.get(node.ports[0].id) : index;
|
|
391
405
|
const size$1 = graph.layers[layerIndex].length;
|
|
392
406
|
for (const face of [
|
|
@@ -396,7 +410,8 @@ var CanonicalPortDistributor = class {
|
|
|
396
410
|
]) {
|
|
397
411
|
let minimum = 0, maximum = 0;
|
|
398
412
|
const inLayer = [];
|
|
399
|
-
for (const port of node.ports
|
|
413
|
+
for (const port of node.ports) {
|
|
414
|
+
if (port.side !== face) continue;
|
|
400
415
|
let sum = 0;
|
|
401
416
|
const cross = port.side === "NORTH" || port.side === "SOUTH";
|
|
402
417
|
if (cross) {
|
|
@@ -414,7 +429,8 @@ var CanonicalPortDistributor = class {
|
|
|
414
429
|
else if (output && !input) sum += position + 1;
|
|
415
430
|
else if (input && output) sum += port.side === "NORTH" ? 0 : large / 2;
|
|
416
431
|
} else {
|
|
417
|
-
|
|
432
|
+
const sameLayer = (peer) => layerByPort.get(peer) === layerIndex;
|
|
433
|
+
if (outgoing.get(port.id).some(sameLayer) || incoming.get(port.id).some(sameLayer)) {
|
|
418
434
|
inLayer.push(port);
|
|
419
435
|
continue;
|
|
420
436
|
}
|
|
@@ -435,12 +451,7 @@ var CanonicalPortDistributor = class {
|
|
|
435
451
|
this.state.barycenters[port.id] = port.side === "EAST" ? above ? minimum - value : maximum + (size$1 + 1 - value) : above ? maximum + value : minimum - (size$1 + 1 - value);
|
|
436
452
|
}
|
|
437
453
|
}
|
|
438
|
-
|
|
439
|
-
NORTH: 0,
|
|
440
|
-
EAST: 1,
|
|
441
|
-
SOUTH: 2,
|
|
442
|
-
WEST: 3
|
|
443
|
-
};
|
|
454
|
+
if (node.ports.length < 2) return;
|
|
444
455
|
node.ports.sort((a, b) => {
|
|
445
456
|
const side$2 = sideRank[a.side] - sideRank[b.side];
|
|
446
457
|
if (side$2) return side$2;
|
|
@@ -506,10 +517,18 @@ function createOrthogonalHypersegments(ports, connections, listRank) {
|
|
|
506
517
|
segmentByPort
|
|
507
518
|
};
|
|
508
519
|
}
|
|
509
|
-
/**
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
520
|
+
/**
|
|
521
|
+
* {@link countOrthogonalHypersegmentCrossings}, optionally separating shared
|
|
522
|
+
* tracks. ELK routes a hyperedge on one shared track. When it joins two or
|
|
523
|
+
* more ports on each side, its edges would run opposite ways along that
|
|
524
|
+
* track, which routing never allows; `separateShared` counts such a
|
|
525
|
+
* hyperedge's edges one by one instead. `shared` reports whether one exists.
|
|
526
|
+
*/
|
|
527
|
+
function scoreOrthogonalHypersegments(ports, connections, separateShared = false) {
|
|
528
|
+
const { segments: merged, segmentByPort } = createOrthogonalHypersegments(ports, connections);
|
|
529
|
+
const sharedSegments = new Set(merged.flatMap((s, i) => s.incoming.length > 1 && s.outgoing.length > 1 ? [i] : []));
|
|
530
|
+
const separating = separateShared && sharedSegments.size > 0;
|
|
531
|
+
const spans = merged.filter((_, i) => !separating || !sharedSegments.has(i)).map((s) => ({
|
|
513
532
|
leftStart: Math.min(...s.incoming),
|
|
514
533
|
leftEnd: Math.max(...s.incoming),
|
|
515
534
|
rightStart: Math.min(...s.outgoing),
|
|
@@ -522,6 +541,43 @@ function countOrthogonalHypersegmentCrossings(ports, connections) {
|
|
|
522
541
|
if (a.leftStart < b.leftEnd && b.leftStart < a.leftEnd) count++;
|
|
523
542
|
if (a.rightStart < b.rightEnd && b.rightStart < a.rightEnd) count++;
|
|
524
543
|
}
|
|
544
|
+
if (separating) {
|
|
545
|
+
const position = new Map(ports.map((p) => [p.id, p.position]));
|
|
546
|
+
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)]);
|
|
547
|
+
for (const [left, right] of edges) for (const b of spans) {
|
|
548
|
+
if ((left - b.leftStart) * (right - b.rightStart) < 0) count++;
|
|
549
|
+
if (left < b.leftEnd && b.leftStart < left) count++;
|
|
550
|
+
if (right < b.rightEnd && b.rightStart < right) count++;
|
|
551
|
+
}
|
|
552
|
+
count += strictInversions(edges);
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
count,
|
|
556
|
+
shared: sharedSegments.size > 0
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/** Pairs whose ends are strictly inverted: two separated edges that cross, in O(n log n). */
|
|
560
|
+
function strictInversions(pairs) {
|
|
561
|
+
const ranks = [...new Set(pairs.map(([, right]) => right))].sort((a, b) => a - b);
|
|
562
|
+
const rankOf = new Map(ranks.map((value, index) => [value, index + 1]));
|
|
563
|
+
const tree = new Array(ranks.length + 1).fill(0);
|
|
564
|
+
const atMost = (rank) => {
|
|
565
|
+
let sum = 0;
|
|
566
|
+
for (let i = rank; i > 0; i -= i & -i) sum += tree[i];
|
|
567
|
+
return sum;
|
|
568
|
+
};
|
|
569
|
+
const sorted = [...pairs].sort((a, b) => a[0] - b[0]);
|
|
570
|
+
let inserted = 0, count = 0;
|
|
571
|
+
for (let start = 0; start < sorted.length;) {
|
|
572
|
+
let end = start;
|
|
573
|
+
while (end < sorted.length && sorted[end][0] === sorted[start][0]) end++;
|
|
574
|
+
for (let i = start; i < end; i++) count += inserted - atMost(rankOf.get(sorted[i][1]));
|
|
575
|
+
for (let i = start; i < end; i++) {
|
|
576
|
+
for (let r = rankOf.get(sorted[i][1]); r <= ranks.length; r += r & -r) tree[r]++;
|
|
577
|
+
inserted++;
|
|
578
|
+
}
|
|
579
|
+
start = end;
|
|
580
|
+
}
|
|
525
581
|
return count;
|
|
526
582
|
}
|
|
527
583
|
|
|
@@ -555,7 +611,7 @@ var IndexTree = class {
|
|
|
555
611
|
};
|
|
556
612
|
/** Score a canonical candidate without changing its node or port orders. */
|
|
557
613
|
function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
558
|
-
|
|
614
|
+
let nodes;
|
|
559
615
|
const layerByPort = /* @__PURE__ */ new Map();
|
|
560
616
|
const incoming = /* @__PURE__ */ new Map(), outgoing = /* @__PURE__ */ new Map();
|
|
561
617
|
for (const [layer, members] of graph.layers.entries()) for (const node of members) for (const port of node.ports) {
|
|
@@ -567,7 +623,6 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
567
623
|
outgoing.get(edge.source).push(edge.target);
|
|
568
624
|
incoming.get(edge.target).push(edge.source);
|
|
569
625
|
}
|
|
570
|
-
const connected = (id) => [...incoming.get(id), ...outgoing.get(id)];
|
|
571
626
|
const degree = (id) => incoming.get(id).length + outgoing.get(id).length;
|
|
572
627
|
const face = (layer, side, topDown = true) => (topDown ? layer : [...layer].reverse()).flatMap((node) => {
|
|
573
628
|
const ports = node.ports.filter((port) => port.side === side);
|
|
@@ -580,7 +635,9 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
580
635
|
for (const [position, port] of ports.entries()) {
|
|
581
636
|
tree.remove(position);
|
|
582
637
|
const pending = [];
|
|
583
|
-
|
|
638
|
+
const before = incoming.get(port.id), after = outgoing.get(port.id);
|
|
639
|
+
for (let index = 0; index < before.length + after.length; index++) {
|
|
640
|
+
const peer = index < before.length ? before[index] : after[index - before.length];
|
|
584
641
|
if (inLayerOnly && layerByPort.get(peer) !== layerByPort.get(port.id)) {
|
|
585
642
|
crossings$1 += tree.size;
|
|
586
643
|
continue;
|
|
@@ -624,7 +681,7 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
624
681
|
for (const [position, port] of ports.entries()) {
|
|
625
682
|
tree.remove(position);
|
|
626
683
|
const node = ownerByPort.get(port.id);
|
|
627
|
-
const targets = node.type === "NORMAL" ? nodes.get(port.dummy).ports.map((peer) => ({
|
|
684
|
+
const targets = node.type === "NORMAL" ? (nodes ??= new Map(graph.layers.flat().map((node$1) => [node$1.id, node$1]))).get(port.dummy).ports.map((peer) => ({
|
|
628
685
|
id: peer.id,
|
|
629
686
|
degree: degree(peer.id)
|
|
630
687
|
})) : node.type === "LONG_EDGE" ? node.ports.filter((peer) => peer.id !== port.id).slice(0, 1).map((peer) => ({
|
|
@@ -650,21 +707,22 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
650
707
|
betweenLayers: 0,
|
|
651
708
|
inLayer: 0,
|
|
652
709
|
northSouth: 0,
|
|
653
|
-
total: 0
|
|
710
|
+
total: 0,
|
|
711
|
+
sharedTracks: false
|
|
654
712
|
};
|
|
655
713
|
if (!graph.layers.length) return score;
|
|
656
714
|
score.inLayer += countPorts(face(graph.layers[0], "WEST"), true);
|
|
657
715
|
score.inLayer += countPorts(face(graph.layers.at(-1), "EAST"), true);
|
|
658
716
|
for (const [index, layer] of graph.layers.entries()) {
|
|
659
717
|
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
|
|
718
|
+
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
719
|
const sourcePorts = layer.flatMap((node) => node.ports).filter((port) => outgoing.get(port.id).some((peer) => layerByPort.get(peer) !== index));
|
|
662
720
|
const targetPorts = next.flatMap((node) => {
|
|
663
721
|
const connectedPorts = node.ports.filter((port) => incoming.get(port.id).some((peer) => layerByPort.get(peer) !== index + 1));
|
|
664
722
|
return [...connectedPorts.filter((port) => port.side === "NORTH").reverse(), ...connectedPorts.filter((port) => port.side !== "NORTH").reverse()];
|
|
665
723
|
});
|
|
666
724
|
const sources = new Set(sourcePorts.map((port) => port.id)), targets = new Set(targetPorts.map((port) => port.id));
|
|
667
|
-
|
|
725
|
+
const hyperedges = scoreOrthogonalHypersegments([...sourcePorts.map((port, position) => ({
|
|
668
726
|
id: port.id,
|
|
669
727
|
side: "source",
|
|
670
728
|
position
|
|
@@ -672,7 +730,9 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
672
730
|
id: port.id,
|
|
673
731
|
side: "target",
|
|
674
732
|
position
|
|
675
|
-
}))], graph.edges.filter((edge) => sources.has(edge.source) && targets.has(edge.target)));
|
|
733
|
+
}))], graph.edges.filter((edge) => sources.has(edge.source) && targets.has(edge.target)), boundaryMode === "separated");
|
|
734
|
+
score.betweenLayers += hyperedges.count;
|
|
735
|
+
score.sharedTracks ||= hyperedges.shared;
|
|
676
736
|
score.inLayer += countPorts(face(layer, "EAST"), true) + countPorts(face(next, "WEST"), true);
|
|
677
737
|
} else score.betweenLayers += countPorts([...face(layer, "EAST"), ...face(next, "WEST", false)], false);
|
|
678
738
|
if (layer.some((node) => node.type === "NORTH_SOUTH_PORT")) score.northSouth += countNorthSouth(layer);
|
|
@@ -1054,7 +1114,7 @@ function buildCrossingGraph(plan, layers, incoming, outgoing) {
|
|
|
1054
1114
|
for (const member of ports.get(operation.northSouthDummy)?.values() ?? []) member.port.origin = operation.id;
|
|
1055
1115
|
}
|
|
1056
1116
|
}
|
|
1057
|
-
const sideRank = {
|
|
1117
|
+
const sideRank$1 = {
|
|
1058
1118
|
NORTH: 0,
|
|
1059
1119
|
EAST: 1,
|
|
1060
1120
|
SOUTH: 2,
|
|
@@ -1067,7 +1127,7 @@ function buildCrossingGraph(plan, layers, incoming, outgoing) {
|
|
|
1067
1127
|
id,
|
|
1068
1128
|
type: kind?.type ?? kindOf(id, plan.units),
|
|
1069
1129
|
unit: kind ? kind.unit : plan.units?.units.get(id),
|
|
1070
|
-
ports: [...ports.get(id)?.values() ?? []].sort((a, b) => sideRank[a.port.side] - sideRank[b.port.side] || a.rank - b.rank).map((member) => member.port)
|
|
1130
|
+
ports: [...ports.get(id)?.values() ?? []].sort((a, b) => sideRank$1[a.port.side] - sideRank$1[b.port.side] || a.rank - b.rank).map((member) => member.port)
|
|
1071
1131
|
};
|
|
1072
1132
|
})),
|
|
1073
1133
|
edges: plan.edges.map((edge) => ({
|
|
@@ -1087,7 +1147,8 @@ function splitMixedCrossingPorts(graph, reversed) {
|
|
|
1087
1147
|
const directions = /* @__PURE__ */ new Map();
|
|
1088
1148
|
graph.edges.forEach((edge, index) => {
|
|
1089
1149
|
const bit = reversed[index] ? 2 : 1;
|
|
1090
|
-
|
|
1150
|
+
directions.set(edge.source, (directions.get(edge.source) ?? 0) | bit);
|
|
1151
|
+
directions.set(edge.target, (directions.get(edge.target) ?? 0) | bit);
|
|
1091
1152
|
});
|
|
1092
1153
|
const mixed = /* @__PURE__ */ new Set();
|
|
1093
1154
|
for (const layer of graph.layers) for (const node of layer) for (const port of node.ports) if (directions.get(port.id) === 3 && (port.side === "EAST" || port.side === "WEST")) mixed.add(port.id);
|
|
@@ -1527,20 +1588,46 @@ function selfLoopTracks(loops) {
|
|
|
1527
1588
|
}));
|
|
1528
1589
|
}
|
|
1529
1590
|
|
|
1591
|
+
//#endregion
|
|
1592
|
+
//#region src/layered/node-lookup.ts
|
|
1593
|
+
const indexByNodes = /* @__PURE__ */ new WeakMap();
|
|
1594
|
+
function indexNodes(nodes) {
|
|
1595
|
+
const positions = /* @__PURE__ */ new Map();
|
|
1596
|
+
for (const [index, node] of nodes.entries()) if (!positions.has(node.id)) positions.set(node.id, index);
|
|
1597
|
+
indexByNodes.set(nodes, {
|
|
1598
|
+
length: nodes.length,
|
|
1599
|
+
positions
|
|
1600
|
+
});
|
|
1601
|
+
return positions;
|
|
1602
|
+
}
|
|
1603
|
+
/** Same as `nodes.find((node) => node.id === id)`, indexed per list. */
|
|
1604
|
+
function findNode(nodes, id) {
|
|
1605
|
+
const cached = indexByNodes.get(nodes);
|
|
1606
|
+
let position = (cached && cached.length === nodes.length ? cached.positions : indexNodes(nodes)).get(id);
|
|
1607
|
+
if (position !== void 0 && nodes[position]?.id !== id) position = indexNodes(nodes).get(id);
|
|
1608
|
+
return position !== void 0 && nodes[position]?.id === id ? nodes[position] : nodes.find((node) => node.id === id);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1530
1611
|
//#endregion
|
|
1531
1612
|
//#region src/layered/loop-envelopes.ts
|
|
1532
1613
|
/** Unconstrained implicit loop ports are restored on loop faces after the sweep. */
|
|
1533
1614
|
function hasMovableLoopPorts(input, edge) {
|
|
1534
1615
|
if (edge.sourceId !== edge.targetId || edge.sourcePort !== void 0 || edge.targetPort !== void 0) return false;
|
|
1535
|
-
const node = input.graph.nodes
|
|
1616
|
+
const node = findNode(input.graph.nodes, edge.sourceId);
|
|
1536
1617
|
const constraints = node && input.nodeSettings?.(node)?.portConstraints;
|
|
1537
1618
|
return constraints === void 0 || constraints === "FREE" || constraints === "UNDEFINED";
|
|
1538
1619
|
}
|
|
1539
1620
|
function loopEnvelopes(input) {
|
|
1540
1621
|
const envelopes = /* @__PURE__ */ new Map();
|
|
1541
1622
|
if ((input.settings.edgeRouting ?? "ORTHOGONAL") !== "ORTHOGONAL") return envelopes;
|
|
1623
|
+
const loopsByNodeId = /* @__PURE__ */ new Map();
|
|
1624
|
+
for (const edge of input.graph.edges) if (edge.sourceId === edge.targetId) {
|
|
1625
|
+
const loops = loopsByNodeId.get(edge.sourceId);
|
|
1626
|
+
if (loops) loops.push(edge);
|
|
1627
|
+
else loopsByNodeId.set(edge.sourceId, [edge]);
|
|
1628
|
+
}
|
|
1542
1629
|
for (const node of input.graph.nodes) {
|
|
1543
|
-
const allLoops =
|
|
1630
|
+
const allLoops = loopsByNodeId.get(node.id)?.slice() ?? [];
|
|
1544
1631
|
const constraints = input.nodeSettings?.(node)?.portConstraints;
|
|
1545
1632
|
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"))) {
|
|
1546
1633
|
const size$1 = input.sizes.get(node.id) ?? {
|
|
@@ -1653,7 +1740,7 @@ function preparedLoopEnvelopes(input) {
|
|
|
1653
1740
|
//#region src/layered/port-edge-order.ts
|
|
1654
1741
|
/** ELK traverses connected edges through the node's port list, preserving each port's edge order. */
|
|
1655
1742
|
function connectedEdgesInPortOrder(input, nodeId) {
|
|
1656
|
-
const node = input.graph.nodes
|
|
1743
|
+
const node = findNode(input.graph.nodes, nodeId);
|
|
1657
1744
|
const portRank = new Map(node?.ports?.map((port, index) => [port.name, index]));
|
|
1658
1745
|
const explicitCount = portRank.size;
|
|
1659
1746
|
return input.graph.edges.map((edge, index) => ({
|
|
@@ -2542,8 +2629,8 @@ function nodeNodeSpacing(input, firstId, secondId) {
|
|
|
2542
2629
|
const secondBreakingPoint = secondId.startsWith("__layout_breaking:");
|
|
2543
2630
|
const firstLabel = firstId.startsWith("__layout_dummy:label:");
|
|
2544
2631
|
const secondLabel = secondId.startsWith("__layout_dummy:label:");
|
|
2545
|
-
const firstNode = input.graph.nodes
|
|
2546
|
-
const secondNode = input.graph.nodes
|
|
2632
|
+
const firstNode = findNode(input.graph.nodes, firstId);
|
|
2633
|
+
const secondNode = findNode(input.graph.nodes, secondId);
|
|
2547
2634
|
const firstExternal = firstNode !== void 0 && externalPortDummyOf(firstNode) !== void 0;
|
|
2548
2635
|
const secondExternal = secondNode !== void 0 && externalPortDummyOf(secondNode) !== void 0;
|
|
2549
2636
|
const firstDummy = firstId.startsWith("__layout_dummy:") || firstBreakingPoint || firstExternal;
|
|
@@ -2551,7 +2638,7 @@ function nodeNodeSpacing(input, firstId, secondId) {
|
|
|
2551
2638
|
const spacingName = firstExternal && secondExternal ? "spacing.portPort" : firstExternal && secondLabel || secondExternal && firstLabel ? "spacing.labelPortVertical" : firstLabel && secondLabel ? "spacing.edgeEdge" : firstLabel && !secondDummy || secondLabel && !firstDummy ? "spacing.node" : firstLabel && secondDummy || secondLabel && firstDummy ? "spacing.edgeNode" : firstDummy && secondDummy && firstBreakingPoint === secondBreakingPoint ? "spacing.edgeEdge" : firstDummy || secondDummy ? "spacing.edgeNode" : "spacing.node";
|
|
2552
2639
|
let spacing = spacingName === "spacing.portPort" ? Number(input.settings["spacing.portPort"] ?? 10) : spacingName === "spacing.labelPortVertical" ? Number(input.settings["spacing.labelPortVertical"] ?? 1) : spacingName === "spacing.edgeEdge" ? Number(input.settings["spacing.edgeEdge"] ?? 10) : spacingName === "spacing.edgeNode" ? Number(input.settings["spacing.edgeNode"] ?? 10) : input.spacing.node;
|
|
2553
2640
|
for (const id of [firstId, secondId]) {
|
|
2554
|
-
const node = input.graph.nodes
|
|
2641
|
+
const node = findNode(input.graph.nodes, id);
|
|
2555
2642
|
if (!node) continue;
|
|
2556
2643
|
const individual = input.nodeSettings?.(node)?.["spacing.individual"];
|
|
2557
2644
|
if (individual && typeof individual === "object") {
|
|
@@ -3362,16 +3449,35 @@ function sidePorts(node, side) {
|
|
|
3362
3449
|
const ports = node.ports.filter((port) => port.side === side);
|
|
3363
3450
|
return side === "WEST" || side === "SOUTH" ? ports.reverse() : ports;
|
|
3364
3451
|
}
|
|
3452
|
+
function greedySwitchTopology(graph) {
|
|
3453
|
+
const topology = {
|
|
3454
|
+
incoming: /* @__PURE__ */ new Map(),
|
|
3455
|
+
outgoing: /* @__PURE__ */ new Map(),
|
|
3456
|
+
layerByPort: /* @__PURE__ */ new Map(),
|
|
3457
|
+
ownerByPort: /* @__PURE__ */ new Map()
|
|
3458
|
+
};
|
|
3459
|
+
for (const [layer, nodes] of graph.layers.entries()) for (const node of nodes) for (const port of node.ports) {
|
|
3460
|
+
topology.layerByPort.set(port.id, layer);
|
|
3461
|
+
topology.ownerByPort.set(port.id, node);
|
|
3462
|
+
topology.incoming.set(port.id, []);
|
|
3463
|
+
topology.outgoing.set(port.id, []);
|
|
3464
|
+
}
|
|
3465
|
+
for (const edge of graph.edges) {
|
|
3466
|
+
topology.outgoing.get(edge.source)?.push(edge.target);
|
|
3467
|
+
topology.incoming.get(edge.target)?.push(edge.source);
|
|
3468
|
+
}
|
|
3469
|
+
return topology;
|
|
3470
|
+
}
|
|
3365
3471
|
/**
|
|
3366
3472
|
* Decides greedy switches in one free layer from local two-node crossing
|
|
3367
3473
|
* counts, exactly as ELK does; these estimates can differ from a global recount.
|
|
3368
3474
|
*/
|
|
3369
3475
|
var GreedySwitchDecider = class {
|
|
3370
3476
|
#free;
|
|
3371
|
-
#incoming
|
|
3372
|
-
#outgoing
|
|
3373
|
-
#layerByPort
|
|
3374
|
-
#ownerByPort
|
|
3477
|
+
#incoming;
|
|
3478
|
+
#outgoing;
|
|
3479
|
+
#layerByPort;
|
|
3480
|
+
#ownerByPort;
|
|
3375
3481
|
#neighbourPositions = /* @__PURE__ */ new Map();
|
|
3376
3482
|
#adjacencies = /* @__PURE__ */ new Map();
|
|
3377
3483
|
#matrix = /* @__PURE__ */ new Map();
|
|
@@ -3379,19 +3485,13 @@ var GreedySwitchDecider = class {
|
|
|
3379
3485
|
#cardinality = /* @__PURE__ */ new Map();
|
|
3380
3486
|
#inLayerSize = /* @__PURE__ */ new Map();
|
|
3381
3487
|
#oneSided;
|
|
3382
|
-
constructor(graph, freeLayerIndex, oneSided) {
|
|
3488
|
+
constructor(graph, freeLayerIndex, oneSided, topology = greedySwitchTopology(graph)) {
|
|
3383
3489
|
this.#free = graph.layers[freeLayerIndex] ?? [];
|
|
3384
3490
|
this.#oneSided = oneSided;
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
this.#outgoing.set(port.id, []);
|
|
3390
|
-
}
|
|
3391
|
-
for (const edge of graph.edges) {
|
|
3392
|
-
this.#outgoing.get(edge.source)?.push(edge.target);
|
|
3393
|
-
this.#incoming.get(edge.target)?.push(edge.source);
|
|
3394
|
-
}
|
|
3491
|
+
this.#incoming = topology.incoming;
|
|
3492
|
+
this.#outgoing = topology.outgoing;
|
|
3493
|
+
this.#layerByPort = topology.layerByPort;
|
|
3494
|
+
this.#ownerByPort = topology.ownerByPort;
|
|
3395
3495
|
for (const [layerIndex, side] of [[freeLayerIndex - 1, "EAST"], [freeLayerIndex + 1, "WEST"]]) {
|
|
3396
3496
|
let position = 0;
|
|
3397
3497
|
for (const node of graph.layers[layerIndex] ?? []) for (const port of sidePorts(node, side)) this.#neighbourPositions.set(port.id, position++);
|
|
@@ -3641,6 +3741,18 @@ function cycleModelOrder(input) {
|
|
|
3641
3741
|
const count = Math.max(1, input.graph.nodes.length);
|
|
3642
3742
|
return new Map(input.graph.nodes.map((node, index) => [node.id, enforced ? Number(input.nodeSettings?.(node)?.["considerModelOrder.groupModelOrder.cycleBreakingId"] ?? 0) * count + index : index]));
|
|
3643
3743
|
}
|
|
3744
|
+
/**
|
|
3745
|
+
* Internal setting: how crossing minimization models ports and tracks.
|
|
3746
|
+
* `mixedPorts` (default true) counts a port carrying both directions as two
|
|
3747
|
+
* ports and groups its neighbours by direction. `separateSharedTracks`
|
|
3748
|
+
* (default false) counts a hyperedge joining two or more ports on each side
|
|
3749
|
+
* of its gap edge by edge, since routing never lets edges run opposite ways
|
|
3750
|
+
* along one track. A `probe` records whether either choice can change the
|
|
3751
|
+
* order. Each helps on average but steers the sweeps into worse orders on
|
|
3752
|
+
* some graphs, so the elkjs facade also lays such graphs out with the
|
|
3753
|
+
* alternatives and keeps the best result.
|
|
3754
|
+
*/
|
|
3755
|
+
const CROSSING_MODEL = "internal.crossingModel";
|
|
3644
3756
|
const breakCyclesWithDepthFirstSearch = (input) => {
|
|
3645
3757
|
const outgoing = /* @__PURE__ */ new Map();
|
|
3646
3758
|
for (const node of input.graph.nodes) outgoing.set(node.id, []);
|
|
@@ -4128,8 +4240,7 @@ function applyLayerConstraintOrder(input, order) {
|
|
|
4128
4240
|
return {
|
|
4129
4241
|
...order,
|
|
4130
4242
|
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))];
|
|
4243
|
+
return [...layer.filter((id) => !constrained.has(id)), ...layer.filter((id) => constrained.has(id))];
|
|
4133
4244
|
})
|
|
4134
4245
|
};
|
|
4135
4246
|
}
|
|
@@ -4252,6 +4363,7 @@ function applyGreedySwitch(input, orientation, order) {
|
|
|
4252
4363
|
if (oneSidedBoundary !== void 0) return countBoundary(oneSidedBoundary);
|
|
4253
4364
|
return (layerIndex > 0 ? countBoundary(layerIndex - 1) : 0) + (layerIndex + 1 < layers.length ? countBoundary(layerIndex) : 0);
|
|
4254
4365
|
};
|
|
4366
|
+
let greedyTopology;
|
|
4255
4367
|
const sweep = (forward$1) => {
|
|
4256
4368
|
let changed = false;
|
|
4257
4369
|
const indices = forward$1 ? Array.from({ length: layers.length }, (_, index) => index) : Array.from({ length: layers.length }, (_, index) => layers.length - index - 1);
|
|
@@ -4261,7 +4373,7 @@ function applyGreedySwitch(input, orientation, order) {
|
|
|
4261
4373
|
if (greedyGraph) {
|
|
4262
4374
|
const units = getCrossingUnits(input);
|
|
4263
4375
|
greedyGraph.layers = layers.map((ids) => ids.map((id) => greedyNodes.get(id)));
|
|
4264
|
-
const decider = new GreedySwitchDecider(greedyGraph, layerIndex, type === "ONE_SIDED" ? forward$1 ? "WEST" : "EAST" : void 0);
|
|
4376
|
+
const decider = new GreedySwitchDecider(greedyGraph, layerIndex, type === "ONE_SIDED" ? forward$1 ? "WEST" : "EAST" : void 0, greedyTopology ??= greedySwitchTopology(greedyGraph));
|
|
4265
4377
|
do {
|
|
4266
4378
|
improved$1 = false;
|
|
4267
4379
|
for (let index = 0; index + 1 < layer.length; index++) {
|
|
@@ -4854,7 +4966,9 @@ function sortLayerByAdjacentPosition(layer, ranksByNodeId, statistic, random, pr
|
|
|
4854
4966
|
if (units && !forcedCompare) layer.splice(0, layer.length, ...resolveCrossingConstraints(layer, adjacentPosition, units));
|
|
4855
4967
|
}
|
|
4856
4968
|
function clonePortOrders(orders) {
|
|
4857
|
-
|
|
4969
|
+
const clone = /* @__PURE__ */ new Map();
|
|
4970
|
+
if (orders) for (const [id, edges] of orders) clone.set(id, edges.slice());
|
|
4971
|
+
return clone;
|
|
4858
4972
|
}
|
|
4859
4973
|
function minimizeCrossingsWithLayerSweep(statistic, sweeps = 7) {
|
|
4860
4974
|
return (input, orientation, assignment) => createLayerSweepSession(input, orientation, assignment, statistic, sweeps).minimize();
|
|
@@ -4896,6 +5010,18 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
4896
5010
|
}
|
|
4897
5011
|
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
4898
5012
|
const edgeById = new Map(input.graph.edges.map((edge) => [edge.id, edge]));
|
|
5013
|
+
const boundaryPortIndex = (nodeId, edgeId, incoming) => {
|
|
5014
|
+
const edge = edgeById.get(edgeId);
|
|
5015
|
+
const name = incoming !== orientation.reversedEdgeIds.has(edgeId) ? edge.targetPort : edge.sourcePort;
|
|
5016
|
+
if (!name?.startsWith("__native_hierarchy")) return -1;
|
|
5017
|
+
return nodeById.get(nodeId).ports?.findIndex((port) => port.name === name) ?? -1;
|
|
5018
|
+
};
|
|
5019
|
+
for (const [orders, incoming] of [[outputPortOrder, false], [inputPortOrder, true]]) for (const [nodeId, edgeIds] of orders) {
|
|
5020
|
+
if (edgeIds.length < 2) continue;
|
|
5021
|
+
const slots = edgeIds.flatMap((id, slot) => boundaryPortIndex(nodeId, id, incoming) < 0 ? [] : [slot]);
|
|
5022
|
+
const sorted = slots.map((slot) => edgeIds[slot]).sort((a, b) => boundaryPortIndex(nodeId, a, incoming) - boundaryPortIndex(nodeId, b, incoming));
|
|
5023
|
+
slots.forEach((slot, index) => edgeIds[slot] = sorted[index]);
|
|
5024
|
+
}
|
|
4899
5025
|
const edgeModelOrder = new Map(input.graph.edges.map((edge, index) => [edge.id, index]));
|
|
4900
5026
|
const hierarchicalNodes = /* @__PURE__ */ new Set();
|
|
4901
5027
|
const fixedOrderNodes = new Set(input.graph.nodes.filter((node) => {
|
|
@@ -4933,10 +5059,26 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
4933
5059
|
if (edgeIds.length >= 4) edgeIds.splice(0, edgeIds.length - 1, ...edgeIds.slice(0, -1).reverse());
|
|
4934
5060
|
}
|
|
4935
5061
|
const buildCrossingGraph$1 = createCrossingGraphBuilder(input);
|
|
5062
|
+
const crossingModel = input.settings[CROSSING_MODEL] ?? {};
|
|
5063
|
+
const mixedPorts = crossingModel.mixedPorts !== false, probe = crossingModel.probe;
|
|
5064
|
+
const physicalSlot = /* @__PURE__ */ new Map();
|
|
4936
5065
|
const countCrossings = (candidateLayers) => {
|
|
4937
5066
|
if (exactPortSweep) {
|
|
4938
5067
|
const graph = buildCrossingGraph$1(orientation, candidateLayers, inputPortOrder, outputPortOrder);
|
|
4939
|
-
|
|
5068
|
+
for (const layer of graph.layers) for (const node of layer) {
|
|
5069
|
+
const physical = node.ports.length > 1 ? canonicalNodes.get(node.id)?.ports : void 0;
|
|
5070
|
+
if (physical?.length !== node.ports.length) continue;
|
|
5071
|
+
physicalSlot.clear();
|
|
5072
|
+
for (const [index, port] of physical.entries()) physicalSlot.set(port.id, index);
|
|
5073
|
+
if (node.ports.every((port) => physicalSlot.has(port.id))) node.ports.sort((a, b) => physicalSlot.get(a.id) - physicalSlot.get(b.id));
|
|
5074
|
+
}
|
|
5075
|
+
const split = mixedPorts ? splitMixedCrossingPorts(graph, canonicalReversed) : graph;
|
|
5076
|
+
const score = countAllCrossings(split, crossingModel.separateSharedTracks ? "separated" : "hyperedges");
|
|
5077
|
+
if (probe) {
|
|
5078
|
+
if (split !== graph) probe.mixedPorts = true;
|
|
5079
|
+
if (score.sharedTracks) probe.sharedTracks = true;
|
|
5080
|
+
}
|
|
5081
|
+
let total = score.total;
|
|
4940
5082
|
const nodeInfluence = Number(input.settings["considerModelOrder.crossingCounterNodeInfluence"] ?? 0);
|
|
4941
5083
|
if ((input.settings["considerModelOrder.strategy"] ?? "NONE") !== "NONE" && nodeInfluence !== 0) {
|
|
4942
5084
|
const changes = countModelOrderChanges(input, orientation, graph);
|
|
@@ -4986,11 +5128,12 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
4986
5128
|
const nodes = fixedLayer.map((id) => canonicalNodes.get(id));
|
|
4987
5129
|
canonicalGraph.layers[fixedIndex] = nodes;
|
|
4988
5130
|
canonicalDistributor.calculatePortRanks(canonicalGraph, nodes, !forward, nodeRelativePortRanks);
|
|
4989
|
-
const
|
|
4990
|
-
|
|
5131
|
+
const byEnd = forward ? canonicalEdgesBySource : canonicalEdgesByTarget;
|
|
5132
|
+
const indices = fixedLayer.flatMap((id) => byEnd.get(id) ?? []).sort((a, b) => a - b);
|
|
5133
|
+
for (const index of indices) {
|
|
4991
5134
|
const edge = canonicalEdges[index];
|
|
4992
5135
|
const [source, target] = canonicalOrientedEndpoints[index];
|
|
4993
|
-
if (!
|
|
5136
|
+
if (!freeIds.has(forward ? target : source)) continue;
|
|
4994
5137
|
const endpoint = canonicalEndpoints.get(edge.id);
|
|
4995
5138
|
const rank = canonicalDistributor.state.ranks[forward ? endpoint.source : endpoint.target];
|
|
4996
5139
|
ranks.get(forward ? target : source).push(rank);
|
|
@@ -5029,6 +5172,17 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5029
5172
|
}
|
|
5030
5173
|
for (const id of freeLayer) {
|
|
5031
5174
|
const order = (forward ? inputPortOrder : outputPortOrder).get(id) ?? [];
|
|
5175
|
+
const visit = (edgeId) => {
|
|
5176
|
+
const [source, target] = getOrientedEndpoints(edgeById.get(edgeId), orientation);
|
|
5177
|
+
const neighbor = forward ? source : target;
|
|
5178
|
+
const rank$1 = edgeRanks.get(edgeId);
|
|
5179
|
+
if (assignment.layerByNodeId.get(neighbor) === assignment.layerByNodeId.get(id)) visits.get(id).push(neighbor);
|
|
5180
|
+
else if (rank$1 !== void 0) visits.get(id).push(rank$1);
|
|
5181
|
+
};
|
|
5182
|
+
if (order.length < 2) {
|
|
5183
|
+
for (const edgeId of order) visit(edgeId);
|
|
5184
|
+
continue;
|
|
5185
|
+
}
|
|
5032
5186
|
const groups = /* @__PURE__ */ new Map();
|
|
5033
5187
|
for (const edgeId of order) {
|
|
5034
5188
|
const edge = edgeById.get(edgeId);
|
|
@@ -5037,17 +5191,11 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5037
5191
|
if (group) group.push(edgeId);
|
|
5038
5192
|
else groups.set(key, [edgeId]);
|
|
5039
5193
|
}
|
|
5194
|
+
const listed = units?.edgeListRanks?.[forward ? "incoming" : "outgoing"].get(id);
|
|
5195
|
+
const rank = (edgeId) => listed?.get(listedEdgeId(edgeId));
|
|
5040
5196
|
for (const group of groups.values()) {
|
|
5041
|
-
|
|
5042
|
-
const
|
|
5043
|
-
group.sort((a, b) => (rank(a) !== void 0 && rank(b) !== void 0 ? rank(a) - rank(b) : 0) || edgeModelOrder.get(a) - edgeModelOrder.get(b));
|
|
5044
|
-
for (const edgeId of group) {
|
|
5045
|
-
const [source, target] = getOrientedEndpoints(edgeById.get(edgeId), orientation);
|
|
5046
|
-
const neighbor = forward ? source : target;
|
|
5047
|
-
const rank$1 = edgeRanks.get(edgeId);
|
|
5048
|
-
if (assignment.layerByNodeId.get(neighbor) === assignment.layerByNodeId.get(id)) visits.get(id).push(neighbor);
|
|
5049
|
-
else if (rank$1 !== void 0) visits.get(id).push(rank$1);
|
|
5050
|
-
}
|
|
5197
|
+
if (group.length > 1) group.sort((a, b) => (rank(a) !== void 0 && rank(b) !== void 0 ? rank(a) - rank(b) : 0) || edgeModelOrder.get(a) - edgeModelOrder.get(b));
|
|
5198
|
+
for (const edgeId of group) visit(edgeId);
|
|
5051
5199
|
}
|
|
5052
5200
|
}
|
|
5053
5201
|
return {
|
|
@@ -5072,6 +5220,14 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5072
5220
|
const canonicalEdges = input.graph.edges.filter((edge) => edge.sourceId !== edge.targetId);
|
|
5073
5221
|
const canonicalReversed = canonicalEdges.map((edge) => orientation.reversedEdgeIds.has(edge.id));
|
|
5074
5222
|
const canonicalOrientedEndpoints = canonicalEdges.map((edge) => getOrientedEndpoints(edge, orientation));
|
|
5223
|
+
const canonicalEdgesBySource = /* @__PURE__ */ new Map(), canonicalEdgesByTarget = /* @__PURE__ */ new Map();
|
|
5224
|
+
canonicalOrientedEndpoints.forEach(([source, target], index) => {
|
|
5225
|
+
for (const [byEnd, id] of [[canonicalEdgesBySource, source], [canonicalEdgesByTarget, target]]) {
|
|
5226
|
+
const list = byEnd.get(id);
|
|
5227
|
+
if (list) list.push(index);
|
|
5228
|
+
else byEnd.set(id, [index]);
|
|
5229
|
+
}
|
|
5230
|
+
});
|
|
5075
5231
|
const canonicalEndpoints = new Map(canonicalEdges.map((edge, index) => [edge.id, canonicalGraph.edges[index]]));
|
|
5076
5232
|
const canonicalFixedOrder = new Set(input.graph.nodes.filter((node) => {
|
|
5077
5233
|
const constraint = input.nodeSettings?.(node)?.portConstraints;
|
|
@@ -5087,6 +5243,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5087
5243
|
hierarchical: hierarchicalNodes
|
|
5088
5244
|
});
|
|
5089
5245
|
for (const index of touched) for (const node of canonicalGraph.layers[index]) {
|
|
5246
|
+
if (node.ports.length < 2 || (outputPortOrder.get(node.id)?.length ?? 0) < 2 && (inputPortOrder.get(node.id)?.length ?? 0) < 2) continue;
|
|
5090
5247
|
const ordinal = new Map(node.ports.map((port, index$1) => [port.id, index$1]));
|
|
5091
5248
|
for (const [orders, source] of [[outputPortOrder, true], [inputPortOrder, false]]) orders.get(node.id)?.sort((left, right) => ordinal.get(canonicalEndpoints.get(left)[source ? "source" : "target"]) - ordinal.get(canonicalEndpoints.get(right)[source ? "source" : "target"]));
|
|
5092
5249
|
}
|
|
@@ -5121,6 +5278,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5121
5278
|
*/
|
|
5122
5279
|
const constrained = new Set([...units?.units.keys() ?? [], ...[...units?.successors ?? []].flatMap(([id, after]) => [id, ...after])]);
|
|
5123
5280
|
const groupMixedPortDirections = (free, fixed, beyond = []) => {
|
|
5281
|
+
if (!mixedPorts) return;
|
|
5124
5282
|
const fixedIds = new Set(fixed);
|
|
5125
5283
|
const groups = /* @__PURE__ */ new Map();
|
|
5126
5284
|
for (const id of free) {
|
|
@@ -5143,6 +5301,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5143
5301
|
groups.set(key, group);
|
|
5144
5302
|
}
|
|
5145
5303
|
if (![...groups.values()].some((g) => g.some((m) => m.reversed) && g.some((m) => !m.reversed))) return;
|
|
5304
|
+
if (probe) probe.mixedPorts = true;
|
|
5146
5305
|
const index = new Map(free.map((id, i) => [id, i]));
|
|
5147
5306
|
const far = new Map(beyond.map((id, i) => [id, i]));
|
|
5148
5307
|
const reach = (members) => {
|
|
@@ -5302,15 +5461,23 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5302
5461
|
shuffleFirstLayer,
|
|
5303
5462
|
sweep,
|
|
5304
5463
|
countCrossings: () => countCrossings(working),
|
|
5305
|
-
snapshot: () =>
|
|
5306
|
-
physicalPortOrderByNodeId
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5464
|
+
snapshot: () => {
|
|
5465
|
+
const physicalPortOrderByNodeId = /* @__PURE__ */ new Map();
|
|
5466
|
+
for (const [id, node] of canonicalNodes) physicalPortOrderByNodeId.set(id, node.ports.map((port) => port.id));
|
|
5467
|
+
return {
|
|
5468
|
+
physicalPortOrderByNodeId,
|
|
5469
|
+
layers: working.map((layer) => [...layer]),
|
|
5470
|
+
inputPortOrderByNodeId: clonePortOrders(inputPortOrder),
|
|
5471
|
+
outputPortOrderByNodeId: clonePortOrders(outputPortOrder)
|
|
5472
|
+
};
|
|
5473
|
+
},
|
|
5311
5474
|
restore,
|
|
5312
5475
|
finish,
|
|
5313
5476
|
currentLayers: () => working,
|
|
5477
|
+
crossingGraph: () => ({
|
|
5478
|
+
layers: working.map((layer) => layer.map((id) => canonicalNodes.get(id))),
|
|
5479
|
+
edges: canonicalGraph.edges
|
|
5480
|
+
}),
|
|
5314
5481
|
physicalPortOrder: (id) => canonicalNodes.get(id)?.ports.map((port) => port.id) ?? [],
|
|
5315
5482
|
edgeOrders: (id) => ({
|
|
5316
5483
|
incoming: inputPortOrder.get(id) ?? [],
|
|
@@ -5713,7 +5880,7 @@ function sumWithSpacing(ids, input, axis) {
|
|
|
5713
5880
|
}, 0);
|
|
5714
5881
|
}
|
|
5715
5882
|
function nodeFlowOffset(input, id, layerFlowSize, nodeFlowSize, layerByNodeId, margins) {
|
|
5716
|
-
const node = input.graph.nodes
|
|
5883
|
+
const node = findNode(input.graph.nodes, id);
|
|
5717
5884
|
const alignment = node ? input.nodeSettings?.(node)?.alignment ?? input.settings.alignment : input.settings.alignment;
|
|
5718
5885
|
let ratio;
|
|
5719
5886
|
if (alignment === "LEFT" || alignment === "TOP") ratio = 0;
|
|
@@ -5962,7 +6129,56 @@ function simplifyRoute$1(points) {
|
|
|
5962
6129
|
function implicitEdgeEndpoints(input, placement, orientation) {
|
|
5963
6130
|
const horizontal$1 = input.direction === "left" || input.direction === "right";
|
|
5964
6131
|
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
5965
|
-
const
|
|
6132
|
+
const nodeSettingsByNode = /* @__PURE__ */ new Map();
|
|
6133
|
+
const nodeSettingsOf = (node) => {
|
|
6134
|
+
if (!nodeSettingsByNode.has(node)) nodeSettingsByNode.set(node, input.nodeSettings?.(node));
|
|
6135
|
+
return nodeSettingsByNode.get(node);
|
|
6136
|
+
};
|
|
6137
|
+
const portSettingsByNode = /* @__PURE__ */ new Map();
|
|
6138
|
+
const portSettingsOf = (port, node) => {
|
|
6139
|
+
let byPort = portSettingsByNode.get(node);
|
|
6140
|
+
if (!byPort) portSettingsByNode.set(node, byPort = /* @__PURE__ */ new Map());
|
|
6141
|
+
if (!byPort.has(port)) byPort.set(port, input.portSettings?.(port, node));
|
|
6142
|
+
return byPort.get(port);
|
|
6143
|
+
};
|
|
6144
|
+
const groups = [];
|
|
6145
|
+
const groupsBySide = /* @__PURE__ */ new Map();
|
|
6146
|
+
const modelOrderPromotion = input.settings["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT";
|
|
6147
|
+
const directionReversed = input.direction === "left" || input.direction === "up";
|
|
6148
|
+
const endpointSide = (nodeId, portName) => {
|
|
6149
|
+
const node = nodeById.get(nodeId);
|
|
6150
|
+
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
6151
|
+
const constraints = node ? nodeSettingsOf(node)?.portConstraints : void 0;
|
|
6152
|
+
if (!node || !port || constraints !== "FIXED_SIDE" && constraints !== "FIXED_ORDER" && constraints !== "FIXED_RATIO" && constraints !== "FIXED_POS") return;
|
|
6153
|
+
const configured = portSettingsOf(port, node)?.["port.side"];
|
|
6154
|
+
return configured === "WEST" || configured === "NORTH" ? "before" : configured === "EAST" || configured === "SOUTH" ? "after" : void 0;
|
|
6155
|
+
};
|
|
6156
|
+
const fixedImplicitSide = (nodeId, portName, source) => {
|
|
6157
|
+
const node = nodeById.get(nodeId);
|
|
6158
|
+
const constraints = node && nodeSettingsOf(node)?.portConstraints;
|
|
6159
|
+
if (portName !== void 0 || ![
|
|
6160
|
+
"FIXED_SIDE",
|
|
6161
|
+
"FIXED_ORDER",
|
|
6162
|
+
"FIXED_RATIO",
|
|
6163
|
+
"FIXED_POS"
|
|
6164
|
+
].includes(String(constraints))) return void 0;
|
|
6165
|
+
return source !== directionReversed ? "after" : "before";
|
|
6166
|
+
};
|
|
6167
|
+
const addEntry = (nodeId, side, entry) => {
|
|
6168
|
+
let byNode = groupsBySide.get(side);
|
|
6169
|
+
if (!byNode) groupsBySide.set(side, byNode = /* @__PURE__ */ new Map());
|
|
6170
|
+
const group = byNode.get(nodeId);
|
|
6171
|
+
if (group) group.entries.push(entry);
|
|
6172
|
+
else {
|
|
6173
|
+
const created = {
|
|
6174
|
+
nodeId,
|
|
6175
|
+
side,
|
|
6176
|
+
entries: [entry]
|
|
6177
|
+
};
|
|
6178
|
+
byNode.set(nodeId, created);
|
|
6179
|
+
groups.push(created);
|
|
6180
|
+
}
|
|
6181
|
+
};
|
|
5966
6182
|
for (const edge of input.graph.edges) {
|
|
5967
6183
|
if (hasMovableLoopPorts(input, edge)) continue;
|
|
5968
6184
|
const sourceRect = placement.rectByNodeId.get(edge.sourceId);
|
|
@@ -5970,50 +6186,33 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
5970
6186
|
if (!sourceRect || !targetRect) continue;
|
|
5971
6187
|
const sourceFlow = horizontal$1 ? sourceRect.x : sourceRect.y;
|
|
5972
6188
|
const targetFlow = horizontal$1 ? targetRect.x : targetRect.y;
|
|
5973
|
-
const modelOrderPromotion = input.settings["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT";
|
|
5974
6189
|
const sourceBoundary = externalPortDummyOf(nodeById.get(edge.sourceId) ?? {});
|
|
5975
6190
|
const targetBoundary = externalPortDummyOf(nodeById.get(edge.targetId) ?? {});
|
|
5976
6191
|
const forward = modelOrderPromotion || (sourceFlow === targetFlow && (sourceBoundary || targetBoundary) ? sourceBoundary ? sourceBoundary.port.side === "EAST" || sourceBoundary.port.side === "SOUTH" : targetBoundary.port.side === "WEST" || targetBoundary.port.side === "NORTH" : sourceFlow <= targetFlow);
|
|
5977
6192
|
const feedback = input.settings.feedbackEdges === true && orientation?.reversedEdgeIds.has(edge.id) === true;
|
|
5978
|
-
const directionReversed = input.direction === "left" || input.direction === "up";
|
|
5979
|
-
const endpointSide = (nodeId, portName) => {
|
|
5980
|
-
const node = nodeById.get(nodeId);
|
|
5981
|
-
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
5982
|
-
const constraints = node ? input.nodeSettings?.(node)?.portConstraints : void 0;
|
|
5983
|
-
if (!node || !port || constraints !== "FIXED_SIDE" && constraints !== "FIXED_ORDER" && constraints !== "FIXED_RATIO" && constraints !== "FIXED_POS") return;
|
|
5984
|
-
const configured = input.portSettings?.(port, node)?.["port.side"];
|
|
5985
|
-
return configured === "WEST" || configured === "NORTH" ? "before" : configured === "EAST" || configured === "SOUTH" ? "after" : void 0;
|
|
5986
|
-
};
|
|
5987
|
-
const fixedImplicitSide = (nodeId, portName, source) => {
|
|
5988
|
-
const node = nodeById.get(nodeId);
|
|
5989
|
-
const constraints = node && input.nodeSettings?.(node)?.portConstraints;
|
|
5990
|
-
if (portName !== void 0 || ![
|
|
5991
|
-
"FIXED_SIDE",
|
|
5992
|
-
"FIXED_ORDER",
|
|
5993
|
-
"FIXED_RATIO",
|
|
5994
|
-
"FIXED_POS"
|
|
5995
|
-
].includes(String(constraints))) return void 0;
|
|
5996
|
-
return source !== directionReversed ? "after" : "before";
|
|
5997
|
-
};
|
|
5998
6193
|
const sourceSide = endpointSide(edge.sourceId, edge.sourcePort) ?? fixedImplicitSide(edge.sourceId, edge.sourcePort, true) ?? (feedback ? directionReversed ? "before" : "after" : forward ? "after" : "before");
|
|
5999
6194
|
const targetSide = endpointSide(edge.targetId, edge.targetPort) ?? fixedImplicitSide(edge.targetId, edge.targetPort, false) ?? (feedback ? directionReversed ? "after" : "before" : forward ? "before" : "after");
|
|
6000
|
-
|
|
6001
|
-
const targetKey = `${edge.targetId}:${targetSide}`;
|
|
6002
|
-
const sourceGroup = groups.get(sourceKey) ?? [];
|
|
6003
|
-
sourceGroup.push({
|
|
6195
|
+
addEntry(edge.sourceId, sourceSide, {
|
|
6004
6196
|
edge,
|
|
6005
6197
|
endpoint: "source"
|
|
6006
6198
|
});
|
|
6007
|
-
|
|
6008
|
-
const targetGroup = groups.get(targetKey) ?? [];
|
|
6009
|
-
targetGroup.push({
|
|
6199
|
+
addEntry(edge.targetId, targetSide, {
|
|
6010
6200
|
edge,
|
|
6011
6201
|
endpoint: "target"
|
|
6012
6202
|
});
|
|
6013
|
-
groups.set(targetKey, targetGroup);
|
|
6014
6203
|
}
|
|
6015
6204
|
const result = /* @__PURE__ */ new Map();
|
|
6016
6205
|
const edgeModelOrder = new Map(input.graph.edges.map((edge, index) => [edge.id, index]));
|
|
6206
|
+
const orderIndexes = /* @__PURE__ */ new Map();
|
|
6207
|
+
const orderIndex = (list, id) => {
|
|
6208
|
+
let positions = orderIndexes.get(list);
|
|
6209
|
+
if (!positions) {
|
|
6210
|
+
positions = /* @__PURE__ */ new Map();
|
|
6211
|
+
for (const [index, item] of list.entries()) if (!positions.has(item)) positions.set(item, index);
|
|
6212
|
+
orderIndexes.set(list, positions);
|
|
6213
|
+
}
|
|
6214
|
+
return positions.get(id) ?? -1;
|
|
6215
|
+
};
|
|
6017
6216
|
const placedPorts = /* @__PURE__ */ new Map();
|
|
6018
6217
|
const portSideCounts = /* @__PURE__ */ new Map();
|
|
6019
6218
|
const portSideCount = (node, side) => {
|
|
@@ -6021,22 +6220,19 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6021
6220
|
if (!counts) {
|
|
6022
6221
|
counts = /* @__PURE__ */ new Map();
|
|
6023
6222
|
for (const port of node.ports ?? []) {
|
|
6024
|
-
const portSide =
|
|
6223
|
+
const portSide = portSettingsOf(port, node)?.["port.side"];
|
|
6025
6224
|
counts.set(portSide, (counts.get(portSide) ?? 0) + 1);
|
|
6026
6225
|
}
|
|
6027
6226
|
portSideCounts.set(node, counts);
|
|
6028
6227
|
}
|
|
6029
6228
|
return counts.get(side) ?? 0;
|
|
6030
6229
|
};
|
|
6031
|
-
for (const
|
|
6032
|
-
const split = key.lastIndexOf(":");
|
|
6033
|
-
const nodeId = key.slice(0, split);
|
|
6034
|
-
const side = key.slice(split + 1);
|
|
6230
|
+
for (const { nodeId, side, entries } of groups) {
|
|
6035
6231
|
const rect = placement.rectByNodeId.get(nodeId);
|
|
6036
6232
|
if (!rect) continue;
|
|
6037
6233
|
const mergeEdges = input.settings.mergeEdges === true;
|
|
6038
6234
|
const node = nodeById.get(nodeId);
|
|
6039
|
-
const nodeSettings = node ?
|
|
6235
|
+
const nodeSettings = node ? nodeSettingsOf(node) : void 0;
|
|
6040
6236
|
const hypernode = nodeSettings?.hypernode;
|
|
6041
6237
|
const unzipping = (input.settings["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING";
|
|
6042
6238
|
const crossingStrategy = input.settings["crossingMinimization.strategy"] ?? "LAYER_SWEEP";
|
|
@@ -6055,7 +6251,7 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6055
6251
|
const leftIncoming = left.endpoint === "target" !== (orientation?.reversedEdgeIds.has(left.edge.id) ?? false);
|
|
6056
6252
|
if (leftIncoming === (right.endpoint === "target" !== (orientation?.reversedEdgeIds.has(right.edge.id) ?? false))) {
|
|
6057
6253
|
const selected = (leftIncoming ? sweptOrder?.inputPortOrderByNodeId : sweptOrder?.outputPortOrderByNodeId)?.get(nodeId);
|
|
6058
|
-
const a = selected
|
|
6254
|
+
const a = selected ? orderIndex(selected, left.edge.id) : -1, b = selected ? orderIndex(selected, right.edge.id) : -1;
|
|
6059
6255
|
if (a >= 0 && b >= 0 && a !== b) return (leftIncoming ? -1 : 1) * (a - b);
|
|
6060
6256
|
}
|
|
6061
6257
|
if (interactiveLeftSourceOrder) {
|
|
@@ -6128,9 +6324,9 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6128
6324
|
};
|
|
6129
6325
|
const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
|
|
6130
6326
|
const explicitPort = node?.ports?.find((port) => port.name === portName);
|
|
6131
|
-
const physicalSide = node && explicitPort ?
|
|
6327
|
+
const physicalSide = node && explicitPort ? portSettingsOf(explicitPort, node)?.["port.side"] : void 0;
|
|
6132
6328
|
const singleFixedSidePort = constraints === "FIXED_SIDE" && physicalSide !== void 0 && node?.ports !== void 0 && portSideCount(node, physicalSide) === 1;
|
|
6133
|
-
const authoredAnchor = node && explicitPort ?
|
|
6329
|
+
const authoredAnchor = node && explicitPort ? portSettingsOf(explicitPort, node)?.["port.anchor"] : void 0;
|
|
6134
6330
|
if (portName !== void 0 && node && (constraints === "FIXED_POS" || constraints === "FIXED_RATIO" || constraints === "FIXED_ORDER" || singleFixedSidePort || authoredAnchor !== void 0)) point = getPortPoint(node, portName, rect, point, input.direction, input, placedPorts);
|
|
6135
6331
|
const pair = result.get(edge.id) ?? {
|
|
6136
6332
|
source: point,
|
|
@@ -6341,6 +6537,14 @@ function routeEdges(style) {
|
|
|
6341
6537
|
flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
|
|
6342
6538
|
}
|
|
6343
6539
|
const bodyFlowLayers = flowLayers.map((bounds) => ({ ...bounds }));
|
|
6540
|
+
const placedIdsByFlowLayer = () => {
|
|
6541
|
+
const ids = flowLayers.map(() => []);
|
|
6542
|
+
for (const id of placement.rectByNodeId.keys()) {
|
|
6543
|
+
const layer = flowLayerByNodeId.get(id);
|
|
6544
|
+
if (layer !== void 0) ids[layer].push(id);
|
|
6545
|
+
}
|
|
6546
|
+
return ids;
|
|
6547
|
+
};
|
|
6344
6548
|
const labelFlowLayers = new Set([...flowLayerByNodeId].filter(([id]) => id.startsWith("__layout_dummy:label:")).map(([, layer]) => layer));
|
|
6345
6549
|
for (const [id, rect] of placement.rectByNodeId) {
|
|
6346
6550
|
const node = nodeById.get(id);
|
|
@@ -6417,10 +6621,11 @@ function routeEdges(style) {
|
|
|
6417
6621
|
labelExtraByGap[gap] = Math.max(labelExtraByGap[gap] ?? 0, extra);
|
|
6418
6622
|
}
|
|
6419
6623
|
let labelShift = 0;
|
|
6624
|
+
const labelShiftIds = placedIdsByFlowLayer();
|
|
6420
6625
|
for (const [layerNo, bounds] of flowLayers.entries()) {
|
|
6421
6626
|
if (labelShift !== 0) {
|
|
6422
|
-
for (const
|
|
6423
|
-
|
|
6627
|
+
for (const id of labelShiftIds[layerNo]) {
|
|
6628
|
+
const rect = placement.rectByNodeId.get(id);
|
|
6424
6629
|
mutableRects.set(id, horizontal$1 ? {
|
|
6425
6630
|
...rect,
|
|
6426
6631
|
x: rect.x + labelShift
|
|
@@ -6753,8 +6958,9 @@ function routeEdges(style) {
|
|
|
6753
6958
|
candidate.slot = increasing ? segment.slot : maximumSlot - segment.slot;
|
|
6754
6959
|
if (segment.partner !== void 0) {
|
|
6755
6960
|
const partner = result.segments[segment.partner];
|
|
6756
|
-
|
|
6757
|
-
|
|
6961
|
+
const [first, second] = physicalPort(candidate, "source").graphSource ? [segment, partner] : [partner, segment];
|
|
6962
|
+
candidate.slot = increasing ? first.slot : maximumSlot - first.slot;
|
|
6963
|
+
candidate.secondSlot = increasing ? second.slot : maximumSlot - second.slot;
|
|
6758
6964
|
const sourcePort = portByCandidate.get(candidate).source;
|
|
6759
6965
|
const offset = (increasing ? candidate.sourceCross : candidate.targetCross) - byPort.get(sourcePort).position + (runShift.get(sourcePort) ?? 0);
|
|
6760
6966
|
candidate.crossover = segment.outgoing[0] + offset;
|
|
@@ -6767,18 +6973,21 @@ function routeEdges(style) {
|
|
|
6767
6973
|
const leadingSlots = slotsByGap[0] ?? 0;
|
|
6768
6974
|
const leadingWidth = leadingSlots > 0 ? edgeNodeSpacing + (leadingSlots - 1) * edgeEdgeSpacing : 0;
|
|
6769
6975
|
let nextStart = (flowLayers[0]?.start ?? 0) + leadingWidth;
|
|
6976
|
+
const flowLayerIds = flowLayers.map(() => []);
|
|
6977
|
+
for (const [id, layer] of flowLayerByNodeId) flowLayerIds[layer].push(id);
|
|
6770
6978
|
const externalFlowLayers = new Set(flowLayers.flatMap((_, layer) => {
|
|
6771
|
-
const ids = [
|
|
6772
|
-
return ids.length && ids.every((
|
|
6979
|
+
const ids = flowLayerIds[layer];
|
|
6980
|
+
return ids.length && ids.every((id) => {
|
|
6773
6981
|
const node = nodeById.get(id);
|
|
6774
6982
|
const side = node && externalPortDummyOf(node)?.side;
|
|
6775
6983
|
return horizontal$1 ? side === "WEST" || side === "EAST" : side === "NORTH" || side === "SOUTH";
|
|
6776
6984
|
}) ? [layer] : [];
|
|
6777
6985
|
}));
|
|
6986
|
+
const shiftedIds = placedIdsByFlowLayer();
|
|
6778
6987
|
for (const [layerNo, bounds] of flowLayers.entries()) {
|
|
6779
6988
|
const shift = nextStart - bounds.start;
|
|
6780
|
-
for (const
|
|
6781
|
-
|
|
6989
|
+
for (const id of shiftedIds[layerNo]) {
|
|
6990
|
+
const rect = placement.rectByNodeId.get(id);
|
|
6782
6991
|
mutableRects.set(id, horizontal$1 ? {
|
|
6783
6992
|
...rect,
|
|
6784
6993
|
x: rect.x + shift
|
|
@@ -6992,10 +7201,11 @@ function routeEdges(style) {
|
|
|
6992
7201
|
});
|
|
6993
7202
|
}
|
|
6994
7203
|
let nextStart = flowLayers[0]?.start ?? 0;
|
|
7204
|
+
const shiftedIds = placedIdsByFlowLayer();
|
|
6995
7205
|
for (const [layerNo, bounds] of flowLayers.entries()) {
|
|
6996
7206
|
const shift = nextStart - bounds.start;
|
|
6997
|
-
for (const
|
|
6998
|
-
|
|
7207
|
+
for (const id of shiftedIds[layerNo]) {
|
|
7208
|
+
const rect = placement.rectByNodeId.get(id);
|
|
6999
7209
|
mutableRects.set(id, horizontal$1 ? {
|
|
7000
7210
|
...rect,
|
|
7001
7211
|
x: rect.x + shift
|
|
@@ -7868,7 +8078,7 @@ function normalizePlacementForPortExtents(input, placement, order, orientation)
|
|
|
7868
8078
|
let trailingBoundary = Number.NEGATIVE_INFINITY;
|
|
7869
8079
|
let leadingBoundary = Number.POSITIVE_INFINITY;
|
|
7870
8080
|
for (const [ids, trailing] of [[layer, true], [nextLayer, false]]) for (const id of ids) {
|
|
7871
|
-
const node = input.graph.nodes
|
|
8081
|
+
const node = findNode(input.graph.nodes, id);
|
|
7872
8082
|
const rect = placement.rectByNodeId.get(id);
|
|
7873
8083
|
if (!node || !rect) continue;
|
|
7874
8084
|
const bodyStart = horizontal$1 ? rect.x : rect.y;
|
|
@@ -9654,6 +9864,10 @@ function crossing(s, t) {
|
|
|
9654
9864
|
const v = h === s ? t : s;
|
|
9655
9865
|
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
9866
|
}
|
|
9867
|
+
const onBorder = (p, r) => {
|
|
9868
|
+
const inX = p.x >= r.x - EPS && p.x <= r.x + r.width + EPS;
|
|
9869
|
+
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);
|
|
9870
|
+
};
|
|
9657
9871
|
const segmentsOf = (points) => points.slice(1).flatMap((b, i) => {
|
|
9658
9872
|
const s = {
|
|
9659
9873
|
a: points[i],
|
|
@@ -9686,7 +9900,34 @@ function separateOpposingTracks(scene) {
|
|
|
9686
9900
|
if (!rects) terminalCache.set(key, rects = terminals(a.route, b.route));
|
|
9687
9901
|
return rects;
|
|
9688
9902
|
};
|
|
9903
|
+
const boxes = /* @__PURE__ */ new WeakMap();
|
|
9904
|
+
const boxOf = (segments) => {
|
|
9905
|
+
let box = boxes.get(segments);
|
|
9906
|
+
if (!box) {
|
|
9907
|
+
box = [
|
|
9908
|
+
Infinity,
|
|
9909
|
+
Infinity,
|
|
9910
|
+
-Infinity,
|
|
9911
|
+
-Infinity
|
|
9912
|
+
];
|
|
9913
|
+
for (const { a, b } of segments) {
|
|
9914
|
+
box[0] = Math.min(box[0], a.x, b.x);
|
|
9915
|
+
box[1] = Math.min(box[1], a.y, b.y);
|
|
9916
|
+
box[2] = Math.max(box[2], a.x, b.x);
|
|
9917
|
+
box[3] = Math.max(box[3], a.y, b.y);
|
|
9918
|
+
}
|
|
9919
|
+
boxes.set(segments, box);
|
|
9920
|
+
}
|
|
9921
|
+
return box;
|
|
9922
|
+
};
|
|
9923
|
+
const none = {
|
|
9924
|
+
opposing: 0,
|
|
9925
|
+
overlap: 0,
|
|
9926
|
+
crossings: 0
|
|
9927
|
+
};
|
|
9689
9928
|
const pair = (mine, self, other) => {
|
|
9929
|
+
const [ax0, ay0, ax1, ay1] = boxOf(mine), [bx0, by0, bx1, by1] = boxOf(other.measured);
|
|
9930
|
+
if (ax1 < bx0 - EPS || bx1 < ax0 - EPS || ay1 < by0 - EPS || by1 < ay0 - EPS) return none;
|
|
9690
9931
|
const rects = terminalsOf(self, other);
|
|
9691
9932
|
const as = rects.length ? clip(mine, rects, true) : mine, bs = rects.length ? clip(other.measured, rects, true) : other.measured;
|
|
9692
9933
|
let opposing = 0, overlap = 0, crossings$1 = 0;
|
|
@@ -9899,8 +10140,8 @@ function separateOpposingTracks(scene) {
|
|
|
9899
10140
|
y: p.y
|
|
9900
10141
|
};
|
|
9901
10142
|
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;
|
|
10143
|
+
const before = raw[k - 1], after$1 = raw[k + 1];
|
|
10144
|
+
if (h ? !vertical(before) || !vertical(after$1) : !horizontal(before) || !horizontal(after$1)) continue;
|
|
9904
10145
|
for (const delta of offsets) {
|
|
9905
10146
|
const points = state.points.map((p, i) => i === k || i === k + 1 ? shift(p, delta) : p);
|
|
9906
10147
|
const next = [
|
|
@@ -9911,11 +10152,11 @@ function separateOpposingTracks(scene) {
|
|
|
9911
10152
|
a: points[i],
|
|
9912
10153
|
b: points[i + 1]
|
|
9913
10154
|
}));
|
|
9914
|
-
if (!keeps(before, next[0]) || !keeps(after, next[2])) continue;
|
|
10155
|
+
if (!keeps(before, next[0]) || !keeps(after$1, next[2])) continue;
|
|
9915
10156
|
consider(points, [
|
|
9916
10157
|
before,
|
|
9917
10158
|
segment,
|
|
9918
|
-
after
|
|
10159
|
+
after$1
|
|
9919
10160
|
], next, next[1], 0, delta);
|
|
9920
10161
|
}
|
|
9921
10162
|
continue;
|
|
@@ -9938,6 +10179,32 @@ function separateOpposingTracks(scene) {
|
|
|
9938
10179
|
a: port,
|
|
9939
10180
|
b: corner
|
|
9940
10181
|
}], [inflate(rect, TERMINAL_MARGIN)], true).reduce((sum, part) => sum + size(part), 0) : along;
|
|
10182
|
+
const after = {
|
|
10183
|
+
a: corner,
|
|
10184
|
+
b: beyond
|
|
10185
|
+
};
|
|
10186
|
+
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);
|
|
10187
|
+
if (state.route.ends.length === 2 && state.route.ports?.[reversed ? 1 : 0] === void 0 && rect && onBorder(port, rect) && (h ? vertical(after) : horizontal(after))) {
|
|
10188
|
+
const lo = h ? rect.y : rect.x, hi = lo + (h ? rect.height : rect.width);
|
|
10189
|
+
for (const delta of offsets) {
|
|
10190
|
+
const moved = shift(port, delta);
|
|
10191
|
+
if ((h ? moved.y : moved.x) < lo - EPS || (h ? moved.y : moved.x) > hi + EPS) continue;
|
|
10192
|
+
const next = [{
|
|
10193
|
+
a: moved,
|
|
10194
|
+
b: shift(corner, delta)
|
|
10195
|
+
}, {
|
|
10196
|
+
a: shift(corner, delta),
|
|
10197
|
+
b: beyond
|
|
10198
|
+
}];
|
|
10199
|
+
if (!keeps(after, next[1]) || against(next[1]) > against(after) + EPS) continue;
|
|
10200
|
+
const replaced = [
|
|
10201
|
+
moved,
|
|
10202
|
+
shift(corner, delta),
|
|
10203
|
+
...path.slice(2)
|
|
10204
|
+
];
|
|
10205
|
+
consider(reversed ? replaced.reverse() : replaced, [segment, after], next, next[0], 0, delta);
|
|
10206
|
+
}
|
|
10207
|
+
}
|
|
9941
10208
|
for (const stub of new Set([along - clearance, spacing])) for (const delta of offsets) {
|
|
9942
10209
|
if (stub < 1 || along - stub < spacing) continue;
|
|
9943
10210
|
const q = {
|
|
@@ -9995,14 +10262,34 @@ function separateOpposingTracks(scene) {
|
|
|
9995
10262
|
* half a spacing either side of the port's centre.
|
|
9996
10263
|
*/
|
|
9997
10264
|
function splitSharedPorts(tolerant) {
|
|
9998
|
-
const ports = scene.ports;
|
|
9999
|
-
if (!ports?.size) return;
|
|
10000
10265
|
const groups = /* @__PURE__ */ new Map();
|
|
10266
|
+
const implicit = /* @__PURE__ */ new Map();
|
|
10001
10267
|
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;
|
|
10268
|
+
if (state.points.length < 2) continue;
|
|
10005
10269
|
const [p, q] = end ? [...state.points].reverse() : state.points;
|
|
10270
|
+
let id = state.route.ports?.[end];
|
|
10271
|
+
let port = id === void 0 ? void 0 : scene.ports?.get(id);
|
|
10272
|
+
const node = state.route.ends.length === 2 ? state.route.ends[end] : void 0;
|
|
10273
|
+
const rect = node === void 0 ? void 0 : nodes.get(node);
|
|
10274
|
+
if (id === void 0 && rect && onBorder(p, rect)) {
|
|
10275
|
+
const points = implicit.get(node) ?? [];
|
|
10276
|
+
implicit.set(node, points);
|
|
10277
|
+
let at = points.find((o) => Math.abs(o.x - p.x) <= EPS && Math.abs(o.y - p.y) <= EPS);
|
|
10278
|
+
if (!at) points.push(at = {
|
|
10279
|
+
x: p.x,
|
|
10280
|
+
y: p.y
|
|
10281
|
+
});
|
|
10282
|
+
id = `${node}\0${points.indexOf(at)}`;
|
|
10283
|
+
port = {
|
|
10284
|
+
node,
|
|
10285
|
+
rect: {
|
|
10286
|
+
...at,
|
|
10287
|
+
width: 0,
|
|
10288
|
+
height: 0
|
|
10289
|
+
}
|
|
10290
|
+
};
|
|
10291
|
+
}
|
|
10292
|
+
if (!port) continue;
|
|
10006
10293
|
const s = {
|
|
10007
10294
|
a: p,
|
|
10008
10295
|
b: q
|
|
@@ -10024,7 +10311,13 @@ function separateOpposingTracks(scene) {
|
|
|
10024
10311
|
end
|
|
10025
10312
|
});
|
|
10026
10313
|
}
|
|
10027
|
-
for (const
|
|
10314
|
+
for (const group of groups.values()) {
|
|
10315
|
+
const { port, normal } = group;
|
|
10316
|
+
const ends = group.ends.filter(({ state, end }) => {
|
|
10317
|
+
if (state.route.ports?.[end] !== void 0) return true;
|
|
10318
|
+
const p = end ? state.points.at(-1) : state.points[0];
|
|
10319
|
+
return Math.abs(p.x - port.rect.x) <= EPS && Math.abs(p.y - port.rect.y) <= EPS;
|
|
10320
|
+
});
|
|
10028
10321
|
const incoming = ends.filter((e) => e.end === 1), outgoing = ends.filter((e) => e.end === 0);
|
|
10029
10322
|
if (!incoming.length || !outgoing.length) continue;
|
|
10030
10323
|
const stub = ({ state, end }) => {
|
|
@@ -10043,9 +10336,25 @@ function separateOpposingTracks(scene) {
|
|
|
10043
10336
|
const sideLo = node[lateral], sideHi = node[lateral] + (lateral === "x" ? node.width : node.height);
|
|
10044
10337
|
const wide = extent$2 >= spacing - EPS;
|
|
10045
10338
|
const half = spacing / 2;
|
|
10046
|
-
const
|
|
10339
|
+
const slid = (state, points, end, offset) => {
|
|
10340
|
+
if (state.route.ports?.some((id) => id !== void 0)) return void 0;
|
|
10341
|
+
if (Math.abs(points[0][across] - points[1][across]) <= EPS) return void 0;
|
|
10342
|
+
const far = nodes.get(state.route.ends[1 - end] ?? "");
|
|
10343
|
+
const moved = points.map((p) => ({
|
|
10344
|
+
...p,
|
|
10345
|
+
[lateral]: p[lateral] + offset
|
|
10346
|
+
}));
|
|
10347
|
+
const within = (value$1, rect) => {
|
|
10348
|
+
const lo = rect[lateral];
|
|
10349
|
+
return value$1 >= lo - EPS && value$1 <= lo + (lateral === "x" ? rect.width : rect.height) + EPS;
|
|
10350
|
+
};
|
|
10351
|
+
const value = moved[0][lateral];
|
|
10352
|
+
return within(value, node) && far && within(value, far) ? moved : void 0;
|
|
10353
|
+
};
|
|
10354
|
+
const shifted = (state, points, end, offset) => {
|
|
10047
10355
|
if (Math.abs(offset) <= EPS) return points;
|
|
10048
10356
|
const path = end ? [...points].reverse() : [...points];
|
|
10357
|
+
if (path.length === 2) return slid(state, points, end, offset);
|
|
10049
10358
|
if (path.length < 3) return void 0;
|
|
10050
10359
|
const [p, q, r] = path;
|
|
10051
10360
|
const along = r[lateral] - q[lateral];
|
|
@@ -10109,7 +10418,7 @@ function separateOpposingTracks(scene) {
|
|
|
10109
10418
|
const next = /* @__PURE__ */ new Map();
|
|
10110
10419
|
let feasible = true;
|
|
10111
10420
|
for (const e of ends) {
|
|
10112
|
-
const points = shifted(next.get(e.state) ?? e.state.points, e.end, offsetOf(e));
|
|
10421
|
+
const points = shifted(e.state, next.get(e.state) ?? e.state.points, e.end, offsetOf(e));
|
|
10113
10422
|
if (!points) {
|
|
10114
10423
|
feasible = false;
|
|
10115
10424
|
break;
|
|
@@ -14589,10 +14898,16 @@ function buildNeighbors(input, order) {
|
|
|
14589
14898
|
return (horizontal$1 ? port?.height : port?.width) ?? 0;
|
|
14590
14899
|
});
|
|
14591
14900
|
};
|
|
14901
|
+
const loopsByNodeId = /* @__PURE__ */ new Map();
|
|
14902
|
+
for (const edge of input.graph.edges) if (edge.sourceId === edge.targetId) {
|
|
14903
|
+
const loops = loopsByNodeId.get(edge.sourceId);
|
|
14904
|
+
if (loops) loops.push(edge);
|
|
14905
|
+
else loopsByNodeId.set(edge.sourceId, [edge]);
|
|
14906
|
+
}
|
|
14592
14907
|
const loopTerminals = (id, side) => {
|
|
14593
14908
|
const node = nodeById.get(id);
|
|
14594
14909
|
if (!node) return 0;
|
|
14595
|
-
return
|
|
14910
|
+
return (loopsByNodeId.get(id) ?? []).flatMap((edge) => [edge.sourcePort, edge.targetPort]).filter((name) => {
|
|
14596
14911
|
const port = node.ports?.find((candidate) => candidate.name === name);
|
|
14597
14912
|
return port !== void 0 && input.portSettings?.(port, node)?.["port.side"] === side.toUpperCase() && usesSharedSideSlot(input, node, name);
|
|
14598
14913
|
}).length;
|
|
@@ -14703,8 +15018,8 @@ function alignBlocks(input, order, bal, neighbors, markedEdges) {
|
|
|
14703
15018
|
const adjacent = (bal.hdir === "LEFT" ? neighbors.right : neighbors.left).get(id) ?? [];
|
|
14704
15019
|
const low = Math.floor((adjacent.length + 1) / 2) - 1;
|
|
14705
15020
|
const high = Math.ceil((adjacent.length + 1) / 2) - 1;
|
|
14706
|
-
|
|
14707
|
-
|
|
15021
|
+
for (let i = 0; i < high - low + 1; i++) {
|
|
15022
|
+
const median = bal.vdir === "UP" ? high - i : low + i;
|
|
14708
15023
|
if (bal.align.get(id) !== id) break;
|
|
14709
15024
|
const neighbor = adjacent[median];
|
|
14710
15025
|
if (neighbor === void 0) continue;
|
|
@@ -18533,11 +18848,14 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18533
18848
|
settings: options.settings ?? {},
|
|
18534
18849
|
...options.nodeSettings === void 0 ? {} : { nodeSettings: options.nodeSettings },
|
|
18535
18850
|
...options.edgeSettings === void 0 ? {} : { edgeSettings: options.edgeSettings },
|
|
18536
|
-
portSettings: (port, node) =>
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18851
|
+
portSettings: (port, node) => {
|
|
18852
|
+
const key = `${node.id}\0${port.name}`;
|
|
18853
|
+
return {
|
|
18854
|
+
...options.portSettings?.(port, node),
|
|
18855
|
+
...parallelPortIndexByKey.has(key) ? { "port.index": parallelPortIndexByKey.get(key) } : {},
|
|
18856
|
+
...switchedSideByPort.has(key) ? { "port.side": switchedSideByPort.get(key) } : {}
|
|
18857
|
+
};
|
|
18858
|
+
}
|
|
18541
18859
|
};
|
|
18542
18860
|
const measure = (id, run) => {
|
|
18543
18861
|
context?.throwIfAborted();
|
|
@@ -18683,8 +19001,10 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18683
19001
|
const northEdges = /* @__PURE__ */ new Set(), southEdges = /* @__PURE__ */ new Set();
|
|
18684
19002
|
const upper = direction === "right" || direction === "left" ? "NORTH" : "WEST";
|
|
18685
19003
|
const lower = direction === "right" || direction === "left" ? "SOUTH" : "EAST";
|
|
19004
|
+
const endpointNodes = /* @__PURE__ */ new Map();
|
|
19005
|
+
for (const node of expanded.input.graph.nodes) if (!endpointNodes.has(node.id)) endpointNodes.set(node.id, node);
|
|
18686
19006
|
for (const edge of expanded.input.graph.edges) for (const [id, name] of [[edge.sourceId, edge.sourcePort], [edge.targetId, edge.targetPort]]) {
|
|
18687
|
-
const node =
|
|
19007
|
+
const node = endpointNodes.get(id), port = node?.ports?.find((p) => p.name === name);
|
|
18688
19008
|
const side = node && port ? expanded.input.portSettings?.(port, node)?.["port.side"] : void 0;
|
|
18689
19009
|
if (side === upper) northEdges.add(id);
|
|
18690
19010
|
if (side === lower) southEdges.add(id);
|
|
@@ -18811,10 +19131,6 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18811
19131
|
switchedSideByPort.set(`${origin.node.id}\0${origin.port.name}`, opposite$2);
|
|
18812
19132
|
}
|
|
18813
19133
|
}
|
|
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
19134
|
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
19135
|
if ((options.settings?.["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING") if (unzippingFanIn) order = applyLayerUnzipping(expanded.input, order);
|
|
18820
19136
|
else {
|
|
@@ -19717,4 +20033,4 @@ const elkjs0111LayeredAlgorithm = {
|
|
|
19717
20033
|
};
|
|
19718
20034
|
|
|
19719
20035
|
//#endregion
|
|
19720
|
-
export {
|
|
20036
|
+
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 };
|