@statelyai/layout 0.4.1 → 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-BBfKN4Dw.mjs → elkjs-CSi7oB5J.mjs} +18 -8
- package/dist/{elkjs-DNfXDPup.cjs → elkjs-CnBvdyBu.cjs} +18 -8
- 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-5RxQhryb.cjs → layered-BJu8A21s.cjs} +231 -121
- package/dist/{layered-DSesbctm.mjs → layered-BsAdm_u8.mjs} +231 -121
- package/dist/{layout-engine-CLFySz3R.cjs → layout-engine-BFP1TZbF.cjs} +1 -1
- package/dist/{layout-engine-GpBIX6ng.mjs → layout-engine-DnlmGxHL.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +23 -5
- package/src/layered/bk-node-placement.ts +10 -7
- package/src/layered/crossing-counter.ts +10 -4
- package/src/layered/crossing-graph.ts +2 -2
- package/src/layered/greedy-switch-decider.ts +44 -17
- package/src/layered/index.ts +15 -10
- package/src/layered/loop-envelopes.ts +10 -4
- package/src/layered/node-lookup.ts +29 -0
- 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 +176 -96
|
@@ -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;
|
|
@@ -600,7 +611,7 @@ var IndexTree = class {
|
|
|
600
611
|
};
|
|
601
612
|
/** Score a canonical candidate without changing its node or port orders. */
|
|
602
613
|
function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
603
|
-
|
|
614
|
+
let nodes;
|
|
604
615
|
const layerByPort = /* @__PURE__ */ new Map();
|
|
605
616
|
const incoming = /* @__PURE__ */ new Map(), outgoing = /* @__PURE__ */ new Map();
|
|
606
617
|
for (const [layer, members] of graph.layers.entries()) for (const node of members) for (const port of node.ports) {
|
|
@@ -612,7 +623,6 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
612
623
|
outgoing.get(edge.source).push(edge.target);
|
|
613
624
|
incoming.get(edge.target).push(edge.source);
|
|
614
625
|
}
|
|
615
|
-
const connected = (id) => [...incoming.get(id), ...outgoing.get(id)];
|
|
616
626
|
const degree = (id) => incoming.get(id).length + outgoing.get(id).length;
|
|
617
627
|
const face = (layer, side, topDown = true) => (topDown ? layer : [...layer].reverse()).flatMap((node) => {
|
|
618
628
|
const ports = node.ports.filter((port) => port.side === side);
|
|
@@ -625,7 +635,9 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
625
635
|
for (const [position, port] of ports.entries()) {
|
|
626
636
|
tree.remove(position);
|
|
627
637
|
const pending = [];
|
|
628
|
-
|
|
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];
|
|
629
641
|
if (inLayerOnly && layerByPort.get(peer) !== layerByPort.get(port.id)) {
|
|
630
642
|
crossings$1 += tree.size;
|
|
631
643
|
continue;
|
|
@@ -669,7 +681,7 @@ function countAllCrossings(graph, boundaryMode = "hyperedges") {
|
|
|
669
681
|
for (const [position, port] of ports.entries()) {
|
|
670
682
|
tree.remove(position);
|
|
671
683
|
const node = ownerByPort.get(port.id);
|
|
672
|
-
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) => ({
|
|
673
685
|
id: peer.id,
|
|
674
686
|
degree: degree(peer.id)
|
|
675
687
|
})) : node.type === "LONG_EDGE" ? node.ports.filter((peer) => peer.id !== port.id).slice(0, 1).map((peer) => ({
|
|
@@ -1102,7 +1114,7 @@ function buildCrossingGraph(plan, layers, incoming, outgoing) {
|
|
|
1102
1114
|
for (const member of ports.get(operation.northSouthDummy)?.values() ?? []) member.port.origin = operation.id;
|
|
1103
1115
|
}
|
|
1104
1116
|
}
|
|
1105
|
-
const sideRank = {
|
|
1117
|
+
const sideRank$1 = {
|
|
1106
1118
|
NORTH: 0,
|
|
1107
1119
|
EAST: 1,
|
|
1108
1120
|
SOUTH: 2,
|
|
@@ -1115,7 +1127,7 @@ function buildCrossingGraph(plan, layers, incoming, outgoing) {
|
|
|
1115
1127
|
id,
|
|
1116
1128
|
type: kind?.type ?? kindOf(id, plan.units),
|
|
1117
1129
|
unit: kind ? kind.unit : plan.units?.units.get(id),
|
|
1118
|
-
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)
|
|
1119
1131
|
};
|
|
1120
1132
|
})),
|
|
1121
1133
|
edges: plan.edges.map((edge) => ({
|
|
@@ -1135,7 +1147,8 @@ function splitMixedCrossingPorts(graph, reversed) {
|
|
|
1135
1147
|
const directions = /* @__PURE__ */ new Map();
|
|
1136
1148
|
graph.edges.forEach((edge, index) => {
|
|
1137
1149
|
const bit = reversed[index] ? 2 : 1;
|
|
1138
|
-
|
|
1150
|
+
directions.set(edge.source, (directions.get(edge.source) ?? 0) | bit);
|
|
1151
|
+
directions.set(edge.target, (directions.get(edge.target) ?? 0) | bit);
|
|
1139
1152
|
});
|
|
1140
1153
|
const mixed = /* @__PURE__ */ new Set();
|
|
1141
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);
|
|
@@ -1575,20 +1588,46 @@ function selfLoopTracks(loops) {
|
|
|
1575
1588
|
}));
|
|
1576
1589
|
}
|
|
1577
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
|
+
|
|
1578
1611
|
//#endregion
|
|
1579
1612
|
//#region src/layered/loop-envelopes.ts
|
|
1580
1613
|
/** Unconstrained implicit loop ports are restored on loop faces after the sweep. */
|
|
1581
1614
|
function hasMovableLoopPorts(input, edge) {
|
|
1582
1615
|
if (edge.sourceId !== edge.targetId || edge.sourcePort !== void 0 || edge.targetPort !== void 0) return false;
|
|
1583
|
-
const node = input.graph.nodes
|
|
1616
|
+
const node = findNode(input.graph.nodes, edge.sourceId);
|
|
1584
1617
|
const constraints = node && input.nodeSettings?.(node)?.portConstraints;
|
|
1585
1618
|
return constraints === void 0 || constraints === "FREE" || constraints === "UNDEFINED";
|
|
1586
1619
|
}
|
|
1587
1620
|
function loopEnvelopes(input) {
|
|
1588
1621
|
const envelopes = /* @__PURE__ */ new Map();
|
|
1589
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
|
+
}
|
|
1590
1629
|
for (const node of input.graph.nodes) {
|
|
1591
|
-
const allLoops =
|
|
1630
|
+
const allLoops = loopsByNodeId.get(node.id)?.slice() ?? [];
|
|
1592
1631
|
const constraints = input.nodeSettings?.(node)?.portConstraints;
|
|
1593
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"))) {
|
|
1594
1633
|
const size$1 = input.sizes.get(node.id) ?? {
|
|
@@ -1701,7 +1740,7 @@ function preparedLoopEnvelopes(input) {
|
|
|
1701
1740
|
//#region src/layered/port-edge-order.ts
|
|
1702
1741
|
/** ELK traverses connected edges through the node's port list, preserving each port's edge order. */
|
|
1703
1742
|
function connectedEdgesInPortOrder(input, nodeId) {
|
|
1704
|
-
const node = input.graph.nodes
|
|
1743
|
+
const node = findNode(input.graph.nodes, nodeId);
|
|
1705
1744
|
const portRank = new Map(node?.ports?.map((port, index) => [port.name, index]));
|
|
1706
1745
|
const explicitCount = portRank.size;
|
|
1707
1746
|
return input.graph.edges.map((edge, index) => ({
|
|
@@ -2590,8 +2629,8 @@ function nodeNodeSpacing(input, firstId, secondId) {
|
|
|
2590
2629
|
const secondBreakingPoint = secondId.startsWith("__layout_breaking:");
|
|
2591
2630
|
const firstLabel = firstId.startsWith("__layout_dummy:label:");
|
|
2592
2631
|
const secondLabel = secondId.startsWith("__layout_dummy:label:");
|
|
2593
|
-
const firstNode = input.graph.nodes
|
|
2594
|
-
const secondNode = input.graph.nodes
|
|
2632
|
+
const firstNode = findNode(input.graph.nodes, firstId);
|
|
2633
|
+
const secondNode = findNode(input.graph.nodes, secondId);
|
|
2595
2634
|
const firstExternal = firstNode !== void 0 && externalPortDummyOf(firstNode) !== void 0;
|
|
2596
2635
|
const secondExternal = secondNode !== void 0 && externalPortDummyOf(secondNode) !== void 0;
|
|
2597
2636
|
const firstDummy = firstId.startsWith("__layout_dummy:") || firstBreakingPoint || firstExternal;
|
|
@@ -2599,7 +2638,7 @@ function nodeNodeSpacing(input, firstId, secondId) {
|
|
|
2599
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";
|
|
2600
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;
|
|
2601
2640
|
for (const id of [firstId, secondId]) {
|
|
2602
|
-
const node = input.graph.nodes
|
|
2641
|
+
const node = findNode(input.graph.nodes, id);
|
|
2603
2642
|
if (!node) continue;
|
|
2604
2643
|
const individual = input.nodeSettings?.(node)?.["spacing.individual"];
|
|
2605
2644
|
if (individual && typeof individual === "object") {
|
|
@@ -3410,16 +3449,35 @@ function sidePorts(node, side) {
|
|
|
3410
3449
|
const ports = node.ports.filter((port) => port.side === side);
|
|
3411
3450
|
return side === "WEST" || side === "SOUTH" ? ports.reverse() : ports;
|
|
3412
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
|
+
}
|
|
3413
3471
|
/**
|
|
3414
3472
|
* Decides greedy switches in one free layer from local two-node crossing
|
|
3415
3473
|
* counts, exactly as ELK does; these estimates can differ from a global recount.
|
|
3416
3474
|
*/
|
|
3417
3475
|
var GreedySwitchDecider = class {
|
|
3418
3476
|
#free;
|
|
3419
|
-
#incoming
|
|
3420
|
-
#outgoing
|
|
3421
|
-
#layerByPort
|
|
3422
|
-
#ownerByPort
|
|
3477
|
+
#incoming;
|
|
3478
|
+
#outgoing;
|
|
3479
|
+
#layerByPort;
|
|
3480
|
+
#ownerByPort;
|
|
3423
3481
|
#neighbourPositions = /* @__PURE__ */ new Map();
|
|
3424
3482
|
#adjacencies = /* @__PURE__ */ new Map();
|
|
3425
3483
|
#matrix = /* @__PURE__ */ new Map();
|
|
@@ -3427,19 +3485,13 @@ var GreedySwitchDecider = class {
|
|
|
3427
3485
|
#cardinality = /* @__PURE__ */ new Map();
|
|
3428
3486
|
#inLayerSize = /* @__PURE__ */ new Map();
|
|
3429
3487
|
#oneSided;
|
|
3430
|
-
constructor(graph, freeLayerIndex, oneSided) {
|
|
3488
|
+
constructor(graph, freeLayerIndex, oneSided, topology = greedySwitchTopology(graph)) {
|
|
3431
3489
|
this.#free = graph.layers[freeLayerIndex] ?? [];
|
|
3432
3490
|
this.#oneSided = oneSided;
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
this.#outgoing.set(port.id, []);
|
|
3438
|
-
}
|
|
3439
|
-
for (const edge of graph.edges) {
|
|
3440
|
-
this.#outgoing.get(edge.source)?.push(edge.target);
|
|
3441
|
-
this.#incoming.get(edge.target)?.push(edge.source);
|
|
3442
|
-
}
|
|
3491
|
+
this.#incoming = topology.incoming;
|
|
3492
|
+
this.#outgoing = topology.outgoing;
|
|
3493
|
+
this.#layerByPort = topology.layerByPort;
|
|
3494
|
+
this.#ownerByPort = topology.ownerByPort;
|
|
3443
3495
|
for (const [layerIndex, side] of [[freeLayerIndex - 1, "EAST"], [freeLayerIndex + 1, "WEST"]]) {
|
|
3444
3496
|
let position = 0;
|
|
3445
3497
|
for (const node of graph.layers[layerIndex] ?? []) for (const port of sidePorts(node, side)) this.#neighbourPositions.set(port.id, position++);
|
|
@@ -4311,6 +4363,7 @@ function applyGreedySwitch(input, orientation, order) {
|
|
|
4311
4363
|
if (oneSidedBoundary !== void 0) return countBoundary(oneSidedBoundary);
|
|
4312
4364
|
return (layerIndex > 0 ? countBoundary(layerIndex - 1) : 0) + (layerIndex + 1 < layers.length ? countBoundary(layerIndex) : 0);
|
|
4313
4365
|
};
|
|
4366
|
+
let greedyTopology;
|
|
4314
4367
|
const sweep = (forward$1) => {
|
|
4315
4368
|
let changed = false;
|
|
4316
4369
|
const indices = forward$1 ? Array.from({ length: layers.length }, (_, index) => index) : Array.from({ length: layers.length }, (_, index) => layers.length - index - 1);
|
|
@@ -4320,7 +4373,7 @@ function applyGreedySwitch(input, orientation, order) {
|
|
|
4320
4373
|
if (greedyGraph) {
|
|
4321
4374
|
const units = getCrossingUnits(input);
|
|
4322
4375
|
greedyGraph.layers = layers.map((ids) => ids.map((id) => greedyNodes.get(id)));
|
|
4323
|
-
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));
|
|
4324
4377
|
do {
|
|
4325
4378
|
improved$1 = false;
|
|
4326
4379
|
for (let index = 0; index + 1 < layer.length; index++) {
|
|
@@ -4913,7 +4966,9 @@ function sortLayerByAdjacentPosition(layer, ranksByNodeId, statistic, random, pr
|
|
|
4913
4966
|
if (units && !forcedCompare) layer.splice(0, layer.length, ...resolveCrossingConstraints(layer, adjacentPosition, units));
|
|
4914
4967
|
}
|
|
4915
4968
|
function clonePortOrders(orders) {
|
|
4916
|
-
|
|
4969
|
+
const clone = /* @__PURE__ */ new Map();
|
|
4970
|
+
if (orders) for (const [id, edges] of orders) clone.set(id, edges.slice());
|
|
4971
|
+
return clone;
|
|
4917
4972
|
}
|
|
4918
4973
|
function minimizeCrossingsWithLayerSweep(statistic, sweeps = 7) {
|
|
4919
4974
|
return (input, orientation, assignment) => createLayerSweepSession(input, orientation, assignment, statistic, sweeps).minimize();
|
|
@@ -5006,14 +5061,16 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5006
5061
|
const buildCrossingGraph$1 = createCrossingGraphBuilder(input);
|
|
5007
5062
|
const crossingModel = input.settings[CROSSING_MODEL] ?? {};
|
|
5008
5063
|
const mixedPorts = crossingModel.mixedPorts !== false, probe = crossingModel.probe;
|
|
5064
|
+
const physicalSlot = /* @__PURE__ */ new Map();
|
|
5009
5065
|
const countCrossings = (candidateLayers) => {
|
|
5010
5066
|
if (exactPortSweep) {
|
|
5011
5067
|
const graph = buildCrossingGraph$1(orientation, candidateLayers, inputPortOrder, outputPortOrder);
|
|
5012
5068
|
for (const layer of graph.layers) for (const node of layer) {
|
|
5013
5069
|
const physical = node.ports.length > 1 ? canonicalNodes.get(node.id)?.ports : void 0;
|
|
5014
5070
|
if (physical?.length !== node.ports.length) continue;
|
|
5015
|
-
|
|
5016
|
-
|
|
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));
|
|
5017
5074
|
}
|
|
5018
5075
|
const split = mixedPorts ? splitMixedCrossingPorts(graph, canonicalReversed) : graph;
|
|
5019
5076
|
const score = countAllCrossings(split, crossingModel.separateSharedTracks ? "separated" : "hyperedges");
|
|
@@ -5115,6 +5172,17 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5115
5172
|
}
|
|
5116
5173
|
for (const id of freeLayer) {
|
|
5117
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
|
+
}
|
|
5118
5186
|
const groups = /* @__PURE__ */ new Map();
|
|
5119
5187
|
for (const edgeId of order) {
|
|
5120
5188
|
const edge = edgeById.get(edgeId);
|
|
@@ -5123,17 +5191,11 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5123
5191
|
if (group) group.push(edgeId);
|
|
5124
5192
|
else groups.set(key, [edgeId]);
|
|
5125
5193
|
}
|
|
5194
|
+
const listed = units?.edgeListRanks?.[forward ? "incoming" : "outgoing"].get(id);
|
|
5195
|
+
const rank = (edgeId) => listed?.get(listedEdgeId(edgeId));
|
|
5126
5196
|
for (const group of groups.values()) {
|
|
5127
|
-
|
|
5128
|
-
const
|
|
5129
|
-
group.sort((a, b) => (rank(a) !== void 0 && rank(b) !== void 0 ? rank(a) - rank(b) : 0) || edgeModelOrder.get(a) - edgeModelOrder.get(b));
|
|
5130
|
-
for (const edgeId of group) {
|
|
5131
|
-
const [source, target] = getOrientedEndpoints(edgeById.get(edgeId), orientation);
|
|
5132
|
-
const neighbor = forward ? source : target;
|
|
5133
|
-
const rank$1 = edgeRanks.get(edgeId);
|
|
5134
|
-
if (assignment.layerByNodeId.get(neighbor) === assignment.layerByNodeId.get(id)) visits.get(id).push(neighbor);
|
|
5135
|
-
else if (rank$1 !== void 0) visits.get(id).push(rank$1);
|
|
5136
|
-
}
|
|
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);
|
|
5137
5199
|
}
|
|
5138
5200
|
}
|
|
5139
5201
|
return {
|
|
@@ -5181,6 +5243,7 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5181
5243
|
hierarchical: hierarchicalNodes
|
|
5182
5244
|
});
|
|
5183
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;
|
|
5184
5247
|
const ordinal = new Map(node.ports.map((port, index$1) => [port.id, index$1]));
|
|
5185
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"]));
|
|
5186
5249
|
}
|
|
@@ -5398,12 +5461,16 @@ function createLayerSweepSession(input, orientation, assignment, statistic = "me
|
|
|
5398
5461
|
shuffleFirstLayer,
|
|
5399
5462
|
sweep,
|
|
5400
5463
|
countCrossings: () => countCrossings(working),
|
|
5401
|
-
snapshot: () =>
|
|
5402
|
-
physicalPortOrderByNodeId
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
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
|
+
},
|
|
5407
5474
|
restore,
|
|
5408
5475
|
finish,
|
|
5409
5476
|
currentLayers: () => working,
|
|
@@ -5813,7 +5880,7 @@ function sumWithSpacing(ids, input, axis) {
|
|
|
5813
5880
|
}, 0);
|
|
5814
5881
|
}
|
|
5815
5882
|
function nodeFlowOffset(input, id, layerFlowSize, nodeFlowSize, layerByNodeId, margins) {
|
|
5816
|
-
const node = input.graph.nodes
|
|
5883
|
+
const node = findNode(input.graph.nodes, id);
|
|
5817
5884
|
const alignment = node ? input.nodeSettings?.(node)?.alignment ?? input.settings.alignment : input.settings.alignment;
|
|
5818
5885
|
let ratio;
|
|
5819
5886
|
if (alignment === "LEFT" || alignment === "TOP") ratio = 0;
|
|
@@ -6062,7 +6129,56 @@ function simplifyRoute$1(points) {
|
|
|
6062
6129
|
function implicitEdgeEndpoints(input, placement, orientation) {
|
|
6063
6130
|
const horizontal$1 = input.direction === "left" || input.direction === "right";
|
|
6064
6131
|
const nodeById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
6065
|
-
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
|
+
};
|
|
6066
6182
|
for (const edge of input.graph.edges) {
|
|
6067
6183
|
if (hasMovableLoopPorts(input, edge)) continue;
|
|
6068
6184
|
const sourceRect = placement.rectByNodeId.get(edge.sourceId);
|
|
@@ -6070,47 +6186,20 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6070
6186
|
if (!sourceRect || !targetRect) continue;
|
|
6071
6187
|
const sourceFlow = horizontal$1 ? sourceRect.x : sourceRect.y;
|
|
6072
6188
|
const targetFlow = horizontal$1 ? targetRect.x : targetRect.y;
|
|
6073
|
-
const modelOrderPromotion = input.settings["layering.nodePromotion.strategy"] === "MODEL_ORDER_LEFT_TO_RIGHT";
|
|
6074
6189
|
const sourceBoundary = externalPortDummyOf(nodeById.get(edge.sourceId) ?? {});
|
|
6075
6190
|
const targetBoundary = externalPortDummyOf(nodeById.get(edge.targetId) ?? {});
|
|
6076
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);
|
|
6077
6192
|
const feedback = input.settings.feedbackEdges === true && orientation?.reversedEdgeIds.has(edge.id) === true;
|
|
6078
|
-
const directionReversed = input.direction === "left" || input.direction === "up";
|
|
6079
|
-
const endpointSide = (nodeId, portName) => {
|
|
6080
|
-
const node = nodeById.get(nodeId);
|
|
6081
|
-
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
6082
|
-
const constraints = node ? input.nodeSettings?.(node)?.portConstraints : void 0;
|
|
6083
|
-
if (!node || !port || constraints !== "FIXED_SIDE" && constraints !== "FIXED_ORDER" && constraints !== "FIXED_RATIO" && constraints !== "FIXED_POS") return;
|
|
6084
|
-
const configured = input.portSettings?.(port, node)?.["port.side"];
|
|
6085
|
-
return configured === "WEST" || configured === "NORTH" ? "before" : configured === "EAST" || configured === "SOUTH" ? "after" : void 0;
|
|
6086
|
-
};
|
|
6087
|
-
const fixedImplicitSide = (nodeId, portName, source) => {
|
|
6088
|
-
const node = nodeById.get(nodeId);
|
|
6089
|
-
const constraints = node && input.nodeSettings?.(node)?.portConstraints;
|
|
6090
|
-
if (portName !== void 0 || ![
|
|
6091
|
-
"FIXED_SIDE",
|
|
6092
|
-
"FIXED_ORDER",
|
|
6093
|
-
"FIXED_RATIO",
|
|
6094
|
-
"FIXED_POS"
|
|
6095
|
-
].includes(String(constraints))) return void 0;
|
|
6096
|
-
return source !== directionReversed ? "after" : "before";
|
|
6097
|
-
};
|
|
6098
6193
|
const sourceSide = endpointSide(edge.sourceId, edge.sourcePort) ?? fixedImplicitSide(edge.sourceId, edge.sourcePort, true) ?? (feedback ? directionReversed ? "before" : "after" : forward ? "after" : "before");
|
|
6099
6194
|
const targetSide = endpointSide(edge.targetId, edge.targetPort) ?? fixedImplicitSide(edge.targetId, edge.targetPort, false) ?? (feedback ? directionReversed ? "after" : "before" : forward ? "before" : "after");
|
|
6100
|
-
|
|
6101
|
-
const targetKey = `${edge.targetId}:${targetSide}`;
|
|
6102
|
-
const sourceGroup = groups.get(sourceKey) ?? [];
|
|
6103
|
-
sourceGroup.push({
|
|
6195
|
+
addEntry(edge.sourceId, sourceSide, {
|
|
6104
6196
|
edge,
|
|
6105
6197
|
endpoint: "source"
|
|
6106
6198
|
});
|
|
6107
|
-
|
|
6108
|
-
const targetGroup = groups.get(targetKey) ?? [];
|
|
6109
|
-
targetGroup.push({
|
|
6199
|
+
addEntry(edge.targetId, targetSide, {
|
|
6110
6200
|
edge,
|
|
6111
6201
|
endpoint: "target"
|
|
6112
6202
|
});
|
|
6113
|
-
groups.set(targetKey, targetGroup);
|
|
6114
6203
|
}
|
|
6115
6204
|
const result = /* @__PURE__ */ new Map();
|
|
6116
6205
|
const edgeModelOrder = new Map(input.graph.edges.map((edge, index) => [edge.id, index]));
|
|
@@ -6131,22 +6220,19 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6131
6220
|
if (!counts) {
|
|
6132
6221
|
counts = /* @__PURE__ */ new Map();
|
|
6133
6222
|
for (const port of node.ports ?? []) {
|
|
6134
|
-
const portSide =
|
|
6223
|
+
const portSide = portSettingsOf(port, node)?.["port.side"];
|
|
6135
6224
|
counts.set(portSide, (counts.get(portSide) ?? 0) + 1);
|
|
6136
6225
|
}
|
|
6137
6226
|
portSideCounts.set(node, counts);
|
|
6138
6227
|
}
|
|
6139
6228
|
return counts.get(side) ?? 0;
|
|
6140
6229
|
};
|
|
6141
|
-
for (const
|
|
6142
|
-
const split = key.lastIndexOf(":");
|
|
6143
|
-
const nodeId = key.slice(0, split);
|
|
6144
|
-
const side = key.slice(split + 1);
|
|
6230
|
+
for (const { nodeId, side, entries } of groups) {
|
|
6145
6231
|
const rect = placement.rectByNodeId.get(nodeId);
|
|
6146
6232
|
if (!rect) continue;
|
|
6147
6233
|
const mergeEdges = input.settings.mergeEdges === true;
|
|
6148
6234
|
const node = nodeById.get(nodeId);
|
|
6149
|
-
const nodeSettings = node ?
|
|
6235
|
+
const nodeSettings = node ? nodeSettingsOf(node) : void 0;
|
|
6150
6236
|
const hypernode = nodeSettings?.hypernode;
|
|
6151
6237
|
const unzipping = (input.settings["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING";
|
|
6152
6238
|
const crossingStrategy = input.settings["crossingMinimization.strategy"] ?? "LAYER_SWEEP";
|
|
@@ -6238,9 +6324,9 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
6238
6324
|
};
|
|
6239
6325
|
const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
|
|
6240
6326
|
const explicitPort = node?.ports?.find((port) => port.name === portName);
|
|
6241
|
-
const physicalSide = node && explicitPort ?
|
|
6327
|
+
const physicalSide = node && explicitPort ? portSettingsOf(explicitPort, node)?.["port.side"] : void 0;
|
|
6242
6328
|
const singleFixedSidePort = constraints === "FIXED_SIDE" && physicalSide !== void 0 && node?.ports !== void 0 && portSideCount(node, physicalSide) === 1;
|
|
6243
|
-
const authoredAnchor = node && explicitPort ?
|
|
6329
|
+
const authoredAnchor = node && explicitPort ? portSettingsOf(explicitPort, node)?.["port.anchor"] : void 0;
|
|
6244
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);
|
|
6245
6331
|
const pair = result.get(edge.id) ?? {
|
|
6246
6332
|
source: point,
|
|
@@ -6451,6 +6537,14 @@ function routeEdges(style) {
|
|
|
6451
6537
|
flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
|
|
6452
6538
|
}
|
|
6453
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
|
+
};
|
|
6454
6548
|
const labelFlowLayers = new Set([...flowLayerByNodeId].filter(([id]) => id.startsWith("__layout_dummy:label:")).map(([, layer]) => layer));
|
|
6455
6549
|
for (const [id, rect] of placement.rectByNodeId) {
|
|
6456
6550
|
const node = nodeById.get(id);
|
|
@@ -6527,10 +6621,11 @@ function routeEdges(style) {
|
|
|
6527
6621
|
labelExtraByGap[gap] = Math.max(labelExtraByGap[gap] ?? 0, extra);
|
|
6528
6622
|
}
|
|
6529
6623
|
let labelShift = 0;
|
|
6624
|
+
const labelShiftIds = placedIdsByFlowLayer();
|
|
6530
6625
|
for (const [layerNo, bounds] of flowLayers.entries()) {
|
|
6531
6626
|
if (labelShift !== 0) {
|
|
6532
|
-
for (const
|
|
6533
|
-
|
|
6627
|
+
for (const id of labelShiftIds[layerNo]) {
|
|
6628
|
+
const rect = placement.rectByNodeId.get(id);
|
|
6534
6629
|
mutableRects.set(id, horizontal$1 ? {
|
|
6535
6630
|
...rect,
|
|
6536
6631
|
x: rect.x + labelShift
|
|
@@ -6878,18 +6973,21 @@ function routeEdges(style) {
|
|
|
6878
6973
|
const leadingSlots = slotsByGap[0] ?? 0;
|
|
6879
6974
|
const leadingWidth = leadingSlots > 0 ? edgeNodeSpacing + (leadingSlots - 1) * edgeEdgeSpacing : 0;
|
|
6880
6975
|
let nextStart = (flowLayers[0]?.start ?? 0) + leadingWidth;
|
|
6976
|
+
const flowLayerIds = flowLayers.map(() => []);
|
|
6977
|
+
for (const [id, layer] of flowLayerByNodeId) flowLayerIds[layer].push(id);
|
|
6881
6978
|
const externalFlowLayers = new Set(flowLayers.flatMap((_, layer) => {
|
|
6882
|
-
const ids = [
|
|
6883
|
-
return ids.length && ids.every((
|
|
6979
|
+
const ids = flowLayerIds[layer];
|
|
6980
|
+
return ids.length && ids.every((id) => {
|
|
6884
6981
|
const node = nodeById.get(id);
|
|
6885
6982
|
const side = node && externalPortDummyOf(node)?.side;
|
|
6886
6983
|
return horizontal$1 ? side === "WEST" || side === "EAST" : side === "NORTH" || side === "SOUTH";
|
|
6887
6984
|
}) ? [layer] : [];
|
|
6888
6985
|
}));
|
|
6986
|
+
const shiftedIds = placedIdsByFlowLayer();
|
|
6889
6987
|
for (const [layerNo, bounds] of flowLayers.entries()) {
|
|
6890
6988
|
const shift = nextStart - bounds.start;
|
|
6891
|
-
for (const
|
|
6892
|
-
|
|
6989
|
+
for (const id of shiftedIds[layerNo]) {
|
|
6990
|
+
const rect = placement.rectByNodeId.get(id);
|
|
6893
6991
|
mutableRects.set(id, horizontal$1 ? {
|
|
6894
6992
|
...rect,
|
|
6895
6993
|
x: rect.x + shift
|
|
@@ -7103,10 +7201,11 @@ function routeEdges(style) {
|
|
|
7103
7201
|
});
|
|
7104
7202
|
}
|
|
7105
7203
|
let nextStart = flowLayers[0]?.start ?? 0;
|
|
7204
|
+
const shiftedIds = placedIdsByFlowLayer();
|
|
7106
7205
|
for (const [layerNo, bounds] of flowLayers.entries()) {
|
|
7107
7206
|
const shift = nextStart - bounds.start;
|
|
7108
|
-
for (const
|
|
7109
|
-
|
|
7207
|
+
for (const id of shiftedIds[layerNo]) {
|
|
7208
|
+
const rect = placement.rectByNodeId.get(id);
|
|
7110
7209
|
mutableRects.set(id, horizontal$1 ? {
|
|
7111
7210
|
...rect,
|
|
7112
7211
|
x: rect.x + shift
|
|
@@ -7979,7 +8078,7 @@ function normalizePlacementForPortExtents(input, placement, order, orientation)
|
|
|
7979
8078
|
let trailingBoundary = Number.NEGATIVE_INFINITY;
|
|
7980
8079
|
let leadingBoundary = Number.POSITIVE_INFINITY;
|
|
7981
8080
|
for (const [ids, trailing] of [[layer, true], [nextLayer, false]]) for (const id of ids) {
|
|
7982
|
-
const node = input.graph.nodes
|
|
8081
|
+
const node = findNode(input.graph.nodes, id);
|
|
7983
8082
|
const rect = placement.rectByNodeId.get(id);
|
|
7984
8083
|
if (!node || !rect) continue;
|
|
7985
8084
|
const bodyStart = horizontal$1 ? rect.x : rect.y;
|
|
@@ -14799,10 +14898,16 @@ function buildNeighbors(input, order) {
|
|
|
14799
14898
|
return (horizontal$1 ? port?.height : port?.width) ?? 0;
|
|
14800
14899
|
});
|
|
14801
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
|
+
}
|
|
14802
14907
|
const loopTerminals = (id, side) => {
|
|
14803
14908
|
const node = nodeById.get(id);
|
|
14804
14909
|
if (!node) return 0;
|
|
14805
|
-
return
|
|
14910
|
+
return (loopsByNodeId.get(id) ?? []).flatMap((edge) => [edge.sourcePort, edge.targetPort]).filter((name) => {
|
|
14806
14911
|
const port = node.ports?.find((candidate) => candidate.name === name);
|
|
14807
14912
|
return port !== void 0 && input.portSettings?.(port, node)?.["port.side"] === side.toUpperCase() && usesSharedSideSlot(input, node, name);
|
|
14808
14913
|
}).length;
|
|
@@ -14913,8 +15018,8 @@ function alignBlocks(input, order, bal, neighbors, markedEdges) {
|
|
|
14913
15018
|
const adjacent = (bal.hdir === "LEFT" ? neighbors.right : neighbors.left).get(id) ?? [];
|
|
14914
15019
|
const low = Math.floor((adjacent.length + 1) / 2) - 1;
|
|
14915
15020
|
const high = Math.ceil((adjacent.length + 1) / 2) - 1;
|
|
14916
|
-
|
|
14917
|
-
|
|
15021
|
+
for (let i = 0; i < high - low + 1; i++) {
|
|
15022
|
+
const median = bal.vdir === "UP" ? high - i : low + i;
|
|
14918
15023
|
if (bal.align.get(id) !== id) break;
|
|
14919
15024
|
const neighbor = adjacent[median];
|
|
14920
15025
|
if (neighbor === void 0) continue;
|
|
@@ -18743,11 +18848,14 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18743
18848
|
settings: options.settings ?? {},
|
|
18744
18849
|
...options.nodeSettings === void 0 ? {} : { nodeSettings: options.nodeSettings },
|
|
18745
18850
|
...options.edgeSettings === void 0 ? {} : { edgeSettings: options.edgeSettings },
|
|
18746
|
-
portSettings: (port, node) =>
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
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
|
+
}
|
|
18751
18859
|
};
|
|
18752
18860
|
const measure = (id, run) => {
|
|
18753
18861
|
context?.throwIfAborted();
|
|
@@ -18893,8 +19001,10 @@ function* createLayeredScopePipeline(graph, options, context) {
|
|
|
18893
19001
|
const northEdges = /* @__PURE__ */ new Set(), southEdges = /* @__PURE__ */ new Set();
|
|
18894
19002
|
const upper = direction === "right" || direction === "left" ? "NORTH" : "WEST";
|
|
18895
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);
|
|
18896
19006
|
for (const edge of expanded.input.graph.edges) for (const [id, name] of [[edge.sourceId, edge.sourcePort], [edge.targetId, edge.targetPort]]) {
|
|
18897
|
-
const node =
|
|
19007
|
+
const node = endpointNodes.get(id), port = node?.ports?.find((p) => p.name === name);
|
|
18898
19008
|
const side = node && port ? expanded.input.portSettings?.(port, node)?.["port.side"] : void 0;
|
|
18899
19009
|
if (side === upper) northEdges.add(id);
|
|
18900
19010
|
if (side === lower) southEdges.add(id);
|