@crazyhappyone/auto-graph 0.2.8 → 0.2.11
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/cli/index.cjs +507 -91
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +507 -91
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +507 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +507 -91
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -176,6 +176,28 @@ function applyLayoutConstraints(input) {
|
|
|
176
176
|
if (input.distributeContainedChildren) {
|
|
177
177
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
178
178
|
}
|
|
179
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
180
|
+
for (const swimlane of input.swimlanes) {
|
|
181
|
+
if (swimlane.layout === "contract") continue;
|
|
182
|
+
for (const lane of swimlane.lanes) {
|
|
183
|
+
const fixedChildren = [];
|
|
184
|
+
let participantCount = 0;
|
|
185
|
+
for (const childId of lane.children) {
|
|
186
|
+
const lock = locks.get(childId);
|
|
187
|
+
if (lock === void 0) {
|
|
188
|
+
participantCount += 1;
|
|
189
|
+
} else if (lock.source === "fixed-position") {
|
|
190
|
+
participantCount += 1;
|
|
191
|
+
fixedChildren.push(childId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (participantCount < 2) continue;
|
|
195
|
+
for (const childId of fixedChildren) {
|
|
196
|
+
locks.delete(childId);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
179
201
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
180
202
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
181
203
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -195,6 +217,9 @@ function applyLayoutConstraints(input) {
|
|
|
195
217
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
196
218
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
197
219
|
}
|
|
220
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
221
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
222
|
+
}
|
|
198
223
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
199
224
|
reportOverlaps(
|
|
200
225
|
boxes,
|
|
@@ -1034,9 +1059,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1034
1059
|
}
|
|
1035
1060
|
});
|
|
1036
1061
|
}
|
|
1037
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1038
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1039
|
-
}
|
|
1040
1062
|
}
|
|
1041
1063
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
1042
1064
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -1076,6 +1098,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
1076
1098
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
1077
1099
|
}
|
|
1078
1100
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
1101
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
1079
1102
|
let pos = contentStart;
|
|
1080
1103
|
for (const child of unlocked) {
|
|
1081
1104
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4726,15 +4749,12 @@ var BinaryHeap = class {
|
|
|
4726
4749
|
let smallestIdx = idx;
|
|
4727
4750
|
const leftIdx = (idx << 1) + 1;
|
|
4728
4751
|
const rightIdx = leftIdx + 1;
|
|
4729
|
-
if (leftIdx < size && this._less(
|
|
4730
|
-
this._data[leftIdx],
|
|
4731
|
-
this._data[smallestIdx]
|
|
4732
|
-
)) {
|
|
4752
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4733
4753
|
smallestIdx = leftIdx;
|
|
4734
4754
|
}
|
|
4735
4755
|
if (rightIdx < size && this._less(
|
|
4736
4756
|
this._data[rightIdx],
|
|
4737
|
-
this._data[
|
|
4757
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4738
4758
|
)) {
|
|
4739
4759
|
smallestIdx = rightIdx;
|
|
4740
4760
|
}
|
|
@@ -4801,8 +4821,59 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4801
4821
|
turnPenalty,
|
|
4802
4822
|
segmentPenalty
|
|
4803
4823
|
);
|
|
4804
|
-
if (path
|
|
4805
|
-
|
|
4824
|
+
if (path !== null) {
|
|
4825
|
+
const simplified = simplifyRoute(path);
|
|
4826
|
+
const filteredSet = new Set(filtered);
|
|
4827
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4828
|
+
let crossesExcluded = false;
|
|
4829
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4830
|
+
const a = simplified[i];
|
|
4831
|
+
const b = simplified[i + 1];
|
|
4832
|
+
for (const obs of obstacles) {
|
|
4833
|
+
if (filteredSet.has(obs)) continue;
|
|
4834
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4835
|
+
crossesExcluded = true;
|
|
4836
|
+
break;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
if (crossesExcluded) break;
|
|
4840
|
+
}
|
|
4841
|
+
if (!crossesExcluded) return simplified;
|
|
4842
|
+
} else {
|
|
4843
|
+
return simplified;
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
if (!useCorridor) return null;
|
|
4847
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4848
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4849
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4850
|
+
diagnostics?.push({
|
|
4851
|
+
severity: "warning",
|
|
4852
|
+
code: "routing.astar.grid_overflow",
|
|
4853
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4854
|
+
detail: { xsCount: xsFull.length, ysCount: ysFull.length, maxNodes }
|
|
4855
|
+
});
|
|
4856
|
+
return null;
|
|
4857
|
+
}
|
|
4858
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4859
|
+
connectHorizontalEdges(
|
|
4860
|
+
nodesFull,
|
|
4861
|
+
ysFull,
|
|
4862
|
+
obstacles,
|
|
4863
|
+
endpointObstacles,
|
|
4864
|
+
margin
|
|
4865
|
+
);
|
|
4866
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4867
|
+
const pathFull = aStarSearch(
|
|
4868
|
+
nodesFull,
|
|
4869
|
+
idxFull,
|
|
4870
|
+
source,
|
|
4871
|
+
target,
|
|
4872
|
+
turnPenalty,
|
|
4873
|
+
segmentPenalty
|
|
4874
|
+
);
|
|
4875
|
+
if (pathFull === null) return null;
|
|
4876
|
+
return simplifyRoute(pathFull);
|
|
4806
4877
|
}
|
|
4807
4878
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4808
4879
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -5029,7 +5100,7 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
5029
5100
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
5030
5101
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
5031
5102
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
5032
|
-
const maxCorners = options.maxCorners ??
|
|
5103
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
5033
5104
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
5034
5105
|
if (vertices.length > maxCorners) {
|
|
5035
5106
|
diagnostics?.push({
|
|
@@ -5310,7 +5381,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
5310
5381
|
}
|
|
5311
5382
|
|
|
5312
5383
|
// src/routing/routes.ts
|
|
5313
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
5384
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
5314
5385
|
if (points.length < 2) return;
|
|
5315
5386
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
5316
5387
|
if (direct <= 0) return;
|
|
@@ -5320,7 +5391,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
5320
5391
|
const b = points[i + 1];
|
|
5321
5392
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
5322
5393
|
}
|
|
5323
|
-
const threshold =
|
|
5394
|
+
const threshold = maxRatio ?? 20;
|
|
5324
5395
|
if (routeLen > direct * threshold) {
|
|
5325
5396
|
diagnostics.push({
|
|
5326
5397
|
severity: "warning",
|
|
@@ -5338,8 +5409,20 @@ function routeEdge(input) {
|
|
|
5338
5409
|
const diagnostics = [];
|
|
5339
5410
|
const softObstacles = input.obstacles ?? [];
|
|
5340
5411
|
const hardObstacles = input.hardObstacles ?? [];
|
|
5412
|
+
let bestRejectedPath;
|
|
5413
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
5341
5414
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
5342
5415
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
5416
|
+
const recordRejected = (candidate) => {
|
|
5417
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5420
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
5421
|
+
if (crossings < bestRejectedCrossings) {
|
|
5422
|
+
bestRejectedCrossings = crossings;
|
|
5423
|
+
bestRejectedPath = candidate;
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5343
5426
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
5344
5427
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
5345
5428
|
input.source.box,
|
|
@@ -5398,18 +5481,38 @@ function routeEdge(input) {
|
|
|
5398
5481
|
input.source.center,
|
|
5399
5482
|
targetAnchor
|
|
5400
5483
|
);
|
|
5401
|
-
const
|
|
5484
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
5485
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
5486
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
5487
|
+
source,
|
|
5488
|
+
target,
|
|
5489
|
+
allObstacles,
|
|
5490
|
+
[],
|
|
5491
|
+
// endpointObstacles passed separately via options
|
|
5492
|
+
corridorMargin
|
|
5493
|
+
);
|
|
5494
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
5495
|
+
let cornerPath = findCornerGraphPath(
|
|
5402
5496
|
source,
|
|
5403
5497
|
target,
|
|
5404
|
-
|
|
5498
|
+
cornerObstacles,
|
|
5405
5499
|
{ endpointObstacles, margin: 2 },
|
|
5406
5500
|
diagnostics
|
|
5407
5501
|
);
|
|
5502
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
5503
|
+
cornerPath = findCornerGraphPath(
|
|
5504
|
+
source,
|
|
5505
|
+
target,
|
|
5506
|
+
allObstacles,
|
|
5507
|
+
{ endpointObstacles, margin: 2 },
|
|
5508
|
+
diagnostics
|
|
5509
|
+
);
|
|
5510
|
+
}
|
|
5408
5511
|
const path = cornerPath ?? findObstacleFreePath(
|
|
5409
5512
|
source,
|
|
5410
5513
|
target,
|
|
5411
|
-
|
|
5412
|
-
{ endpointObstacles, margin: 0 },
|
|
5514
|
+
allObstacles,
|
|
5515
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
5413
5516
|
diagnostics
|
|
5414
5517
|
);
|
|
5415
5518
|
if (path !== null && path.length >= 2) {
|
|
@@ -5426,9 +5529,90 @@ function routeEdge(input) {
|
|
|
5426
5529
|
softObstacles,
|
|
5427
5530
|
softObstacleIndex
|
|
5428
5531
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
5429
|
-
checkBacktracking(
|
|
5532
|
+
checkBacktracking(
|
|
5533
|
+
finalized,
|
|
5534
|
+
source,
|
|
5535
|
+
target,
|
|
5536
|
+
diagnostics,
|
|
5537
|
+
input.maxBacktrackingRatio
|
|
5538
|
+
);
|
|
5430
5539
|
return { points: finalized, diagnostics };
|
|
5431
5540
|
}
|
|
5541
|
+
recordRejected(finalized);
|
|
5542
|
+
if (cornerPath !== null) {
|
|
5543
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
5544
|
+
source,
|
|
5545
|
+
target,
|
|
5546
|
+
allObstacles,
|
|
5547
|
+
{ endpointObstacles, margin: 2 },
|
|
5548
|
+
diagnostics
|
|
5549
|
+
) : null;
|
|
5550
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5551
|
+
const fullFinalized = finalizeRoute(
|
|
5552
|
+
fullCornerPath,
|
|
5553
|
+
softObstacles,
|
|
5554
|
+
hardObstacles,
|
|
5555
|
+
diagnostics,
|
|
5556
|
+
softObstacleIndex,
|
|
5557
|
+
hardObstacleIndex
|
|
5558
|
+
);
|
|
5559
|
+
if (!routeIntersectsObstacles(
|
|
5560
|
+
fullFinalized,
|
|
5561
|
+
softObstacles,
|
|
5562
|
+
softObstacleIndex
|
|
5563
|
+
) && !routeIntersectsObstacles(
|
|
5564
|
+
fullFinalized,
|
|
5565
|
+
hardObstacles,
|
|
5566
|
+
hardObstacleIndex
|
|
5567
|
+
)) {
|
|
5568
|
+
checkBacktracking(
|
|
5569
|
+
fullFinalized,
|
|
5570
|
+
source,
|
|
5571
|
+
target,
|
|
5572
|
+
diagnostics,
|
|
5573
|
+
input.maxBacktrackingRatio
|
|
5574
|
+
);
|
|
5575
|
+
return { points: fullFinalized, diagnostics };
|
|
5576
|
+
}
|
|
5577
|
+
recordRejected(fullFinalized);
|
|
5578
|
+
}
|
|
5579
|
+
const gridPath = findObstacleFreePath(
|
|
5580
|
+
source,
|
|
5581
|
+
target,
|
|
5582
|
+
allObstacles,
|
|
5583
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
5584
|
+
diagnostics
|
|
5585
|
+
);
|
|
5586
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5587
|
+
const gridFinalized = finalizeRoute(
|
|
5588
|
+
gridPath,
|
|
5589
|
+
softObstacles,
|
|
5590
|
+
hardObstacles,
|
|
5591
|
+
diagnostics,
|
|
5592
|
+
softObstacleIndex,
|
|
5593
|
+
hardObstacleIndex
|
|
5594
|
+
);
|
|
5595
|
+
if (!routeIntersectsObstacles(
|
|
5596
|
+
gridFinalized,
|
|
5597
|
+
softObstacles,
|
|
5598
|
+
softObstacleIndex
|
|
5599
|
+
) && !routeIntersectsObstacles(
|
|
5600
|
+
gridFinalized,
|
|
5601
|
+
hardObstacles,
|
|
5602
|
+
hardObstacleIndex
|
|
5603
|
+
)) {
|
|
5604
|
+
checkBacktracking(
|
|
5605
|
+
gridFinalized,
|
|
5606
|
+
source,
|
|
5607
|
+
target,
|
|
5608
|
+
diagnostics,
|
|
5609
|
+
input.maxBacktrackingRatio
|
|
5610
|
+
);
|
|
5611
|
+
return { points: gridFinalized, diagnostics };
|
|
5612
|
+
}
|
|
5613
|
+
recordRejected(gridFinalized);
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5432
5616
|
}
|
|
5433
5617
|
}
|
|
5434
5618
|
}
|
|
@@ -5490,7 +5674,8 @@ function routeEdge(input) {
|
|
|
5490
5674
|
finalizedClean,
|
|
5491
5675
|
candidate.points[0],
|
|
5492
5676
|
candidate.points[candidate.points.length - 1],
|
|
5493
|
-
diagnostics
|
|
5677
|
+
diagnostics,
|
|
5678
|
+
input.maxBacktrackingRatio
|
|
5494
5679
|
);
|
|
5495
5680
|
return { points: finalizedClean, diagnostics };
|
|
5496
5681
|
}
|
|
@@ -5556,13 +5741,41 @@ function routeEdge(input) {
|
|
|
5556
5741
|
code: "routing.obstacle.unavoidable",
|
|
5557
5742
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5558
5743
|
});
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5744
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5745
|
+
bestPoints2,
|
|
5746
|
+
softObstacles,
|
|
5747
|
+
hardObstacles,
|
|
5748
|
+
diagnostics
|
|
5749
|
+
);
|
|
5750
|
+
let softFallback = finalizedSoftBest;
|
|
5751
|
+
if (bestRejectedPath !== void 0) {
|
|
5752
|
+
const finalizedRejected = finalizeRoute(
|
|
5753
|
+
bestRejectedPath,
|
|
5562
5754
|
softObstacles,
|
|
5563
5755
|
hardObstacles,
|
|
5564
5756
|
diagnostics
|
|
5565
|
-
)
|
|
5757
|
+
);
|
|
5758
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5759
|
+
finalizedRejected,
|
|
5760
|
+
softObstacles
|
|
5761
|
+
);
|
|
5762
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5763
|
+
finalizedSoftBest,
|
|
5764
|
+
softObstacles
|
|
5765
|
+
);
|
|
5766
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5767
|
+
softFallback = finalizedRejected;
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
checkBacktracking(
|
|
5771
|
+
softFallback,
|
|
5772
|
+
softFallback[0],
|
|
5773
|
+
softFallback[softFallback.length - 1],
|
|
5774
|
+
diagnostics,
|
|
5775
|
+
input.maxBacktrackingRatio
|
|
5776
|
+
);
|
|
5777
|
+
return {
|
|
5778
|
+
points: softFallback,
|
|
5566
5779
|
diagnostics
|
|
5567
5780
|
};
|
|
5568
5781
|
}
|
|
@@ -5594,6 +5807,22 @@ function routeEdge(input) {
|
|
|
5594
5807
|
maxAttempts
|
|
5595
5808
|
);
|
|
5596
5809
|
}
|
|
5810
|
+
if (bestRejectedPath !== void 0) {
|
|
5811
|
+
diagnostics.push({
|
|
5812
|
+
severity: "warning",
|
|
5813
|
+
code: "routing.obstacle.unavoidable",
|
|
5814
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5815
|
+
});
|
|
5816
|
+
return {
|
|
5817
|
+
points: finalizeRoute(
|
|
5818
|
+
bestRejectedPath,
|
|
5819
|
+
softObstacles,
|
|
5820
|
+
hardObstacles,
|
|
5821
|
+
diagnostics
|
|
5822
|
+
),
|
|
5823
|
+
diagnostics
|
|
5824
|
+
};
|
|
5825
|
+
}
|
|
5597
5826
|
diagnostics.push({
|
|
5598
5827
|
severity: "error",
|
|
5599
5828
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5641,13 +5870,41 @@ function routeEdge(input) {
|
|
|
5641
5870
|
code: "routing.obstacle.unavoidable",
|
|
5642
5871
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5643
5872
|
});
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5873
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5874
|
+
bestPoints,
|
|
5875
|
+
softObstacles,
|
|
5876
|
+
hardObstacles,
|
|
5877
|
+
diagnostics
|
|
5878
|
+
);
|
|
5879
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5880
|
+
if (bestRejectedPath !== void 0) {
|
|
5881
|
+
const finalizedRejected = finalizeRoute(
|
|
5882
|
+
bestRejectedPath,
|
|
5647
5883
|
softObstacles,
|
|
5648
5884
|
hardObstacles,
|
|
5649
5885
|
diagnostics
|
|
5650
|
-
)
|
|
5886
|
+
);
|
|
5887
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5888
|
+
finalizedRejected,
|
|
5889
|
+
softObstacles
|
|
5890
|
+
);
|
|
5891
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5892
|
+
finalizedBestPoints,
|
|
5893
|
+
softObstacles
|
|
5894
|
+
);
|
|
5895
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5896
|
+
fallbackPoints = finalizedRejected;
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
checkBacktracking(
|
|
5900
|
+
fallbackPoints,
|
|
5901
|
+
fallbackPoints[0],
|
|
5902
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5903
|
+
diagnostics,
|
|
5904
|
+
input.maxBacktrackingRatio
|
|
5905
|
+
);
|
|
5906
|
+
return {
|
|
5907
|
+
points: fallbackPoints,
|
|
5651
5908
|
diagnostics
|
|
5652
5909
|
};
|
|
5653
5910
|
}
|
|
@@ -6146,6 +6403,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
6146
6403
|
}
|
|
6147
6404
|
return false;
|
|
6148
6405
|
}
|
|
6406
|
+
function countObstacleCrossings(points, obstacles) {
|
|
6407
|
+
let count = 0;
|
|
6408
|
+
for (const obstacle of obstacles) {
|
|
6409
|
+
validateBox(obstacle);
|
|
6410
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
6411
|
+
const a = points[pointIndex];
|
|
6412
|
+
const b = points[pointIndex + 1];
|
|
6413
|
+
if (a === void 0 || b === void 0) {
|
|
6414
|
+
continue;
|
|
6415
|
+
}
|
|
6416
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
6417
|
+
count += 1;
|
|
6418
|
+
break;
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
}
|
|
6422
|
+
return count;
|
|
6423
|
+
}
|
|
6149
6424
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
6150
6425
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
6151
6426
|
const a = points[index];
|
|
@@ -6529,7 +6804,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6529
6804
|
edges: styledEdges
|
|
6530
6805
|
});
|
|
6531
6806
|
diagnostics.push(...layout2.diagnostics);
|
|
6532
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6807
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6533
6808
|
layout2.boxes,
|
|
6534
6809
|
styledNodes,
|
|
6535
6810
|
styledEdges,
|
|
@@ -6537,7 +6812,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6537
6812
|
options,
|
|
6538
6813
|
diagnostics
|
|
6539
6814
|
);
|
|
6540
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6815
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6816
|
+
const diagCountBefore = diagnostics.length;
|
|
6541
6817
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
6542
6818
|
initialNodeBoxes,
|
|
6543
6819
|
styledNodes,
|
|
@@ -6548,6 +6824,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6548
6824
|
for (const [id, box] of rewrapped) {
|
|
6549
6825
|
initialNodeBoxes.set(id, box);
|
|
6550
6826
|
}
|
|
6827
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6828
|
+
for (const node of styledNodes) {
|
|
6829
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6830
|
+
const rwBox = rewrapped.get(node.id);
|
|
6831
|
+
const idx = styledNodes.indexOf(node);
|
|
6832
|
+
if (idx !== -1) {
|
|
6833
|
+
styledNodes[idx] = {
|
|
6834
|
+
...node,
|
|
6835
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6551
6841
|
}
|
|
6552
6842
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
6553
6843
|
const recursiveLayout = layout2;
|
|
@@ -6561,7 +6851,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6561
6851
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
6562
6852
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
6563
6853
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
6564
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6854
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
6565
6855
|
swimlanes: styledSwimlanes,
|
|
6566
6856
|
boxes: initialNodeBoxes,
|
|
6567
6857
|
nodes: styledNodes,
|
|
@@ -6576,7 +6866,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6576
6866
|
constrained.boxes,
|
|
6577
6867
|
constrained.locks,
|
|
6578
6868
|
options?.overlapSpacing ?? 40,
|
|
6579
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6869
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6870
|
+
options.distributeContainedChildren ?? true
|
|
6580
6871
|
);
|
|
6581
6872
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
6582
6873
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6743,7 +7034,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6743
7034
|
diagram.direction,
|
|
6744
7035
|
options,
|
|
6745
7036
|
diagnostics,
|
|
6746
|
-
coordinatedGroups
|
|
7037
|
+
coordinatedGroups,
|
|
7038
|
+
contentBounds
|
|
6747
7039
|
);
|
|
6748
7040
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6749
7041
|
coordinatedEdges,
|
|
@@ -7165,7 +7457,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
7165
7457
|
function containsCjk(value) {
|
|
7166
7458
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
7167
7459
|
}
|
|
7168
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
7460
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
7169
7461
|
const layouts = /* @__PURE__ */ new Map();
|
|
7170
7462
|
const diagnostics = [];
|
|
7171
7463
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -7184,7 +7476,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
7184
7476
|
locks,
|
|
7185
7477
|
diagnostics,
|
|
7186
7478
|
movedChildIds,
|
|
7187
|
-
laneGutter
|
|
7479
|
+
laneGutter,
|
|
7480
|
+
constraints,
|
|
7481
|
+
distributeContainedChildren
|
|
7188
7482
|
);
|
|
7189
7483
|
if (layout2 !== void 0) {
|
|
7190
7484
|
layouts.set(swimlane.id, layout2);
|
|
@@ -7280,9 +7574,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
7280
7574
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
7281
7575
|
return new Map(boxes);
|
|
7282
7576
|
}
|
|
7283
|
-
|
|
7284
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
7285
|
-
|
|
7577
|
+
let maxRowDepth = options.maxRowDepth;
|
|
7578
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
7579
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
7580
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
7581
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
7582
|
+
} else {
|
|
7583
|
+
return new Map(boxes);
|
|
7584
|
+
}
|
|
7286
7585
|
}
|
|
7287
7586
|
const ordered = [...nodes].sort((a, b) => {
|
|
7288
7587
|
const ba = boxes.get(a.id);
|
|
@@ -7343,10 +7642,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
7343
7642
|
});
|
|
7344
7643
|
}
|
|
7345
7644
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
7346
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
7645
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
7347
7646
|
return false;
|
|
7348
7647
|
}
|
|
7349
|
-
if (nodes.length < 2
|
|
7648
|
+
if (nodes.length < 2) {
|
|
7350
7649
|
return false;
|
|
7351
7650
|
}
|
|
7352
7651
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -7354,17 +7653,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
7354
7653
|
return false;
|
|
7355
7654
|
}
|
|
7356
7655
|
const bounds = unionBoxes(nodeBoxes);
|
|
7357
|
-
const
|
|
7358
|
-
const
|
|
7359
|
-
|
|
7656
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
7657
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
7658
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
7659
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
7360
7660
|
return false;
|
|
7361
7661
|
}
|
|
7662
|
+
if (isHorizontal) {
|
|
7663
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
7664
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
7665
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
7666
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
7667
|
+
}
|
|
7362
7668
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
7363
7669
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
7364
7670
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
7365
7671
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
7366
7672
|
}
|
|
7367
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7673
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7368
7674
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
7369
7675
|
const padding = swimlane.padding ?? 16;
|
|
7370
7676
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -7389,7 +7695,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7389
7695
|
locks,
|
|
7390
7696
|
diagnostics,
|
|
7391
7697
|
movedChildIds,
|
|
7392
|
-
laneGutter
|
|
7698
|
+
laneGutter,
|
|
7699
|
+
constraints,
|
|
7700
|
+
distributeContainedChildren
|
|
7393
7701
|
);
|
|
7394
7702
|
}
|
|
7395
7703
|
return applyHorizontalSwimlaneContract(
|
|
@@ -7404,13 +7712,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7404
7712
|
laneGutter
|
|
7405
7713
|
);
|
|
7406
7714
|
}
|
|
7407
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7715
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7408
7716
|
const populatedBounds = laneBounds.filter(
|
|
7409
7717
|
(box) => box !== void 0
|
|
7410
7718
|
);
|
|
7411
7719
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
7412
7720
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
7413
7721
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
7722
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
7723
|
+
if (distributeContainedChildren) {
|
|
7724
|
+
for (const c of constraints) {
|
|
7725
|
+
if (c.kind !== "containment") continue;
|
|
7726
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
7727
|
+
const distributable = c.childIds.filter((childId) => {
|
|
7728
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
7729
|
+
const lock = locks.get(childId);
|
|
7730
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
7731
|
+
});
|
|
7732
|
+
if (distributable.length < 2) continue;
|
|
7733
|
+
for (const childId of distributable) {
|
|
7734
|
+
containedChildIds.add(childId);
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7414
7738
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
7415
7739
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
7416
7740
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -7422,7 +7746,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7422
7746
|
);
|
|
7423
7747
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
7424
7748
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
7425
|
-
const
|
|
7749
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
7750
|
+
swimlane,
|
|
7751
|
+
nodeBoxes,
|
|
7752
|
+
flowRanks,
|
|
7753
|
+
locks,
|
|
7754
|
+
rankStackGap,
|
|
7755
|
+
containedChildIds
|
|
7756
|
+
);
|
|
7757
|
+
const slotWidth = Math.max(
|
|
7758
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7759
|
+
spreadWidth
|
|
7760
|
+
) + padding * 2;
|
|
7426
7761
|
const laneStep = slotWidth + laneGutter;
|
|
7427
7762
|
const laneContentTop = top + headerHeight + padding;
|
|
7428
7763
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -7436,6 +7771,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7436
7771
|
y: laneContentTop
|
|
7437
7772
|
};
|
|
7438
7773
|
if (maxRank === 0) {
|
|
7774
|
+
const distributable = lane.children.filter(
|
|
7775
|
+
(childId) => !locks.has(childId)
|
|
7776
|
+
);
|
|
7777
|
+
const coveredByContainment = lane.children.some(
|
|
7778
|
+
(childId) => containedChildIds.has(childId)
|
|
7779
|
+
);
|
|
7780
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7781
|
+
moveRankedVerticalLaneChildren(
|
|
7782
|
+
lane.children,
|
|
7783
|
+
nodeBoxes,
|
|
7784
|
+
locks,
|
|
7785
|
+
diagnostics,
|
|
7786
|
+
movedChildIds,
|
|
7787
|
+
flowRanks,
|
|
7788
|
+
rankSpacing,
|
|
7789
|
+
rankStackGap,
|
|
7790
|
+
{ x: target.x, y: laneContentTop },
|
|
7791
|
+
slotWidth - padding * 2
|
|
7792
|
+
);
|
|
7793
|
+
continue;
|
|
7794
|
+
}
|
|
7439
7795
|
moveLaneChildren(
|
|
7440
7796
|
lane.children,
|
|
7441
7797
|
nodeBoxes,
|
|
@@ -7449,6 +7805,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7449
7805
|
);
|
|
7450
7806
|
continue;
|
|
7451
7807
|
}
|
|
7808
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7809
|
+
(childId) => containedChildIds.has(childId)
|
|
7810
|
+
);
|
|
7452
7811
|
moveRankedVerticalLaneChildren(
|
|
7453
7812
|
lane.children,
|
|
7454
7813
|
nodeBoxes,
|
|
@@ -7458,10 +7817,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7458
7817
|
flowRanks,
|
|
7459
7818
|
rankSpacing,
|
|
7460
7819
|
rankStackGap,
|
|
7461
|
-
{
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
}
|
|
7820
|
+
{ x: target.x, y: laneContentTop },
|
|
7821
|
+
slotWidth - padding * 2,
|
|
7822
|
+
rankedCoveredByContainment
|
|
7465
7823
|
);
|
|
7466
7824
|
}
|
|
7467
7825
|
return {
|
|
@@ -7570,31 +7928,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
7570
7928
|
}
|
|
7571
7929
|
return maxHeight;
|
|
7572
7930
|
}
|
|
7573
|
-
|
|
7931
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7932
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7933
|
+
return items.reduce(
|
|
7934
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7935
|
+
0
|
|
7936
|
+
);
|
|
7937
|
+
}
|
|
7938
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7939
|
+
let maxWidth = 0;
|
|
7940
|
+
for (const lane of swimlane.lanes) {
|
|
7941
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7942
|
+
continue;
|
|
7943
|
+
}
|
|
7944
|
+
for (const stack of rankStacks(
|
|
7945
|
+
lane.children,
|
|
7946
|
+
nodeBoxes,
|
|
7947
|
+
flowRanks
|
|
7948
|
+
).values()) {
|
|
7949
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7950
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7951
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
return maxWidth;
|
|
7955
|
+
}
|
|
7956
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
7574
7957
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
7575
|
-
|
|
7958
|
+
const unlocked = [];
|
|
7576
7959
|
for (const item of stack) {
|
|
7577
|
-
|
|
7578
|
-
if (locks.has(childId)) {
|
|
7960
|
+
if (locks.has(item.childId)) {
|
|
7579
7961
|
diagnostics.push({
|
|
7580
7962
|
severity: "warning",
|
|
7581
7963
|
code: "constraints.locked-target-not-moved",
|
|
7582
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7964
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
7583
7965
|
path: ["swimlanes"],
|
|
7584
|
-
detail: { nodeId: childId }
|
|
7966
|
+
detail: { nodeId: item.childId }
|
|
7585
7967
|
});
|
|
7586
|
-
|
|
7968
|
+
} else {
|
|
7969
|
+
unlocked.push(item);
|
|
7587
7970
|
}
|
|
7971
|
+
}
|
|
7972
|
+
if (unlocked.length === 0) continue;
|
|
7973
|
+
if (unlocked.length === 1) {
|
|
7974
|
+
const { childId, box } = unlocked[0];
|
|
7588
7975
|
const next = {
|
|
7589
7976
|
...box,
|
|
7590
|
-
x:
|
|
7591
|
-
y: target.y + rank * rankSpacing
|
|
7977
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7978
|
+
y: target.y + rank * rankSpacing
|
|
7592
7979
|
};
|
|
7593
7980
|
if (next.x !== box.x || next.y !== box.y) {
|
|
7594
7981
|
movedChildIds.add(childId);
|
|
7595
7982
|
}
|
|
7596
7983
|
nodeBoxes.set(childId, next);
|
|
7597
|
-
|
|
7984
|
+
} else {
|
|
7985
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7986
|
+
if (!shouldSpread) {
|
|
7987
|
+
let yOffset = 0;
|
|
7988
|
+
for (const { childId, box } of unlocked) {
|
|
7989
|
+
const next = {
|
|
7990
|
+
...box,
|
|
7991
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7992
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7993
|
+
};
|
|
7994
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7995
|
+
movedChildIds.add(childId);
|
|
7996
|
+
}
|
|
7997
|
+
nodeBoxes.set(childId, next);
|
|
7998
|
+
yOffset += box.height + rankStackGap;
|
|
7999
|
+
}
|
|
8000
|
+
} else {
|
|
8001
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
8002
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
8003
|
+
for (const { childId, box } of unlocked) {
|
|
8004
|
+
const next = {
|
|
8005
|
+
...box,
|
|
8006
|
+
x: xCursor,
|
|
8007
|
+
y: target.y + rank * rankSpacing
|
|
8008
|
+
};
|
|
8009
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
8010
|
+
movedChildIds.add(childId);
|
|
8011
|
+
}
|
|
8012
|
+
nodeBoxes.set(childId, next);
|
|
8013
|
+
xCursor += box.width + rankStackGap;
|
|
8014
|
+
}
|
|
8015
|
+
diagnostics.push({
|
|
8016
|
+
severity: "info",
|
|
8017
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
8018
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
8019
|
+
path: ["swimlanes"],
|
|
8020
|
+
detail: {
|
|
8021
|
+
rank,
|
|
8022
|
+
childCount: unlocked.length,
|
|
8023
|
+
contentWidth
|
|
8024
|
+
}
|
|
8025
|
+
});
|
|
8026
|
+
}
|
|
7598
8027
|
}
|
|
7599
8028
|
}
|
|
7600
8029
|
}
|
|
@@ -7933,7 +8362,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7933
8362
|
});
|
|
7934
8363
|
continue;
|
|
7935
8364
|
}
|
|
7936
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
8365
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7937
8366
|
const geometry = computeShapeGeometry({
|
|
7938
8367
|
shape: node.shape,
|
|
7939
8368
|
box,
|
|
@@ -8027,7 +8456,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
8027
8456
|
}
|
|
8028
8457
|
}
|
|
8029
8458
|
}
|
|
8030
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
8459
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
8031
8460
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
8032
8461
|
for (const port of node.ports ?? []) {
|
|
8033
8462
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -8050,9 +8479,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8050
8479
|
side,
|
|
8051
8480
|
index,
|
|
8052
8481
|
sorted.length,
|
|
8053
|
-
portShifting
|
|
8054
|
-
diagnostics,
|
|
8055
|
-
node.id
|
|
8482
|
+
portShifting
|
|
8056
8483
|
);
|
|
8057
8484
|
const box = portBox(anchor);
|
|
8058
8485
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -8060,32 +8487,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8060
8487
|
}
|
|
8061
8488
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
8062
8489
|
}
|
|
8063
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
8490
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
8064
8491
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
8065
8492
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
8066
8493
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
8067
8494
|
const availableSpan = 2 * maxOffset;
|
|
8068
8495
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
8069
|
-
const
|
|
8496
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
8070
8497
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
8071
8498
|
minSpacing
|
|
8072
8499
|
) : requestedSpacing;
|
|
8073
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
8074
|
-
diagnostics.push({
|
|
8075
|
-
severity: "warning",
|
|
8076
|
-
code: "port_constraint_overlap",
|
|
8077
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
8078
|
-
path: ["nodes", nodeId, "ports"],
|
|
8079
|
-
detail: {
|
|
8080
|
-
nodeId,
|
|
8081
|
-
side,
|
|
8082
|
-
requestedSpacing,
|
|
8083
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
8084
|
-
portCount: count
|
|
8085
|
-
}
|
|
8086
|
-
});
|
|
8087
|
-
}
|
|
8088
|
-
const spacing = effectiveSpacing;
|
|
8089
8500
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
8090
8501
|
switch (side) {
|
|
8091
8502
|
case "left":
|
|
@@ -8700,14 +9111,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8700
9111
|
}
|
|
8701
9112
|
};
|
|
8702
9113
|
}
|
|
8703
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
9114
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8704
9115
|
const coordinated = [];
|
|
8705
9116
|
const coordinatedNodeById = new Map(
|
|
8706
9117
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8707
9118
|
);
|
|
9119
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
9120
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
9121
|
+
200,
|
|
9122
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
9123
|
+
);
|
|
9124
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
9125
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8708
9126
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8709
9127
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8710
|
-
|
|
9128
|
+
queryGutter
|
|
8711
9129
|
);
|
|
8712
9130
|
for (const edge of edges) {
|
|
8713
9131
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8729,11 +9147,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8729
9147
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8730
9148
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8731
9149
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8732
|
-
const corridor = edgeCorridorBox(
|
|
8733
|
-
source.box,
|
|
8734
|
-
target.box,
|
|
8735
|
-
options.routingGutter ?? 160
|
|
8736
|
-
);
|
|
9150
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8737
9151
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8738
9152
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8739
9153
|
);
|
|
@@ -8751,7 +9165,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8751
9165
|
...routeTextObstacles
|
|
8752
9166
|
],
|
|
8753
9167
|
hardObstacles,
|
|
8754
|
-
|
|
9168
|
+
corridorMargin,
|
|
9169
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
9170
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
8755
9171
|
});
|
|
8756
9172
|
diagnostics.push(
|
|
8757
9173
|
...route.diagnostics.map((diagnostic) => ({
|