@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/cli/index.cjs
CHANGED
|
@@ -1394,6 +1394,28 @@ function applyLayoutConstraints(input) {
|
|
|
1394
1394
|
if (input.distributeContainedChildren) {
|
|
1395
1395
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
1396
1396
|
}
|
|
1397
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1398
|
+
for (const swimlane of input.swimlanes) {
|
|
1399
|
+
if (swimlane.layout === "contract") continue;
|
|
1400
|
+
for (const lane of swimlane.lanes) {
|
|
1401
|
+
const fixedChildren = [];
|
|
1402
|
+
let participantCount = 0;
|
|
1403
|
+
for (const childId of lane.children) {
|
|
1404
|
+
const lock = locks.get(childId);
|
|
1405
|
+
if (lock === void 0) {
|
|
1406
|
+
participantCount += 1;
|
|
1407
|
+
} else if (lock.source === "fixed-position") {
|
|
1408
|
+
participantCount += 1;
|
|
1409
|
+
fixedChildren.push(childId);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (participantCount < 2) continue;
|
|
1413
|
+
for (const childId of fixedChildren) {
|
|
1414
|
+
locks.delete(childId);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1397
1419
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
1398
1420
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
1399
1421
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -1413,6 +1435,9 @@ function applyLayoutConstraints(input) {
|
|
|
1413
1435
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1414
1436
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
1415
1437
|
}
|
|
1438
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1439
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1440
|
+
}
|
|
1416
1441
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
1417
1442
|
reportOverlaps(
|
|
1418
1443
|
boxes,
|
|
@@ -2252,9 +2277,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
2252
2277
|
}
|
|
2253
2278
|
});
|
|
2254
2279
|
}
|
|
2255
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
2256
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
2257
|
-
}
|
|
2258
2280
|
}
|
|
2259
2281
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
2260
2282
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -2294,6 +2316,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
2294
2316
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
2295
2317
|
}
|
|
2296
2318
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
2319
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
2297
2320
|
let pos = contentStart;
|
|
2298
2321
|
for (const child of unlocked) {
|
|
2299
2322
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4085,15 +4108,12 @@ var BinaryHeap = class {
|
|
|
4085
4108
|
let smallestIdx = idx;
|
|
4086
4109
|
const leftIdx = (idx << 1) + 1;
|
|
4087
4110
|
const rightIdx = leftIdx + 1;
|
|
4088
|
-
if (leftIdx < size && this._less(
|
|
4089
|
-
this._data[leftIdx],
|
|
4090
|
-
this._data[smallestIdx]
|
|
4091
|
-
)) {
|
|
4111
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4092
4112
|
smallestIdx = leftIdx;
|
|
4093
4113
|
}
|
|
4094
4114
|
if (rightIdx < size && this._less(
|
|
4095
4115
|
this._data[rightIdx],
|
|
4096
|
-
this._data[
|
|
4116
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4097
4117
|
)) {
|
|
4098
4118
|
smallestIdx = rightIdx;
|
|
4099
4119
|
}
|
|
@@ -4160,8 +4180,59 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4160
4180
|
turnPenalty,
|
|
4161
4181
|
segmentPenalty
|
|
4162
4182
|
);
|
|
4163
|
-
if (path
|
|
4164
|
-
|
|
4183
|
+
if (path !== null) {
|
|
4184
|
+
const simplified = simplifyRoute(path);
|
|
4185
|
+
const filteredSet = new Set(filtered);
|
|
4186
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4187
|
+
let crossesExcluded = false;
|
|
4188
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4189
|
+
const a = simplified[i];
|
|
4190
|
+
const b = simplified[i + 1];
|
|
4191
|
+
for (const obs of obstacles) {
|
|
4192
|
+
if (filteredSet.has(obs)) continue;
|
|
4193
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4194
|
+
crossesExcluded = true;
|
|
4195
|
+
break;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
if (crossesExcluded) break;
|
|
4199
|
+
}
|
|
4200
|
+
if (!crossesExcluded) return simplified;
|
|
4201
|
+
} else {
|
|
4202
|
+
return simplified;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (!useCorridor) return null;
|
|
4206
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4207
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4208
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4209
|
+
diagnostics?.push({
|
|
4210
|
+
severity: "warning",
|
|
4211
|
+
code: "routing.astar.grid_overflow",
|
|
4212
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4213
|
+
detail: { xsCount: xsFull.length, ysCount: ysFull.length, maxNodes }
|
|
4214
|
+
});
|
|
4215
|
+
return null;
|
|
4216
|
+
}
|
|
4217
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4218
|
+
connectHorizontalEdges(
|
|
4219
|
+
nodesFull,
|
|
4220
|
+
ysFull,
|
|
4221
|
+
obstacles,
|
|
4222
|
+
endpointObstacles,
|
|
4223
|
+
margin
|
|
4224
|
+
);
|
|
4225
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4226
|
+
const pathFull = aStarSearch(
|
|
4227
|
+
nodesFull,
|
|
4228
|
+
idxFull,
|
|
4229
|
+
source,
|
|
4230
|
+
target,
|
|
4231
|
+
turnPenalty,
|
|
4232
|
+
segmentPenalty
|
|
4233
|
+
);
|
|
4234
|
+
if (pathFull === null) return null;
|
|
4235
|
+
return simplifyRoute(pathFull);
|
|
4165
4236
|
}
|
|
4166
4237
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4167
4238
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -4388,7 +4459,7 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
4388
4459
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
4389
4460
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
4390
4461
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
4391
|
-
const maxCorners = options.maxCorners ??
|
|
4462
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
4392
4463
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
4393
4464
|
if (vertices.length > maxCorners) {
|
|
4394
4465
|
diagnostics?.push({
|
|
@@ -4669,7 +4740,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
4669
4740
|
}
|
|
4670
4741
|
|
|
4671
4742
|
// src/routing/routes.ts
|
|
4672
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
4743
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
4673
4744
|
if (points.length < 2) return;
|
|
4674
4745
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
4675
4746
|
if (direct <= 0) return;
|
|
@@ -4679,7 +4750,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
4679
4750
|
const b = points[i + 1];
|
|
4680
4751
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
4681
4752
|
}
|
|
4682
|
-
const threshold =
|
|
4753
|
+
const threshold = maxRatio ?? 20;
|
|
4683
4754
|
if (routeLen > direct * threshold) {
|
|
4684
4755
|
diagnostics.push({
|
|
4685
4756
|
severity: "warning",
|
|
@@ -4697,8 +4768,20 @@ function routeEdge(input) {
|
|
|
4697
4768
|
const diagnostics = [];
|
|
4698
4769
|
const softObstacles = input.obstacles ?? [];
|
|
4699
4770
|
const hardObstacles = input.hardObstacles ?? [];
|
|
4771
|
+
let bestRejectedPath;
|
|
4772
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
4700
4773
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
4701
4774
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
4775
|
+
const recordRejected = (candidate) => {
|
|
4776
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
4777
|
+
return;
|
|
4778
|
+
}
|
|
4779
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
4780
|
+
if (crossings < bestRejectedCrossings) {
|
|
4781
|
+
bestRejectedCrossings = crossings;
|
|
4782
|
+
bestRejectedPath = candidate;
|
|
4783
|
+
}
|
|
4784
|
+
};
|
|
4702
4785
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
4703
4786
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
4704
4787
|
input.source.box,
|
|
@@ -4757,18 +4840,38 @@ function routeEdge(input) {
|
|
|
4757
4840
|
input.source.center,
|
|
4758
4841
|
targetAnchor
|
|
4759
4842
|
);
|
|
4760
|
-
const
|
|
4843
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4844
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
4845
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
4846
|
+
source,
|
|
4847
|
+
target,
|
|
4848
|
+
allObstacles,
|
|
4849
|
+
[],
|
|
4850
|
+
// endpointObstacles passed separately via options
|
|
4851
|
+
corridorMargin
|
|
4852
|
+
);
|
|
4853
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4854
|
+
let cornerPath = findCornerGraphPath(
|
|
4761
4855
|
source,
|
|
4762
4856
|
target,
|
|
4763
|
-
|
|
4857
|
+
cornerObstacles,
|
|
4764
4858
|
{ endpointObstacles, margin: 2 },
|
|
4765
4859
|
diagnostics
|
|
4766
4860
|
);
|
|
4861
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
4862
|
+
cornerPath = findCornerGraphPath(
|
|
4863
|
+
source,
|
|
4864
|
+
target,
|
|
4865
|
+
allObstacles,
|
|
4866
|
+
{ endpointObstacles, margin: 2 },
|
|
4867
|
+
diagnostics
|
|
4868
|
+
);
|
|
4869
|
+
}
|
|
4767
4870
|
const path = cornerPath ?? findObstacleFreePath(
|
|
4768
4871
|
source,
|
|
4769
4872
|
target,
|
|
4770
|
-
|
|
4771
|
-
{ endpointObstacles, margin: 0 },
|
|
4873
|
+
allObstacles,
|
|
4874
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
4772
4875
|
diagnostics
|
|
4773
4876
|
);
|
|
4774
4877
|
if (path !== null && path.length >= 2) {
|
|
@@ -4785,9 +4888,90 @@ function routeEdge(input) {
|
|
|
4785
4888
|
softObstacles,
|
|
4786
4889
|
softObstacleIndex
|
|
4787
4890
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
4788
|
-
checkBacktracking(
|
|
4891
|
+
checkBacktracking(
|
|
4892
|
+
finalized,
|
|
4893
|
+
source,
|
|
4894
|
+
target,
|
|
4895
|
+
diagnostics,
|
|
4896
|
+
input.maxBacktrackingRatio
|
|
4897
|
+
);
|
|
4789
4898
|
return { points: finalized, diagnostics };
|
|
4790
4899
|
}
|
|
4900
|
+
recordRejected(finalized);
|
|
4901
|
+
if (cornerPath !== null) {
|
|
4902
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4903
|
+
source,
|
|
4904
|
+
target,
|
|
4905
|
+
allObstacles,
|
|
4906
|
+
{ endpointObstacles, margin: 2 },
|
|
4907
|
+
diagnostics
|
|
4908
|
+
) : null;
|
|
4909
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
4910
|
+
const fullFinalized = finalizeRoute(
|
|
4911
|
+
fullCornerPath,
|
|
4912
|
+
softObstacles,
|
|
4913
|
+
hardObstacles,
|
|
4914
|
+
diagnostics,
|
|
4915
|
+
softObstacleIndex,
|
|
4916
|
+
hardObstacleIndex
|
|
4917
|
+
);
|
|
4918
|
+
if (!routeIntersectsObstacles(
|
|
4919
|
+
fullFinalized,
|
|
4920
|
+
softObstacles,
|
|
4921
|
+
softObstacleIndex
|
|
4922
|
+
) && !routeIntersectsObstacles(
|
|
4923
|
+
fullFinalized,
|
|
4924
|
+
hardObstacles,
|
|
4925
|
+
hardObstacleIndex
|
|
4926
|
+
)) {
|
|
4927
|
+
checkBacktracking(
|
|
4928
|
+
fullFinalized,
|
|
4929
|
+
source,
|
|
4930
|
+
target,
|
|
4931
|
+
diagnostics,
|
|
4932
|
+
input.maxBacktrackingRatio
|
|
4933
|
+
);
|
|
4934
|
+
return { points: fullFinalized, diagnostics };
|
|
4935
|
+
}
|
|
4936
|
+
recordRejected(fullFinalized);
|
|
4937
|
+
}
|
|
4938
|
+
const gridPath = findObstacleFreePath(
|
|
4939
|
+
source,
|
|
4940
|
+
target,
|
|
4941
|
+
allObstacles,
|
|
4942
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
4943
|
+
diagnostics
|
|
4944
|
+
);
|
|
4945
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
4946
|
+
const gridFinalized = finalizeRoute(
|
|
4947
|
+
gridPath,
|
|
4948
|
+
softObstacles,
|
|
4949
|
+
hardObstacles,
|
|
4950
|
+
diagnostics,
|
|
4951
|
+
softObstacleIndex,
|
|
4952
|
+
hardObstacleIndex
|
|
4953
|
+
);
|
|
4954
|
+
if (!routeIntersectsObstacles(
|
|
4955
|
+
gridFinalized,
|
|
4956
|
+
softObstacles,
|
|
4957
|
+
softObstacleIndex
|
|
4958
|
+
) && !routeIntersectsObstacles(
|
|
4959
|
+
gridFinalized,
|
|
4960
|
+
hardObstacles,
|
|
4961
|
+
hardObstacleIndex
|
|
4962
|
+
)) {
|
|
4963
|
+
checkBacktracking(
|
|
4964
|
+
gridFinalized,
|
|
4965
|
+
source,
|
|
4966
|
+
target,
|
|
4967
|
+
diagnostics,
|
|
4968
|
+
input.maxBacktrackingRatio
|
|
4969
|
+
);
|
|
4970
|
+
return { points: gridFinalized, diagnostics };
|
|
4971
|
+
}
|
|
4972
|
+
recordRejected(gridFinalized);
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4791
4975
|
}
|
|
4792
4976
|
}
|
|
4793
4977
|
}
|
|
@@ -4849,7 +5033,8 @@ function routeEdge(input) {
|
|
|
4849
5033
|
finalizedClean,
|
|
4850
5034
|
candidate.points[0],
|
|
4851
5035
|
candidate.points[candidate.points.length - 1],
|
|
4852
|
-
diagnostics
|
|
5036
|
+
diagnostics,
|
|
5037
|
+
input.maxBacktrackingRatio
|
|
4853
5038
|
);
|
|
4854
5039
|
return { points: finalizedClean, diagnostics };
|
|
4855
5040
|
}
|
|
@@ -4915,13 +5100,41 @@ function routeEdge(input) {
|
|
|
4915
5100
|
code: "routing.obstacle.unavoidable",
|
|
4916
5101
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
4917
5102
|
});
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
5103
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5104
|
+
bestPoints2,
|
|
5105
|
+
softObstacles,
|
|
5106
|
+
hardObstacles,
|
|
5107
|
+
diagnostics
|
|
5108
|
+
);
|
|
5109
|
+
let softFallback = finalizedSoftBest;
|
|
5110
|
+
if (bestRejectedPath !== void 0) {
|
|
5111
|
+
const finalizedRejected = finalizeRoute(
|
|
5112
|
+
bestRejectedPath,
|
|
4921
5113
|
softObstacles,
|
|
4922
5114
|
hardObstacles,
|
|
4923
5115
|
diagnostics
|
|
4924
|
-
)
|
|
5116
|
+
);
|
|
5117
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5118
|
+
finalizedRejected,
|
|
5119
|
+
softObstacles
|
|
5120
|
+
);
|
|
5121
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5122
|
+
finalizedSoftBest,
|
|
5123
|
+
softObstacles
|
|
5124
|
+
);
|
|
5125
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5126
|
+
softFallback = finalizedRejected;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
checkBacktracking(
|
|
5130
|
+
softFallback,
|
|
5131
|
+
softFallback[0],
|
|
5132
|
+
softFallback[softFallback.length - 1],
|
|
5133
|
+
diagnostics,
|
|
5134
|
+
input.maxBacktrackingRatio
|
|
5135
|
+
);
|
|
5136
|
+
return {
|
|
5137
|
+
points: softFallback,
|
|
4925
5138
|
diagnostics
|
|
4926
5139
|
};
|
|
4927
5140
|
}
|
|
@@ -4953,6 +5166,22 @@ function routeEdge(input) {
|
|
|
4953
5166
|
maxAttempts
|
|
4954
5167
|
);
|
|
4955
5168
|
}
|
|
5169
|
+
if (bestRejectedPath !== void 0) {
|
|
5170
|
+
diagnostics.push({
|
|
5171
|
+
severity: "warning",
|
|
5172
|
+
code: "routing.obstacle.unavoidable",
|
|
5173
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5174
|
+
});
|
|
5175
|
+
return {
|
|
5176
|
+
points: finalizeRoute(
|
|
5177
|
+
bestRejectedPath,
|
|
5178
|
+
softObstacles,
|
|
5179
|
+
hardObstacles,
|
|
5180
|
+
diagnostics
|
|
5181
|
+
),
|
|
5182
|
+
diagnostics
|
|
5183
|
+
};
|
|
5184
|
+
}
|
|
4956
5185
|
diagnostics.push({
|
|
4957
5186
|
severity: "error",
|
|
4958
5187
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5000,13 +5229,41 @@ function routeEdge(input) {
|
|
|
5000
5229
|
code: "routing.obstacle.unavoidable",
|
|
5001
5230
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5002
5231
|
});
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5232
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5233
|
+
bestPoints,
|
|
5234
|
+
softObstacles,
|
|
5235
|
+
hardObstacles,
|
|
5236
|
+
diagnostics
|
|
5237
|
+
);
|
|
5238
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5239
|
+
if (bestRejectedPath !== void 0) {
|
|
5240
|
+
const finalizedRejected = finalizeRoute(
|
|
5241
|
+
bestRejectedPath,
|
|
5006
5242
|
softObstacles,
|
|
5007
5243
|
hardObstacles,
|
|
5008
5244
|
diagnostics
|
|
5009
|
-
)
|
|
5245
|
+
);
|
|
5246
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5247
|
+
finalizedRejected,
|
|
5248
|
+
softObstacles
|
|
5249
|
+
);
|
|
5250
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5251
|
+
finalizedBestPoints,
|
|
5252
|
+
softObstacles
|
|
5253
|
+
);
|
|
5254
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5255
|
+
fallbackPoints = finalizedRejected;
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
checkBacktracking(
|
|
5259
|
+
fallbackPoints,
|
|
5260
|
+
fallbackPoints[0],
|
|
5261
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5262
|
+
diagnostics,
|
|
5263
|
+
input.maxBacktrackingRatio
|
|
5264
|
+
);
|
|
5265
|
+
return {
|
|
5266
|
+
points: fallbackPoints,
|
|
5010
5267
|
diagnostics
|
|
5011
5268
|
};
|
|
5012
5269
|
}
|
|
@@ -5505,6 +5762,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
5505
5762
|
}
|
|
5506
5763
|
return false;
|
|
5507
5764
|
}
|
|
5765
|
+
function countObstacleCrossings(points, obstacles) {
|
|
5766
|
+
let count = 0;
|
|
5767
|
+
for (const obstacle of obstacles) {
|
|
5768
|
+
validateBox(obstacle);
|
|
5769
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
5770
|
+
const a = points[pointIndex];
|
|
5771
|
+
const b = points[pointIndex + 1];
|
|
5772
|
+
if (a === void 0 || b === void 0) {
|
|
5773
|
+
continue;
|
|
5774
|
+
}
|
|
5775
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
5776
|
+
count += 1;
|
|
5777
|
+
break;
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
return count;
|
|
5782
|
+
}
|
|
5508
5783
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
5509
5784
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5510
5785
|
const a = points[index];
|
|
@@ -5677,7 +5952,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5677
5952
|
edges: styledEdges
|
|
5678
5953
|
});
|
|
5679
5954
|
diagnostics.push(...layout2.diagnostics);
|
|
5680
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5955
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5681
5956
|
layout2.boxes,
|
|
5682
5957
|
styledNodes,
|
|
5683
5958
|
styledEdges,
|
|
@@ -5685,7 +5960,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5685
5960
|
options,
|
|
5686
5961
|
diagnostics
|
|
5687
5962
|
);
|
|
5688
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
5963
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
5964
|
+
const diagCountBefore = diagnostics.length;
|
|
5689
5965
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
5690
5966
|
initialNodeBoxes,
|
|
5691
5967
|
styledNodes,
|
|
@@ -5696,6 +5972,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5696
5972
|
for (const [id, box] of rewrapped) {
|
|
5697
5973
|
initialNodeBoxes.set(id, box);
|
|
5698
5974
|
}
|
|
5975
|
+
if (diagnostics.length > diagCountBefore) {
|
|
5976
|
+
for (const node of styledNodes) {
|
|
5977
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
5978
|
+
const rwBox = rewrapped.get(node.id);
|
|
5979
|
+
const idx = styledNodes.indexOf(node);
|
|
5980
|
+
if (idx !== -1) {
|
|
5981
|
+
styledNodes[idx] = {
|
|
5982
|
+
...node,
|
|
5983
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
5984
|
+
};
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5699
5989
|
}
|
|
5700
5990
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
5701
5991
|
const recursiveLayout = layout2;
|
|
@@ -5709,7 +5999,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5709
5999
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
5710
6000
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
5711
6001
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
5712
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6002
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
5713
6003
|
swimlanes: styledSwimlanes,
|
|
5714
6004
|
boxes: initialNodeBoxes,
|
|
5715
6005
|
nodes: styledNodes,
|
|
@@ -5724,7 +6014,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5724
6014
|
constrained.boxes,
|
|
5725
6015
|
constrained.locks,
|
|
5726
6016
|
options?.overlapSpacing ?? 40,
|
|
5727
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6017
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6018
|
+
options.distributeContainedChildren ?? true
|
|
5728
6019
|
);
|
|
5729
6020
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
5730
6021
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -5891,7 +6182,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5891
6182
|
diagram.direction,
|
|
5892
6183
|
options,
|
|
5893
6184
|
diagnostics,
|
|
5894
|
-
coordinatedGroups
|
|
6185
|
+
coordinatedGroups,
|
|
6186
|
+
contentBounds
|
|
5895
6187
|
);
|
|
5896
6188
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
5897
6189
|
coordinatedEdges,
|
|
@@ -6310,7 +6602,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
6310
6602
|
function containsCjk(value) {
|
|
6311
6603
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
6312
6604
|
}
|
|
6313
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
6605
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
6314
6606
|
const layouts = /* @__PURE__ */ new Map();
|
|
6315
6607
|
const diagnostics = [];
|
|
6316
6608
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -6329,7 +6621,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
6329
6621
|
locks,
|
|
6330
6622
|
diagnostics,
|
|
6331
6623
|
movedChildIds,
|
|
6332
|
-
laneGutter
|
|
6624
|
+
laneGutter,
|
|
6625
|
+
constraints,
|
|
6626
|
+
distributeContainedChildren
|
|
6333
6627
|
);
|
|
6334
6628
|
if (layout2 !== void 0) {
|
|
6335
6629
|
layouts.set(swimlane.id, layout2);
|
|
@@ -6425,9 +6719,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
6425
6719
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
6426
6720
|
return new Map(boxes);
|
|
6427
6721
|
}
|
|
6428
|
-
|
|
6429
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
6430
|
-
|
|
6722
|
+
let maxRowDepth = options.maxRowDepth;
|
|
6723
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
6724
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
6725
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
6726
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
6727
|
+
} else {
|
|
6728
|
+
return new Map(boxes);
|
|
6729
|
+
}
|
|
6431
6730
|
}
|
|
6432
6731
|
const ordered = [...nodes].sort((a, b) => {
|
|
6433
6732
|
const ba = boxes.get(a.id);
|
|
@@ -6488,10 +6787,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
6488
6787
|
});
|
|
6489
6788
|
}
|
|
6490
6789
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
6491
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
6790
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
6492
6791
|
return false;
|
|
6493
6792
|
}
|
|
6494
|
-
if (nodes.length < 2
|
|
6793
|
+
if (nodes.length < 2) {
|
|
6495
6794
|
return false;
|
|
6496
6795
|
}
|
|
6497
6796
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -6499,17 +6798,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6499
6798
|
return false;
|
|
6500
6799
|
}
|
|
6501
6800
|
const bounds = unionBoxes(nodeBoxes);
|
|
6502
|
-
const
|
|
6503
|
-
const
|
|
6504
|
-
|
|
6801
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
6802
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
6803
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
6804
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
6505
6805
|
return false;
|
|
6506
6806
|
}
|
|
6807
|
+
if (isHorizontal) {
|
|
6808
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
6809
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
6810
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
6811
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
6812
|
+
}
|
|
6507
6813
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
6508
6814
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
6509
6815
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
6510
6816
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
6511
6817
|
}
|
|
6512
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6818
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6513
6819
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
6514
6820
|
const padding = swimlane.padding ?? 16;
|
|
6515
6821
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -6534,7 +6840,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6534
6840
|
locks,
|
|
6535
6841
|
diagnostics,
|
|
6536
6842
|
movedChildIds,
|
|
6537
|
-
laneGutter
|
|
6843
|
+
laneGutter,
|
|
6844
|
+
constraints,
|
|
6845
|
+
distributeContainedChildren
|
|
6538
6846
|
);
|
|
6539
6847
|
}
|
|
6540
6848
|
return applyHorizontalSwimlaneContract(
|
|
@@ -6549,13 +6857,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6549
6857
|
laneGutter
|
|
6550
6858
|
);
|
|
6551
6859
|
}
|
|
6552
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6860
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6553
6861
|
const populatedBounds = laneBounds.filter(
|
|
6554
6862
|
(box) => box !== void 0
|
|
6555
6863
|
);
|
|
6556
6864
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
6557
6865
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
6558
6866
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
6867
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
6868
|
+
if (distributeContainedChildren) {
|
|
6869
|
+
for (const c of constraints) {
|
|
6870
|
+
if (c.kind !== "containment") continue;
|
|
6871
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
6872
|
+
const distributable = c.childIds.filter((childId) => {
|
|
6873
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
6874
|
+
const lock = locks.get(childId);
|
|
6875
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
6876
|
+
});
|
|
6877
|
+
if (distributable.length < 2) continue;
|
|
6878
|
+
for (const childId of distributable) {
|
|
6879
|
+
containedChildIds.add(childId);
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6559
6883
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
6560
6884
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
6561
6885
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -6567,7 +6891,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6567
6891
|
);
|
|
6568
6892
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
6569
6893
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
6570
|
-
const
|
|
6894
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
6895
|
+
swimlane,
|
|
6896
|
+
nodeBoxes,
|
|
6897
|
+
flowRanks,
|
|
6898
|
+
locks,
|
|
6899
|
+
rankStackGap,
|
|
6900
|
+
containedChildIds
|
|
6901
|
+
);
|
|
6902
|
+
const slotWidth = Math.max(
|
|
6903
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
6904
|
+
spreadWidth
|
|
6905
|
+
) + padding * 2;
|
|
6571
6906
|
const laneStep = slotWidth + laneGutter;
|
|
6572
6907
|
const laneContentTop = top + headerHeight + padding;
|
|
6573
6908
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -6581,6 +6916,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6581
6916
|
y: laneContentTop
|
|
6582
6917
|
};
|
|
6583
6918
|
if (maxRank === 0) {
|
|
6919
|
+
const distributable = lane.children.filter(
|
|
6920
|
+
(childId) => !locks.has(childId)
|
|
6921
|
+
);
|
|
6922
|
+
const coveredByContainment = lane.children.some(
|
|
6923
|
+
(childId) => containedChildIds.has(childId)
|
|
6924
|
+
);
|
|
6925
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
6926
|
+
moveRankedVerticalLaneChildren(
|
|
6927
|
+
lane.children,
|
|
6928
|
+
nodeBoxes,
|
|
6929
|
+
locks,
|
|
6930
|
+
diagnostics,
|
|
6931
|
+
movedChildIds,
|
|
6932
|
+
flowRanks,
|
|
6933
|
+
rankSpacing,
|
|
6934
|
+
rankStackGap,
|
|
6935
|
+
{ x: target.x, y: laneContentTop },
|
|
6936
|
+
slotWidth - padding * 2
|
|
6937
|
+
);
|
|
6938
|
+
continue;
|
|
6939
|
+
}
|
|
6584
6940
|
moveLaneChildren(
|
|
6585
6941
|
lane.children,
|
|
6586
6942
|
nodeBoxes,
|
|
@@ -6594,6 +6950,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6594
6950
|
);
|
|
6595
6951
|
continue;
|
|
6596
6952
|
}
|
|
6953
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
6954
|
+
(childId) => containedChildIds.has(childId)
|
|
6955
|
+
);
|
|
6597
6956
|
moveRankedVerticalLaneChildren(
|
|
6598
6957
|
lane.children,
|
|
6599
6958
|
nodeBoxes,
|
|
@@ -6603,10 +6962,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6603
6962
|
flowRanks,
|
|
6604
6963
|
rankSpacing,
|
|
6605
6964
|
rankStackGap,
|
|
6606
|
-
{
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
}
|
|
6965
|
+
{ x: target.x, y: laneContentTop },
|
|
6966
|
+
slotWidth - padding * 2,
|
|
6967
|
+
rankedCoveredByContainment
|
|
6610
6968
|
);
|
|
6611
6969
|
}
|
|
6612
6970
|
return {
|
|
@@ -6715,31 +7073,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
6715
7073
|
}
|
|
6716
7074
|
return maxHeight;
|
|
6717
7075
|
}
|
|
6718
|
-
|
|
7076
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7077
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7078
|
+
return items.reduce(
|
|
7079
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7080
|
+
0
|
|
7081
|
+
);
|
|
7082
|
+
}
|
|
7083
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7084
|
+
let maxWidth = 0;
|
|
7085
|
+
for (const lane of swimlane.lanes) {
|
|
7086
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7087
|
+
continue;
|
|
7088
|
+
}
|
|
7089
|
+
for (const stack of rankStacks(
|
|
7090
|
+
lane.children,
|
|
7091
|
+
nodeBoxes,
|
|
7092
|
+
flowRanks
|
|
7093
|
+
).values()) {
|
|
7094
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7095
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7096
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
return maxWidth;
|
|
7100
|
+
}
|
|
7101
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
6719
7102
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6720
|
-
|
|
7103
|
+
const unlocked = [];
|
|
6721
7104
|
for (const item of stack) {
|
|
6722
|
-
|
|
6723
|
-
if (locks.has(childId)) {
|
|
7105
|
+
if (locks.has(item.childId)) {
|
|
6724
7106
|
diagnostics.push({
|
|
6725
7107
|
severity: "warning",
|
|
6726
7108
|
code: "constraints.locked-target-not-moved",
|
|
6727
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7109
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
6728
7110
|
path: ["swimlanes"],
|
|
6729
|
-
detail: { nodeId: childId }
|
|
7111
|
+
detail: { nodeId: item.childId }
|
|
6730
7112
|
});
|
|
6731
|
-
|
|
7113
|
+
} else {
|
|
7114
|
+
unlocked.push(item);
|
|
6732
7115
|
}
|
|
7116
|
+
}
|
|
7117
|
+
if (unlocked.length === 0) continue;
|
|
7118
|
+
if (unlocked.length === 1) {
|
|
7119
|
+
const { childId, box } = unlocked[0];
|
|
6733
7120
|
const next = {
|
|
6734
7121
|
...box,
|
|
6735
|
-
x:
|
|
6736
|
-
y: target.y + rank * rankSpacing
|
|
7122
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7123
|
+
y: target.y + rank * rankSpacing
|
|
6737
7124
|
};
|
|
6738
7125
|
if (next.x !== box.x || next.y !== box.y) {
|
|
6739
7126
|
movedChildIds.add(childId);
|
|
6740
7127
|
}
|
|
6741
7128
|
nodeBoxes.set(childId, next);
|
|
6742
|
-
|
|
7129
|
+
} else {
|
|
7130
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7131
|
+
if (!shouldSpread) {
|
|
7132
|
+
let yOffset = 0;
|
|
7133
|
+
for (const { childId, box } of unlocked) {
|
|
7134
|
+
const next = {
|
|
7135
|
+
...box,
|
|
7136
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7137
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7138
|
+
};
|
|
7139
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7140
|
+
movedChildIds.add(childId);
|
|
7141
|
+
}
|
|
7142
|
+
nodeBoxes.set(childId, next);
|
|
7143
|
+
yOffset += box.height + rankStackGap;
|
|
7144
|
+
}
|
|
7145
|
+
} else {
|
|
7146
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
7147
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
7148
|
+
for (const { childId, box } of unlocked) {
|
|
7149
|
+
const next = {
|
|
7150
|
+
...box,
|
|
7151
|
+
x: xCursor,
|
|
7152
|
+
y: target.y + rank * rankSpacing
|
|
7153
|
+
};
|
|
7154
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7155
|
+
movedChildIds.add(childId);
|
|
7156
|
+
}
|
|
7157
|
+
nodeBoxes.set(childId, next);
|
|
7158
|
+
xCursor += box.width + rankStackGap;
|
|
7159
|
+
}
|
|
7160
|
+
diagnostics.push({
|
|
7161
|
+
severity: "info",
|
|
7162
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
7163
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
7164
|
+
path: ["swimlanes"],
|
|
7165
|
+
detail: {
|
|
7166
|
+
rank,
|
|
7167
|
+
childCount: unlocked.length,
|
|
7168
|
+
contentWidth
|
|
7169
|
+
}
|
|
7170
|
+
});
|
|
7171
|
+
}
|
|
6743
7172
|
}
|
|
6744
7173
|
}
|
|
6745
7174
|
}
|
|
@@ -7078,7 +7507,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7078
7507
|
});
|
|
7079
7508
|
continue;
|
|
7080
7509
|
}
|
|
7081
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
7510
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7082
7511
|
const geometry = computeShapeGeometry({
|
|
7083
7512
|
shape: node.shape,
|
|
7084
7513
|
box,
|
|
@@ -7172,7 +7601,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
7172
7601
|
}
|
|
7173
7602
|
}
|
|
7174
7603
|
}
|
|
7175
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
7604
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
7176
7605
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
7177
7606
|
for (const port of node.ports ?? []) {
|
|
7178
7607
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -7195,9 +7624,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7195
7624
|
side,
|
|
7196
7625
|
index,
|
|
7197
7626
|
sorted.length,
|
|
7198
|
-
portShifting
|
|
7199
|
-
diagnostics,
|
|
7200
|
-
node.id
|
|
7627
|
+
portShifting
|
|
7201
7628
|
);
|
|
7202
7629
|
const box = portBox(anchor);
|
|
7203
7630
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -7205,32 +7632,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7205
7632
|
}
|
|
7206
7633
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
7207
7634
|
}
|
|
7208
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
7635
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
7209
7636
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
7210
7637
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
7211
7638
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
7212
7639
|
const availableSpan = 2 * maxOffset;
|
|
7213
7640
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
7214
|
-
const
|
|
7641
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
7215
7642
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
7216
7643
|
minSpacing
|
|
7217
7644
|
) : requestedSpacing;
|
|
7218
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
7219
|
-
diagnostics.push({
|
|
7220
|
-
severity: "warning",
|
|
7221
|
-
code: "port_constraint_overlap",
|
|
7222
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
7223
|
-
path: ["nodes", nodeId, "ports"],
|
|
7224
|
-
detail: {
|
|
7225
|
-
nodeId,
|
|
7226
|
-
side,
|
|
7227
|
-
requestedSpacing,
|
|
7228
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
7229
|
-
portCount: count
|
|
7230
|
-
}
|
|
7231
|
-
});
|
|
7232
|
-
}
|
|
7233
|
-
const spacing = effectiveSpacing;
|
|
7234
7645
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
7235
7646
|
switch (side) {
|
|
7236
7647
|
case "left":
|
|
@@ -7845,14 +8256,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
7845
8256
|
}
|
|
7846
8257
|
};
|
|
7847
8258
|
}
|
|
7848
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
8259
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
7849
8260
|
const coordinated = [];
|
|
7850
8261
|
const coordinatedNodeById = new Map(
|
|
7851
8262
|
coordinatedNodes.map((node) => [node.id, node])
|
|
7852
8263
|
);
|
|
8264
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
8265
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
8266
|
+
200,
|
|
8267
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
8268
|
+
);
|
|
8269
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
8270
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
7853
8271
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
7854
8272
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
7855
|
-
|
|
8273
|
+
queryGutter
|
|
7856
8274
|
);
|
|
7857
8275
|
for (const edge of edges) {
|
|
7858
8276
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -7874,11 +8292,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7874
8292
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
7875
8293
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
7876
8294
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
7877
|
-
const corridor = edgeCorridorBox(
|
|
7878
|
-
source.box,
|
|
7879
|
-
target.box,
|
|
7880
|
-
options.routingGutter ?? 160
|
|
7881
|
-
);
|
|
8295
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
7882
8296
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
7883
8297
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
7884
8298
|
);
|
|
@@ -7896,7 +8310,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7896
8310
|
...routeTextObstacles
|
|
7897
8311
|
],
|
|
7898
8312
|
hardObstacles,
|
|
7899
|
-
|
|
8313
|
+
corridorMargin,
|
|
8314
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
8315
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
7900
8316
|
});
|
|
7901
8317
|
diagnostics.push(
|
|
7902
8318
|
...route.diagnostics.map((diagnostic) => ({
|