@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.cjs
CHANGED
|
@@ -179,6 +179,28 @@ function applyLayoutConstraints(input) {
|
|
|
179
179
|
if (input.distributeContainedChildren) {
|
|
180
180
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
181
181
|
}
|
|
182
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
183
|
+
for (const swimlane of input.swimlanes) {
|
|
184
|
+
if (swimlane.layout === "contract") continue;
|
|
185
|
+
for (const lane of swimlane.lanes) {
|
|
186
|
+
const fixedChildren = [];
|
|
187
|
+
let participantCount = 0;
|
|
188
|
+
for (const childId of lane.children) {
|
|
189
|
+
const lock = locks.get(childId);
|
|
190
|
+
if (lock === void 0) {
|
|
191
|
+
participantCount += 1;
|
|
192
|
+
} else if (lock.source === "fixed-position") {
|
|
193
|
+
participantCount += 1;
|
|
194
|
+
fixedChildren.push(childId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (participantCount < 2) continue;
|
|
198
|
+
for (const childId of fixedChildren) {
|
|
199
|
+
locks.delete(childId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
182
204
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
183
205
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
184
206
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -198,6 +220,9 @@ function applyLayoutConstraints(input) {
|
|
|
198
220
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
199
221
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
200
222
|
}
|
|
223
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
224
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
225
|
+
}
|
|
201
226
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
202
227
|
reportOverlaps(
|
|
203
228
|
boxes,
|
|
@@ -1037,9 +1062,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1037
1062
|
}
|
|
1038
1063
|
});
|
|
1039
1064
|
}
|
|
1040
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1041
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1042
|
-
}
|
|
1043
1065
|
}
|
|
1044
1066
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
1045
1067
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -1079,6 +1101,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
1079
1101
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
1080
1102
|
}
|
|
1081
1103
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
1104
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
1082
1105
|
let pos = contentStart;
|
|
1083
1106
|
for (const child of unlocked) {
|
|
1084
1107
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4729,15 +4752,12 @@ var BinaryHeap = class {
|
|
|
4729
4752
|
let smallestIdx = idx;
|
|
4730
4753
|
const leftIdx = (idx << 1) + 1;
|
|
4731
4754
|
const rightIdx = leftIdx + 1;
|
|
4732
|
-
if (leftIdx < size && this._less(
|
|
4733
|
-
this._data[leftIdx],
|
|
4734
|
-
this._data[smallestIdx]
|
|
4735
|
-
)) {
|
|
4755
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4736
4756
|
smallestIdx = leftIdx;
|
|
4737
4757
|
}
|
|
4738
4758
|
if (rightIdx < size && this._less(
|
|
4739
4759
|
this._data[rightIdx],
|
|
4740
|
-
this._data[
|
|
4760
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4741
4761
|
)) {
|
|
4742
4762
|
smallestIdx = rightIdx;
|
|
4743
4763
|
}
|
|
@@ -4804,8 +4824,59 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4804
4824
|
turnPenalty,
|
|
4805
4825
|
segmentPenalty
|
|
4806
4826
|
);
|
|
4807
|
-
if (path
|
|
4808
|
-
|
|
4827
|
+
if (path !== null) {
|
|
4828
|
+
const simplified = simplifyRoute(path);
|
|
4829
|
+
const filteredSet = new Set(filtered);
|
|
4830
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4831
|
+
let crossesExcluded = false;
|
|
4832
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4833
|
+
const a = simplified[i];
|
|
4834
|
+
const b = simplified[i + 1];
|
|
4835
|
+
for (const obs of obstacles) {
|
|
4836
|
+
if (filteredSet.has(obs)) continue;
|
|
4837
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4838
|
+
crossesExcluded = true;
|
|
4839
|
+
break;
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
if (crossesExcluded) break;
|
|
4843
|
+
}
|
|
4844
|
+
if (!crossesExcluded) return simplified;
|
|
4845
|
+
} else {
|
|
4846
|
+
return simplified;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
if (!useCorridor) return null;
|
|
4850
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4851
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4852
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4853
|
+
diagnostics?.push({
|
|
4854
|
+
severity: "warning",
|
|
4855
|
+
code: "routing.astar.grid_overflow",
|
|
4856
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4857
|
+
detail: { xsCount: xsFull.length, ysCount: ysFull.length, maxNodes }
|
|
4858
|
+
});
|
|
4859
|
+
return null;
|
|
4860
|
+
}
|
|
4861
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4862
|
+
connectHorizontalEdges(
|
|
4863
|
+
nodesFull,
|
|
4864
|
+
ysFull,
|
|
4865
|
+
obstacles,
|
|
4866
|
+
endpointObstacles,
|
|
4867
|
+
margin
|
|
4868
|
+
);
|
|
4869
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4870
|
+
const pathFull = aStarSearch(
|
|
4871
|
+
nodesFull,
|
|
4872
|
+
idxFull,
|
|
4873
|
+
source,
|
|
4874
|
+
target,
|
|
4875
|
+
turnPenalty,
|
|
4876
|
+
segmentPenalty
|
|
4877
|
+
);
|
|
4878
|
+
if (pathFull === null) return null;
|
|
4879
|
+
return simplifyRoute(pathFull);
|
|
4809
4880
|
}
|
|
4810
4881
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4811
4882
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -5032,7 +5103,7 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
5032
5103
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
5033
5104
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
5034
5105
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
5035
|
-
const maxCorners = options.maxCorners ??
|
|
5106
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
5036
5107
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
5037
5108
|
if (vertices.length > maxCorners) {
|
|
5038
5109
|
diagnostics?.push({
|
|
@@ -5313,7 +5384,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
5313
5384
|
}
|
|
5314
5385
|
|
|
5315
5386
|
// src/routing/routes.ts
|
|
5316
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
5387
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
5317
5388
|
if (points.length < 2) return;
|
|
5318
5389
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
5319
5390
|
if (direct <= 0) return;
|
|
@@ -5323,7 +5394,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
5323
5394
|
const b = points[i + 1];
|
|
5324
5395
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
5325
5396
|
}
|
|
5326
|
-
const threshold =
|
|
5397
|
+
const threshold = maxRatio ?? 20;
|
|
5327
5398
|
if (routeLen > direct * threshold) {
|
|
5328
5399
|
diagnostics.push({
|
|
5329
5400
|
severity: "warning",
|
|
@@ -5341,8 +5412,20 @@ function routeEdge(input) {
|
|
|
5341
5412
|
const diagnostics = [];
|
|
5342
5413
|
const softObstacles = input.obstacles ?? [];
|
|
5343
5414
|
const hardObstacles = input.hardObstacles ?? [];
|
|
5415
|
+
let bestRejectedPath;
|
|
5416
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
5344
5417
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
5345
5418
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
5419
|
+
const recordRejected = (candidate) => {
|
|
5420
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
5424
|
+
if (crossings < bestRejectedCrossings) {
|
|
5425
|
+
bestRejectedCrossings = crossings;
|
|
5426
|
+
bestRejectedPath = candidate;
|
|
5427
|
+
}
|
|
5428
|
+
};
|
|
5346
5429
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
5347
5430
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
5348
5431
|
input.source.box,
|
|
@@ -5401,18 +5484,38 @@ function routeEdge(input) {
|
|
|
5401
5484
|
input.source.center,
|
|
5402
5485
|
targetAnchor
|
|
5403
5486
|
);
|
|
5404
|
-
const
|
|
5487
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
5488
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
5489
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
5490
|
+
source,
|
|
5491
|
+
target,
|
|
5492
|
+
allObstacles,
|
|
5493
|
+
[],
|
|
5494
|
+
// endpointObstacles passed separately via options
|
|
5495
|
+
corridorMargin
|
|
5496
|
+
);
|
|
5497
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
5498
|
+
let cornerPath = findCornerGraphPath(
|
|
5405
5499
|
source,
|
|
5406
5500
|
target,
|
|
5407
|
-
|
|
5501
|
+
cornerObstacles,
|
|
5408
5502
|
{ endpointObstacles, margin: 2 },
|
|
5409
5503
|
diagnostics
|
|
5410
5504
|
);
|
|
5505
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
5506
|
+
cornerPath = findCornerGraphPath(
|
|
5507
|
+
source,
|
|
5508
|
+
target,
|
|
5509
|
+
allObstacles,
|
|
5510
|
+
{ endpointObstacles, margin: 2 },
|
|
5511
|
+
diagnostics
|
|
5512
|
+
);
|
|
5513
|
+
}
|
|
5411
5514
|
const path = cornerPath ?? findObstacleFreePath(
|
|
5412
5515
|
source,
|
|
5413
5516
|
target,
|
|
5414
|
-
|
|
5415
|
-
{ endpointObstacles, margin: 0 },
|
|
5517
|
+
allObstacles,
|
|
5518
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
5416
5519
|
diagnostics
|
|
5417
5520
|
);
|
|
5418
5521
|
if (path !== null && path.length >= 2) {
|
|
@@ -5429,9 +5532,90 @@ function routeEdge(input) {
|
|
|
5429
5532
|
softObstacles,
|
|
5430
5533
|
softObstacleIndex
|
|
5431
5534
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
5432
|
-
checkBacktracking(
|
|
5535
|
+
checkBacktracking(
|
|
5536
|
+
finalized,
|
|
5537
|
+
source,
|
|
5538
|
+
target,
|
|
5539
|
+
diagnostics,
|
|
5540
|
+
input.maxBacktrackingRatio
|
|
5541
|
+
);
|
|
5433
5542
|
return { points: finalized, diagnostics };
|
|
5434
5543
|
}
|
|
5544
|
+
recordRejected(finalized);
|
|
5545
|
+
if (cornerPath !== null) {
|
|
5546
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
5547
|
+
source,
|
|
5548
|
+
target,
|
|
5549
|
+
allObstacles,
|
|
5550
|
+
{ endpointObstacles, margin: 2 },
|
|
5551
|
+
diagnostics
|
|
5552
|
+
) : null;
|
|
5553
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5554
|
+
const fullFinalized = finalizeRoute(
|
|
5555
|
+
fullCornerPath,
|
|
5556
|
+
softObstacles,
|
|
5557
|
+
hardObstacles,
|
|
5558
|
+
diagnostics,
|
|
5559
|
+
softObstacleIndex,
|
|
5560
|
+
hardObstacleIndex
|
|
5561
|
+
);
|
|
5562
|
+
if (!routeIntersectsObstacles(
|
|
5563
|
+
fullFinalized,
|
|
5564
|
+
softObstacles,
|
|
5565
|
+
softObstacleIndex
|
|
5566
|
+
) && !routeIntersectsObstacles(
|
|
5567
|
+
fullFinalized,
|
|
5568
|
+
hardObstacles,
|
|
5569
|
+
hardObstacleIndex
|
|
5570
|
+
)) {
|
|
5571
|
+
checkBacktracking(
|
|
5572
|
+
fullFinalized,
|
|
5573
|
+
source,
|
|
5574
|
+
target,
|
|
5575
|
+
diagnostics,
|
|
5576
|
+
input.maxBacktrackingRatio
|
|
5577
|
+
);
|
|
5578
|
+
return { points: fullFinalized, diagnostics };
|
|
5579
|
+
}
|
|
5580
|
+
recordRejected(fullFinalized);
|
|
5581
|
+
}
|
|
5582
|
+
const gridPath = findObstacleFreePath(
|
|
5583
|
+
source,
|
|
5584
|
+
target,
|
|
5585
|
+
allObstacles,
|
|
5586
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
5587
|
+
diagnostics
|
|
5588
|
+
);
|
|
5589
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5590
|
+
const gridFinalized = finalizeRoute(
|
|
5591
|
+
gridPath,
|
|
5592
|
+
softObstacles,
|
|
5593
|
+
hardObstacles,
|
|
5594
|
+
diagnostics,
|
|
5595
|
+
softObstacleIndex,
|
|
5596
|
+
hardObstacleIndex
|
|
5597
|
+
);
|
|
5598
|
+
if (!routeIntersectsObstacles(
|
|
5599
|
+
gridFinalized,
|
|
5600
|
+
softObstacles,
|
|
5601
|
+
softObstacleIndex
|
|
5602
|
+
) && !routeIntersectsObstacles(
|
|
5603
|
+
gridFinalized,
|
|
5604
|
+
hardObstacles,
|
|
5605
|
+
hardObstacleIndex
|
|
5606
|
+
)) {
|
|
5607
|
+
checkBacktracking(
|
|
5608
|
+
gridFinalized,
|
|
5609
|
+
source,
|
|
5610
|
+
target,
|
|
5611
|
+
diagnostics,
|
|
5612
|
+
input.maxBacktrackingRatio
|
|
5613
|
+
);
|
|
5614
|
+
return { points: gridFinalized, diagnostics };
|
|
5615
|
+
}
|
|
5616
|
+
recordRejected(gridFinalized);
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5435
5619
|
}
|
|
5436
5620
|
}
|
|
5437
5621
|
}
|
|
@@ -5493,7 +5677,8 @@ function routeEdge(input) {
|
|
|
5493
5677
|
finalizedClean,
|
|
5494
5678
|
candidate.points[0],
|
|
5495
5679
|
candidate.points[candidate.points.length - 1],
|
|
5496
|
-
diagnostics
|
|
5680
|
+
diagnostics,
|
|
5681
|
+
input.maxBacktrackingRatio
|
|
5497
5682
|
);
|
|
5498
5683
|
return { points: finalizedClean, diagnostics };
|
|
5499
5684
|
}
|
|
@@ -5559,13 +5744,41 @@ function routeEdge(input) {
|
|
|
5559
5744
|
code: "routing.obstacle.unavoidable",
|
|
5560
5745
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5561
5746
|
});
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5747
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5748
|
+
bestPoints2,
|
|
5749
|
+
softObstacles,
|
|
5750
|
+
hardObstacles,
|
|
5751
|
+
diagnostics
|
|
5752
|
+
);
|
|
5753
|
+
let softFallback = finalizedSoftBest;
|
|
5754
|
+
if (bestRejectedPath !== void 0) {
|
|
5755
|
+
const finalizedRejected = finalizeRoute(
|
|
5756
|
+
bestRejectedPath,
|
|
5565
5757
|
softObstacles,
|
|
5566
5758
|
hardObstacles,
|
|
5567
5759
|
diagnostics
|
|
5568
|
-
)
|
|
5760
|
+
);
|
|
5761
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5762
|
+
finalizedRejected,
|
|
5763
|
+
softObstacles
|
|
5764
|
+
);
|
|
5765
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5766
|
+
finalizedSoftBest,
|
|
5767
|
+
softObstacles
|
|
5768
|
+
);
|
|
5769
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5770
|
+
softFallback = finalizedRejected;
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
checkBacktracking(
|
|
5774
|
+
softFallback,
|
|
5775
|
+
softFallback[0],
|
|
5776
|
+
softFallback[softFallback.length - 1],
|
|
5777
|
+
diagnostics,
|
|
5778
|
+
input.maxBacktrackingRatio
|
|
5779
|
+
);
|
|
5780
|
+
return {
|
|
5781
|
+
points: softFallback,
|
|
5569
5782
|
diagnostics
|
|
5570
5783
|
};
|
|
5571
5784
|
}
|
|
@@ -5597,6 +5810,22 @@ function routeEdge(input) {
|
|
|
5597
5810
|
maxAttempts
|
|
5598
5811
|
);
|
|
5599
5812
|
}
|
|
5813
|
+
if (bestRejectedPath !== void 0) {
|
|
5814
|
+
diagnostics.push({
|
|
5815
|
+
severity: "warning",
|
|
5816
|
+
code: "routing.obstacle.unavoidable",
|
|
5817
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5818
|
+
});
|
|
5819
|
+
return {
|
|
5820
|
+
points: finalizeRoute(
|
|
5821
|
+
bestRejectedPath,
|
|
5822
|
+
softObstacles,
|
|
5823
|
+
hardObstacles,
|
|
5824
|
+
diagnostics
|
|
5825
|
+
),
|
|
5826
|
+
diagnostics
|
|
5827
|
+
};
|
|
5828
|
+
}
|
|
5600
5829
|
diagnostics.push({
|
|
5601
5830
|
severity: "error",
|
|
5602
5831
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5644,13 +5873,41 @@ function routeEdge(input) {
|
|
|
5644
5873
|
code: "routing.obstacle.unavoidable",
|
|
5645
5874
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5646
5875
|
});
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5876
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5877
|
+
bestPoints,
|
|
5878
|
+
softObstacles,
|
|
5879
|
+
hardObstacles,
|
|
5880
|
+
diagnostics
|
|
5881
|
+
);
|
|
5882
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5883
|
+
if (bestRejectedPath !== void 0) {
|
|
5884
|
+
const finalizedRejected = finalizeRoute(
|
|
5885
|
+
bestRejectedPath,
|
|
5650
5886
|
softObstacles,
|
|
5651
5887
|
hardObstacles,
|
|
5652
5888
|
diagnostics
|
|
5653
|
-
)
|
|
5889
|
+
);
|
|
5890
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5891
|
+
finalizedRejected,
|
|
5892
|
+
softObstacles
|
|
5893
|
+
);
|
|
5894
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5895
|
+
finalizedBestPoints,
|
|
5896
|
+
softObstacles
|
|
5897
|
+
);
|
|
5898
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5899
|
+
fallbackPoints = finalizedRejected;
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
checkBacktracking(
|
|
5903
|
+
fallbackPoints,
|
|
5904
|
+
fallbackPoints[0],
|
|
5905
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5906
|
+
diagnostics,
|
|
5907
|
+
input.maxBacktrackingRatio
|
|
5908
|
+
);
|
|
5909
|
+
return {
|
|
5910
|
+
points: fallbackPoints,
|
|
5654
5911
|
diagnostics
|
|
5655
5912
|
};
|
|
5656
5913
|
}
|
|
@@ -6149,6 +6406,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
6149
6406
|
}
|
|
6150
6407
|
return false;
|
|
6151
6408
|
}
|
|
6409
|
+
function countObstacleCrossings(points, obstacles) {
|
|
6410
|
+
let count = 0;
|
|
6411
|
+
for (const obstacle of obstacles) {
|
|
6412
|
+
validateBox(obstacle);
|
|
6413
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
6414
|
+
const a = points[pointIndex];
|
|
6415
|
+
const b = points[pointIndex + 1];
|
|
6416
|
+
if (a === void 0 || b === void 0) {
|
|
6417
|
+
continue;
|
|
6418
|
+
}
|
|
6419
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
6420
|
+
count += 1;
|
|
6421
|
+
break;
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
return count;
|
|
6426
|
+
}
|
|
6152
6427
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
6153
6428
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
6154
6429
|
const a = points[index];
|
|
@@ -6532,7 +6807,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6532
6807
|
edges: styledEdges
|
|
6533
6808
|
});
|
|
6534
6809
|
diagnostics.push(...layout2.diagnostics);
|
|
6535
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6810
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6536
6811
|
layout2.boxes,
|
|
6537
6812
|
styledNodes,
|
|
6538
6813
|
styledEdges,
|
|
@@ -6540,7 +6815,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6540
6815
|
options,
|
|
6541
6816
|
diagnostics
|
|
6542
6817
|
);
|
|
6543
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6818
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6819
|
+
const diagCountBefore = diagnostics.length;
|
|
6544
6820
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
6545
6821
|
initialNodeBoxes,
|
|
6546
6822
|
styledNodes,
|
|
@@ -6551,6 +6827,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6551
6827
|
for (const [id, box] of rewrapped) {
|
|
6552
6828
|
initialNodeBoxes.set(id, box);
|
|
6553
6829
|
}
|
|
6830
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6831
|
+
for (const node of styledNodes) {
|
|
6832
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6833
|
+
const rwBox = rewrapped.get(node.id);
|
|
6834
|
+
const idx = styledNodes.indexOf(node);
|
|
6835
|
+
if (idx !== -1) {
|
|
6836
|
+
styledNodes[idx] = {
|
|
6837
|
+
...node,
|
|
6838
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6839
|
+
};
|
|
6840
|
+
}
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6554
6844
|
}
|
|
6555
6845
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
6556
6846
|
const recursiveLayout = layout2;
|
|
@@ -6564,7 +6854,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6564
6854
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
6565
6855
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
6566
6856
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
6567
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6857
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
6568
6858
|
swimlanes: styledSwimlanes,
|
|
6569
6859
|
boxes: initialNodeBoxes,
|
|
6570
6860
|
nodes: styledNodes,
|
|
@@ -6579,7 +6869,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6579
6869
|
constrained.boxes,
|
|
6580
6870
|
constrained.locks,
|
|
6581
6871
|
options?.overlapSpacing ?? 40,
|
|
6582
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6872
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6873
|
+
options.distributeContainedChildren ?? true
|
|
6583
6874
|
);
|
|
6584
6875
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
6585
6876
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6746,7 +7037,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6746
7037
|
diagram.direction,
|
|
6747
7038
|
options,
|
|
6748
7039
|
diagnostics,
|
|
6749
|
-
coordinatedGroups
|
|
7040
|
+
coordinatedGroups,
|
|
7041
|
+
contentBounds
|
|
6750
7042
|
);
|
|
6751
7043
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6752
7044
|
coordinatedEdges,
|
|
@@ -7168,7 +7460,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
7168
7460
|
function containsCjk(value) {
|
|
7169
7461
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
7170
7462
|
}
|
|
7171
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
7463
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
7172
7464
|
const layouts = /* @__PURE__ */ new Map();
|
|
7173
7465
|
const diagnostics = [];
|
|
7174
7466
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -7187,7 +7479,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
7187
7479
|
locks,
|
|
7188
7480
|
diagnostics,
|
|
7189
7481
|
movedChildIds,
|
|
7190
|
-
laneGutter
|
|
7482
|
+
laneGutter,
|
|
7483
|
+
constraints,
|
|
7484
|
+
distributeContainedChildren
|
|
7191
7485
|
);
|
|
7192
7486
|
if (layout2 !== void 0) {
|
|
7193
7487
|
layouts.set(swimlane.id, layout2);
|
|
@@ -7283,9 +7577,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
7283
7577
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
7284
7578
|
return new Map(boxes);
|
|
7285
7579
|
}
|
|
7286
|
-
|
|
7287
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
7288
|
-
|
|
7580
|
+
let maxRowDepth = options.maxRowDepth;
|
|
7581
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
7582
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
7583
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
7584
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
7585
|
+
} else {
|
|
7586
|
+
return new Map(boxes);
|
|
7587
|
+
}
|
|
7289
7588
|
}
|
|
7290
7589
|
const ordered = [...nodes].sort((a, b) => {
|
|
7291
7590
|
const ba = boxes.get(a.id);
|
|
@@ -7346,10 +7645,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
7346
7645
|
});
|
|
7347
7646
|
}
|
|
7348
7647
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
7349
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
7648
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
7350
7649
|
return false;
|
|
7351
7650
|
}
|
|
7352
|
-
if (nodes.length < 2
|
|
7651
|
+
if (nodes.length < 2) {
|
|
7353
7652
|
return false;
|
|
7354
7653
|
}
|
|
7355
7654
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -7357,17 +7656,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
7357
7656
|
return false;
|
|
7358
7657
|
}
|
|
7359
7658
|
const bounds = unionBoxes(nodeBoxes);
|
|
7360
|
-
const
|
|
7361
|
-
const
|
|
7362
|
-
|
|
7659
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
7660
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
7661
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
7662
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
7363
7663
|
return false;
|
|
7364
7664
|
}
|
|
7665
|
+
if (isHorizontal) {
|
|
7666
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
7667
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
7668
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
7669
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
7670
|
+
}
|
|
7365
7671
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
7366
7672
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
7367
7673
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
7368
7674
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
7369
7675
|
}
|
|
7370
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7676
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7371
7677
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
7372
7678
|
const padding = swimlane.padding ?? 16;
|
|
7373
7679
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -7392,7 +7698,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7392
7698
|
locks,
|
|
7393
7699
|
diagnostics,
|
|
7394
7700
|
movedChildIds,
|
|
7395
|
-
laneGutter
|
|
7701
|
+
laneGutter,
|
|
7702
|
+
constraints,
|
|
7703
|
+
distributeContainedChildren
|
|
7396
7704
|
);
|
|
7397
7705
|
}
|
|
7398
7706
|
return applyHorizontalSwimlaneContract(
|
|
@@ -7407,13 +7715,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7407
7715
|
laneGutter
|
|
7408
7716
|
);
|
|
7409
7717
|
}
|
|
7410
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7718
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7411
7719
|
const populatedBounds = laneBounds.filter(
|
|
7412
7720
|
(box) => box !== void 0
|
|
7413
7721
|
);
|
|
7414
7722
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
7415
7723
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
7416
7724
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
7725
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
7726
|
+
if (distributeContainedChildren) {
|
|
7727
|
+
for (const c of constraints) {
|
|
7728
|
+
if (c.kind !== "containment") continue;
|
|
7729
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
7730
|
+
const distributable = c.childIds.filter((childId) => {
|
|
7731
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
7732
|
+
const lock = locks.get(childId);
|
|
7733
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
7734
|
+
});
|
|
7735
|
+
if (distributable.length < 2) continue;
|
|
7736
|
+
for (const childId of distributable) {
|
|
7737
|
+
containedChildIds.add(childId);
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
}
|
|
7417
7741
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
7418
7742
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
7419
7743
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -7425,7 +7749,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7425
7749
|
);
|
|
7426
7750
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
7427
7751
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
7428
|
-
const
|
|
7752
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
7753
|
+
swimlane,
|
|
7754
|
+
nodeBoxes,
|
|
7755
|
+
flowRanks,
|
|
7756
|
+
locks,
|
|
7757
|
+
rankStackGap,
|
|
7758
|
+
containedChildIds
|
|
7759
|
+
);
|
|
7760
|
+
const slotWidth = Math.max(
|
|
7761
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7762
|
+
spreadWidth
|
|
7763
|
+
) + padding * 2;
|
|
7429
7764
|
const laneStep = slotWidth + laneGutter;
|
|
7430
7765
|
const laneContentTop = top + headerHeight + padding;
|
|
7431
7766
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -7439,6 +7774,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7439
7774
|
y: laneContentTop
|
|
7440
7775
|
};
|
|
7441
7776
|
if (maxRank === 0) {
|
|
7777
|
+
const distributable = lane.children.filter(
|
|
7778
|
+
(childId) => !locks.has(childId)
|
|
7779
|
+
);
|
|
7780
|
+
const coveredByContainment = lane.children.some(
|
|
7781
|
+
(childId) => containedChildIds.has(childId)
|
|
7782
|
+
);
|
|
7783
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7784
|
+
moveRankedVerticalLaneChildren(
|
|
7785
|
+
lane.children,
|
|
7786
|
+
nodeBoxes,
|
|
7787
|
+
locks,
|
|
7788
|
+
diagnostics,
|
|
7789
|
+
movedChildIds,
|
|
7790
|
+
flowRanks,
|
|
7791
|
+
rankSpacing,
|
|
7792
|
+
rankStackGap,
|
|
7793
|
+
{ x: target.x, y: laneContentTop },
|
|
7794
|
+
slotWidth - padding * 2
|
|
7795
|
+
);
|
|
7796
|
+
continue;
|
|
7797
|
+
}
|
|
7442
7798
|
moveLaneChildren(
|
|
7443
7799
|
lane.children,
|
|
7444
7800
|
nodeBoxes,
|
|
@@ -7452,6 +7808,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7452
7808
|
);
|
|
7453
7809
|
continue;
|
|
7454
7810
|
}
|
|
7811
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7812
|
+
(childId) => containedChildIds.has(childId)
|
|
7813
|
+
);
|
|
7455
7814
|
moveRankedVerticalLaneChildren(
|
|
7456
7815
|
lane.children,
|
|
7457
7816
|
nodeBoxes,
|
|
@@ -7461,10 +7820,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7461
7820
|
flowRanks,
|
|
7462
7821
|
rankSpacing,
|
|
7463
7822
|
rankStackGap,
|
|
7464
|
-
{
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
}
|
|
7823
|
+
{ x: target.x, y: laneContentTop },
|
|
7824
|
+
slotWidth - padding * 2,
|
|
7825
|
+
rankedCoveredByContainment
|
|
7468
7826
|
);
|
|
7469
7827
|
}
|
|
7470
7828
|
return {
|
|
@@ -7573,31 +7931,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
7573
7931
|
}
|
|
7574
7932
|
return maxHeight;
|
|
7575
7933
|
}
|
|
7576
|
-
|
|
7934
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7935
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7936
|
+
return items.reduce(
|
|
7937
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7938
|
+
0
|
|
7939
|
+
);
|
|
7940
|
+
}
|
|
7941
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7942
|
+
let maxWidth = 0;
|
|
7943
|
+
for (const lane of swimlane.lanes) {
|
|
7944
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7945
|
+
continue;
|
|
7946
|
+
}
|
|
7947
|
+
for (const stack of rankStacks(
|
|
7948
|
+
lane.children,
|
|
7949
|
+
nodeBoxes,
|
|
7950
|
+
flowRanks
|
|
7951
|
+
).values()) {
|
|
7952
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7953
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7954
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7955
|
+
}
|
|
7956
|
+
}
|
|
7957
|
+
return maxWidth;
|
|
7958
|
+
}
|
|
7959
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
7577
7960
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
7578
|
-
|
|
7961
|
+
const unlocked = [];
|
|
7579
7962
|
for (const item of stack) {
|
|
7580
|
-
|
|
7581
|
-
if (locks.has(childId)) {
|
|
7963
|
+
if (locks.has(item.childId)) {
|
|
7582
7964
|
diagnostics.push({
|
|
7583
7965
|
severity: "warning",
|
|
7584
7966
|
code: "constraints.locked-target-not-moved",
|
|
7585
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7967
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
7586
7968
|
path: ["swimlanes"],
|
|
7587
|
-
detail: { nodeId: childId }
|
|
7969
|
+
detail: { nodeId: item.childId }
|
|
7588
7970
|
});
|
|
7589
|
-
|
|
7971
|
+
} else {
|
|
7972
|
+
unlocked.push(item);
|
|
7590
7973
|
}
|
|
7974
|
+
}
|
|
7975
|
+
if (unlocked.length === 0) continue;
|
|
7976
|
+
if (unlocked.length === 1) {
|
|
7977
|
+
const { childId, box } = unlocked[0];
|
|
7591
7978
|
const next = {
|
|
7592
7979
|
...box,
|
|
7593
|
-
x:
|
|
7594
|
-
y: target.y + rank * rankSpacing
|
|
7980
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7981
|
+
y: target.y + rank * rankSpacing
|
|
7595
7982
|
};
|
|
7596
7983
|
if (next.x !== box.x || next.y !== box.y) {
|
|
7597
7984
|
movedChildIds.add(childId);
|
|
7598
7985
|
}
|
|
7599
7986
|
nodeBoxes.set(childId, next);
|
|
7600
|
-
|
|
7987
|
+
} else {
|
|
7988
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7989
|
+
if (!shouldSpread) {
|
|
7990
|
+
let yOffset = 0;
|
|
7991
|
+
for (const { childId, box } of unlocked) {
|
|
7992
|
+
const next = {
|
|
7993
|
+
...box,
|
|
7994
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7995
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7996
|
+
};
|
|
7997
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7998
|
+
movedChildIds.add(childId);
|
|
7999
|
+
}
|
|
8000
|
+
nodeBoxes.set(childId, next);
|
|
8001
|
+
yOffset += box.height + rankStackGap;
|
|
8002
|
+
}
|
|
8003
|
+
} else {
|
|
8004
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
8005
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
8006
|
+
for (const { childId, box } of unlocked) {
|
|
8007
|
+
const next = {
|
|
8008
|
+
...box,
|
|
8009
|
+
x: xCursor,
|
|
8010
|
+
y: target.y + rank * rankSpacing
|
|
8011
|
+
};
|
|
8012
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
8013
|
+
movedChildIds.add(childId);
|
|
8014
|
+
}
|
|
8015
|
+
nodeBoxes.set(childId, next);
|
|
8016
|
+
xCursor += box.width + rankStackGap;
|
|
8017
|
+
}
|
|
8018
|
+
diagnostics.push({
|
|
8019
|
+
severity: "info",
|
|
8020
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
8021
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
8022
|
+
path: ["swimlanes"],
|
|
8023
|
+
detail: {
|
|
8024
|
+
rank,
|
|
8025
|
+
childCount: unlocked.length,
|
|
8026
|
+
contentWidth
|
|
8027
|
+
}
|
|
8028
|
+
});
|
|
8029
|
+
}
|
|
7601
8030
|
}
|
|
7602
8031
|
}
|
|
7603
8032
|
}
|
|
@@ -7936,7 +8365,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7936
8365
|
});
|
|
7937
8366
|
continue;
|
|
7938
8367
|
}
|
|
7939
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
8368
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7940
8369
|
const geometry = computeShapeGeometry({
|
|
7941
8370
|
shape: node.shape,
|
|
7942
8371
|
box,
|
|
@@ -8030,7 +8459,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
8030
8459
|
}
|
|
8031
8460
|
}
|
|
8032
8461
|
}
|
|
8033
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
8462
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
8034
8463
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
8035
8464
|
for (const port of node.ports ?? []) {
|
|
8036
8465
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -8053,9 +8482,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8053
8482
|
side,
|
|
8054
8483
|
index,
|
|
8055
8484
|
sorted.length,
|
|
8056
|
-
portShifting
|
|
8057
|
-
diagnostics,
|
|
8058
|
-
node.id
|
|
8485
|
+
portShifting
|
|
8059
8486
|
);
|
|
8060
8487
|
const box = portBox(anchor);
|
|
8061
8488
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -8063,32 +8490,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8063
8490
|
}
|
|
8064
8491
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
8065
8492
|
}
|
|
8066
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
8493
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
8067
8494
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
8068
8495
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
8069
8496
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
8070
8497
|
const availableSpan = 2 * maxOffset;
|
|
8071
8498
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
8072
|
-
const
|
|
8499
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
8073
8500
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
8074
8501
|
minSpacing
|
|
8075
8502
|
) : requestedSpacing;
|
|
8076
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
8077
|
-
diagnostics.push({
|
|
8078
|
-
severity: "warning",
|
|
8079
|
-
code: "port_constraint_overlap",
|
|
8080
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
8081
|
-
path: ["nodes", nodeId, "ports"],
|
|
8082
|
-
detail: {
|
|
8083
|
-
nodeId,
|
|
8084
|
-
side,
|
|
8085
|
-
requestedSpacing,
|
|
8086
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
8087
|
-
portCount: count
|
|
8088
|
-
}
|
|
8089
|
-
});
|
|
8090
|
-
}
|
|
8091
|
-
const spacing = effectiveSpacing;
|
|
8092
8503
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
8093
8504
|
switch (side) {
|
|
8094
8505
|
case "left":
|
|
@@ -8703,14 +9114,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8703
9114
|
}
|
|
8704
9115
|
};
|
|
8705
9116
|
}
|
|
8706
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
9117
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8707
9118
|
const coordinated = [];
|
|
8708
9119
|
const coordinatedNodeById = new Map(
|
|
8709
9120
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8710
9121
|
);
|
|
9122
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
9123
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
9124
|
+
200,
|
|
9125
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
9126
|
+
);
|
|
9127
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
9128
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8711
9129
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8712
9130
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8713
|
-
|
|
9131
|
+
queryGutter
|
|
8714
9132
|
);
|
|
8715
9133
|
for (const edge of edges) {
|
|
8716
9134
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8732,11 +9150,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8732
9150
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8733
9151
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8734
9152
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8735
|
-
const corridor = edgeCorridorBox(
|
|
8736
|
-
source.box,
|
|
8737
|
-
target.box,
|
|
8738
|
-
options.routingGutter ?? 160
|
|
8739
|
-
);
|
|
9153
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8740
9154
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8741
9155
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8742
9156
|
);
|
|
@@ -8754,7 +9168,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8754
9168
|
...routeTextObstacles
|
|
8755
9169
|
],
|
|
8756
9170
|
hardObstacles,
|
|
8757
|
-
|
|
9171
|
+
corridorMargin,
|
|
9172
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
9173
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
8758
9174
|
});
|
|
8759
9175
|
diagnostics.push(
|
|
8760
9176
|
...route.diagnostics.map((diagnostic) => ({
|