@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.js
CHANGED
|
@@ -1391,6 +1391,28 @@ function applyLayoutConstraints(input) {
|
|
|
1391
1391
|
if (input.distributeContainedChildren) {
|
|
1392
1392
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
1393
1393
|
}
|
|
1394
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1395
|
+
for (const swimlane of input.swimlanes) {
|
|
1396
|
+
if (swimlane.layout === "contract") continue;
|
|
1397
|
+
for (const lane of swimlane.lanes) {
|
|
1398
|
+
const fixedChildren = [];
|
|
1399
|
+
let participantCount = 0;
|
|
1400
|
+
for (const childId of lane.children) {
|
|
1401
|
+
const lock = locks.get(childId);
|
|
1402
|
+
if (lock === void 0) {
|
|
1403
|
+
participantCount += 1;
|
|
1404
|
+
} else if (lock.source === "fixed-position") {
|
|
1405
|
+
participantCount += 1;
|
|
1406
|
+
fixedChildren.push(childId);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (participantCount < 2) continue;
|
|
1410
|
+
for (const childId of fixedChildren) {
|
|
1411
|
+
locks.delete(childId);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1394
1416
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
1395
1417
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
1396
1418
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -1410,6 +1432,9 @@ function applyLayoutConstraints(input) {
|
|
|
1410
1432
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1411
1433
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
1412
1434
|
}
|
|
1435
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1436
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1437
|
+
}
|
|
1413
1438
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
1414
1439
|
reportOverlaps(
|
|
1415
1440
|
boxes,
|
|
@@ -2249,9 +2274,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
2249
2274
|
}
|
|
2250
2275
|
});
|
|
2251
2276
|
}
|
|
2252
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
2253
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
2254
|
-
}
|
|
2255
2277
|
}
|
|
2256
2278
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
2257
2279
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -2291,6 +2313,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
2291
2313
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
2292
2314
|
}
|
|
2293
2315
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
2316
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
2294
2317
|
let pos = contentStart;
|
|
2295
2318
|
for (const child of unlocked) {
|
|
2296
2319
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4082,15 +4105,12 @@ var BinaryHeap = class {
|
|
|
4082
4105
|
let smallestIdx = idx;
|
|
4083
4106
|
const leftIdx = (idx << 1) + 1;
|
|
4084
4107
|
const rightIdx = leftIdx + 1;
|
|
4085
|
-
if (leftIdx < size && this._less(
|
|
4086
|
-
this._data[leftIdx],
|
|
4087
|
-
this._data[smallestIdx]
|
|
4088
|
-
)) {
|
|
4108
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4089
4109
|
smallestIdx = leftIdx;
|
|
4090
4110
|
}
|
|
4091
4111
|
if (rightIdx < size && this._less(
|
|
4092
4112
|
this._data[rightIdx],
|
|
4093
|
-
this._data[
|
|
4113
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4094
4114
|
)) {
|
|
4095
4115
|
smallestIdx = rightIdx;
|
|
4096
4116
|
}
|
|
@@ -4157,8 +4177,59 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4157
4177
|
turnPenalty,
|
|
4158
4178
|
segmentPenalty
|
|
4159
4179
|
);
|
|
4160
|
-
if (path
|
|
4161
|
-
|
|
4180
|
+
if (path !== null) {
|
|
4181
|
+
const simplified = simplifyRoute(path);
|
|
4182
|
+
const filteredSet = new Set(filtered);
|
|
4183
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4184
|
+
let crossesExcluded = false;
|
|
4185
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4186
|
+
const a = simplified[i];
|
|
4187
|
+
const b = simplified[i + 1];
|
|
4188
|
+
for (const obs of obstacles) {
|
|
4189
|
+
if (filteredSet.has(obs)) continue;
|
|
4190
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4191
|
+
crossesExcluded = true;
|
|
4192
|
+
break;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
if (crossesExcluded) break;
|
|
4196
|
+
}
|
|
4197
|
+
if (!crossesExcluded) return simplified;
|
|
4198
|
+
} else {
|
|
4199
|
+
return simplified;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
if (!useCorridor) return null;
|
|
4203
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4204
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4205
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4206
|
+
diagnostics?.push({
|
|
4207
|
+
severity: "warning",
|
|
4208
|
+
code: "routing.astar.grid_overflow",
|
|
4209
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4210
|
+
detail: { xsCount: xsFull.length, ysCount: ysFull.length, maxNodes }
|
|
4211
|
+
});
|
|
4212
|
+
return null;
|
|
4213
|
+
}
|
|
4214
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4215
|
+
connectHorizontalEdges(
|
|
4216
|
+
nodesFull,
|
|
4217
|
+
ysFull,
|
|
4218
|
+
obstacles,
|
|
4219
|
+
endpointObstacles,
|
|
4220
|
+
margin
|
|
4221
|
+
);
|
|
4222
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4223
|
+
const pathFull = aStarSearch(
|
|
4224
|
+
nodesFull,
|
|
4225
|
+
idxFull,
|
|
4226
|
+
source,
|
|
4227
|
+
target,
|
|
4228
|
+
turnPenalty,
|
|
4229
|
+
segmentPenalty
|
|
4230
|
+
);
|
|
4231
|
+
if (pathFull === null) return null;
|
|
4232
|
+
return simplifyRoute(pathFull);
|
|
4162
4233
|
}
|
|
4163
4234
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4164
4235
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -4385,7 +4456,7 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
4385
4456
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
4386
4457
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
4387
4458
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
4388
|
-
const maxCorners = options.maxCorners ??
|
|
4459
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
4389
4460
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
4390
4461
|
if (vertices.length > maxCorners) {
|
|
4391
4462
|
diagnostics?.push({
|
|
@@ -4666,7 +4737,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
4666
4737
|
}
|
|
4667
4738
|
|
|
4668
4739
|
// src/routing/routes.ts
|
|
4669
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
4740
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
4670
4741
|
if (points.length < 2) return;
|
|
4671
4742
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
4672
4743
|
if (direct <= 0) return;
|
|
@@ -4676,7 +4747,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
4676
4747
|
const b = points[i + 1];
|
|
4677
4748
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
4678
4749
|
}
|
|
4679
|
-
const threshold =
|
|
4750
|
+
const threshold = maxRatio ?? 20;
|
|
4680
4751
|
if (routeLen > direct * threshold) {
|
|
4681
4752
|
diagnostics.push({
|
|
4682
4753
|
severity: "warning",
|
|
@@ -4694,8 +4765,20 @@ function routeEdge(input) {
|
|
|
4694
4765
|
const diagnostics = [];
|
|
4695
4766
|
const softObstacles = input.obstacles ?? [];
|
|
4696
4767
|
const hardObstacles = input.hardObstacles ?? [];
|
|
4768
|
+
let bestRejectedPath;
|
|
4769
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
4697
4770
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
4698
4771
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
4772
|
+
const recordRejected = (candidate) => {
|
|
4773
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
4774
|
+
return;
|
|
4775
|
+
}
|
|
4776
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
4777
|
+
if (crossings < bestRejectedCrossings) {
|
|
4778
|
+
bestRejectedCrossings = crossings;
|
|
4779
|
+
bestRejectedPath = candidate;
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4699
4782
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
4700
4783
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
4701
4784
|
input.source.box,
|
|
@@ -4754,18 +4837,38 @@ function routeEdge(input) {
|
|
|
4754
4837
|
input.source.center,
|
|
4755
4838
|
targetAnchor
|
|
4756
4839
|
);
|
|
4757
|
-
const
|
|
4840
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4841
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
4842
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
4843
|
+
source,
|
|
4844
|
+
target,
|
|
4845
|
+
allObstacles,
|
|
4846
|
+
[],
|
|
4847
|
+
// endpointObstacles passed separately via options
|
|
4848
|
+
corridorMargin
|
|
4849
|
+
);
|
|
4850
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4851
|
+
let cornerPath = findCornerGraphPath(
|
|
4758
4852
|
source,
|
|
4759
4853
|
target,
|
|
4760
|
-
|
|
4854
|
+
cornerObstacles,
|
|
4761
4855
|
{ endpointObstacles, margin: 2 },
|
|
4762
4856
|
diagnostics
|
|
4763
4857
|
);
|
|
4858
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
4859
|
+
cornerPath = findCornerGraphPath(
|
|
4860
|
+
source,
|
|
4861
|
+
target,
|
|
4862
|
+
allObstacles,
|
|
4863
|
+
{ endpointObstacles, margin: 2 },
|
|
4864
|
+
diagnostics
|
|
4865
|
+
);
|
|
4866
|
+
}
|
|
4764
4867
|
const path = cornerPath ?? findObstacleFreePath(
|
|
4765
4868
|
source,
|
|
4766
4869
|
target,
|
|
4767
|
-
|
|
4768
|
-
{ endpointObstacles, margin: 0 },
|
|
4870
|
+
allObstacles,
|
|
4871
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
4769
4872
|
diagnostics
|
|
4770
4873
|
);
|
|
4771
4874
|
if (path !== null && path.length >= 2) {
|
|
@@ -4782,9 +4885,90 @@ function routeEdge(input) {
|
|
|
4782
4885
|
softObstacles,
|
|
4783
4886
|
softObstacleIndex
|
|
4784
4887
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
4785
|
-
checkBacktracking(
|
|
4888
|
+
checkBacktracking(
|
|
4889
|
+
finalized,
|
|
4890
|
+
source,
|
|
4891
|
+
target,
|
|
4892
|
+
diagnostics,
|
|
4893
|
+
input.maxBacktrackingRatio
|
|
4894
|
+
);
|
|
4786
4895
|
return { points: finalized, diagnostics };
|
|
4787
4896
|
}
|
|
4897
|
+
recordRejected(finalized);
|
|
4898
|
+
if (cornerPath !== null) {
|
|
4899
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4900
|
+
source,
|
|
4901
|
+
target,
|
|
4902
|
+
allObstacles,
|
|
4903
|
+
{ endpointObstacles, margin: 2 },
|
|
4904
|
+
diagnostics
|
|
4905
|
+
) : null;
|
|
4906
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
4907
|
+
const fullFinalized = finalizeRoute(
|
|
4908
|
+
fullCornerPath,
|
|
4909
|
+
softObstacles,
|
|
4910
|
+
hardObstacles,
|
|
4911
|
+
diagnostics,
|
|
4912
|
+
softObstacleIndex,
|
|
4913
|
+
hardObstacleIndex
|
|
4914
|
+
);
|
|
4915
|
+
if (!routeIntersectsObstacles(
|
|
4916
|
+
fullFinalized,
|
|
4917
|
+
softObstacles,
|
|
4918
|
+
softObstacleIndex
|
|
4919
|
+
) && !routeIntersectsObstacles(
|
|
4920
|
+
fullFinalized,
|
|
4921
|
+
hardObstacles,
|
|
4922
|
+
hardObstacleIndex
|
|
4923
|
+
)) {
|
|
4924
|
+
checkBacktracking(
|
|
4925
|
+
fullFinalized,
|
|
4926
|
+
source,
|
|
4927
|
+
target,
|
|
4928
|
+
diagnostics,
|
|
4929
|
+
input.maxBacktrackingRatio
|
|
4930
|
+
);
|
|
4931
|
+
return { points: fullFinalized, diagnostics };
|
|
4932
|
+
}
|
|
4933
|
+
recordRejected(fullFinalized);
|
|
4934
|
+
}
|
|
4935
|
+
const gridPath = findObstacleFreePath(
|
|
4936
|
+
source,
|
|
4937
|
+
target,
|
|
4938
|
+
allObstacles,
|
|
4939
|
+
{ endpointObstacles, margin: 0, corridorMargin },
|
|
4940
|
+
diagnostics
|
|
4941
|
+
);
|
|
4942
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
4943
|
+
const gridFinalized = finalizeRoute(
|
|
4944
|
+
gridPath,
|
|
4945
|
+
softObstacles,
|
|
4946
|
+
hardObstacles,
|
|
4947
|
+
diagnostics,
|
|
4948
|
+
softObstacleIndex,
|
|
4949
|
+
hardObstacleIndex
|
|
4950
|
+
);
|
|
4951
|
+
if (!routeIntersectsObstacles(
|
|
4952
|
+
gridFinalized,
|
|
4953
|
+
softObstacles,
|
|
4954
|
+
softObstacleIndex
|
|
4955
|
+
) && !routeIntersectsObstacles(
|
|
4956
|
+
gridFinalized,
|
|
4957
|
+
hardObstacles,
|
|
4958
|
+
hardObstacleIndex
|
|
4959
|
+
)) {
|
|
4960
|
+
checkBacktracking(
|
|
4961
|
+
gridFinalized,
|
|
4962
|
+
source,
|
|
4963
|
+
target,
|
|
4964
|
+
diagnostics,
|
|
4965
|
+
input.maxBacktrackingRatio
|
|
4966
|
+
);
|
|
4967
|
+
return { points: gridFinalized, diagnostics };
|
|
4968
|
+
}
|
|
4969
|
+
recordRejected(gridFinalized);
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4788
4972
|
}
|
|
4789
4973
|
}
|
|
4790
4974
|
}
|
|
@@ -4846,7 +5030,8 @@ function routeEdge(input) {
|
|
|
4846
5030
|
finalizedClean,
|
|
4847
5031
|
candidate.points[0],
|
|
4848
5032
|
candidate.points[candidate.points.length - 1],
|
|
4849
|
-
diagnostics
|
|
5033
|
+
diagnostics,
|
|
5034
|
+
input.maxBacktrackingRatio
|
|
4850
5035
|
);
|
|
4851
5036
|
return { points: finalizedClean, diagnostics };
|
|
4852
5037
|
}
|
|
@@ -4912,13 +5097,41 @@ function routeEdge(input) {
|
|
|
4912
5097
|
code: "routing.obstacle.unavoidable",
|
|
4913
5098
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
4914
5099
|
});
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
5100
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5101
|
+
bestPoints2,
|
|
5102
|
+
softObstacles,
|
|
5103
|
+
hardObstacles,
|
|
5104
|
+
diagnostics
|
|
5105
|
+
);
|
|
5106
|
+
let softFallback = finalizedSoftBest;
|
|
5107
|
+
if (bestRejectedPath !== void 0) {
|
|
5108
|
+
const finalizedRejected = finalizeRoute(
|
|
5109
|
+
bestRejectedPath,
|
|
4918
5110
|
softObstacles,
|
|
4919
5111
|
hardObstacles,
|
|
4920
5112
|
diagnostics
|
|
4921
|
-
)
|
|
5113
|
+
);
|
|
5114
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5115
|
+
finalizedRejected,
|
|
5116
|
+
softObstacles
|
|
5117
|
+
);
|
|
5118
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5119
|
+
finalizedSoftBest,
|
|
5120
|
+
softObstacles
|
|
5121
|
+
);
|
|
5122
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5123
|
+
softFallback = finalizedRejected;
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
checkBacktracking(
|
|
5127
|
+
softFallback,
|
|
5128
|
+
softFallback[0],
|
|
5129
|
+
softFallback[softFallback.length - 1],
|
|
5130
|
+
diagnostics,
|
|
5131
|
+
input.maxBacktrackingRatio
|
|
5132
|
+
);
|
|
5133
|
+
return {
|
|
5134
|
+
points: softFallback,
|
|
4922
5135
|
diagnostics
|
|
4923
5136
|
};
|
|
4924
5137
|
}
|
|
@@ -4950,6 +5163,22 @@ function routeEdge(input) {
|
|
|
4950
5163
|
maxAttempts
|
|
4951
5164
|
);
|
|
4952
5165
|
}
|
|
5166
|
+
if (bestRejectedPath !== void 0) {
|
|
5167
|
+
diagnostics.push({
|
|
5168
|
+
severity: "warning",
|
|
5169
|
+
code: "routing.obstacle.unavoidable",
|
|
5170
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5171
|
+
});
|
|
5172
|
+
return {
|
|
5173
|
+
points: finalizeRoute(
|
|
5174
|
+
bestRejectedPath,
|
|
5175
|
+
softObstacles,
|
|
5176
|
+
hardObstacles,
|
|
5177
|
+
diagnostics
|
|
5178
|
+
),
|
|
5179
|
+
diagnostics
|
|
5180
|
+
};
|
|
5181
|
+
}
|
|
4953
5182
|
diagnostics.push({
|
|
4954
5183
|
severity: "error",
|
|
4955
5184
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -4997,13 +5226,41 @@ function routeEdge(input) {
|
|
|
4997
5226
|
code: "routing.obstacle.unavoidable",
|
|
4998
5227
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
4999
5228
|
});
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5229
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5230
|
+
bestPoints,
|
|
5231
|
+
softObstacles,
|
|
5232
|
+
hardObstacles,
|
|
5233
|
+
diagnostics
|
|
5234
|
+
);
|
|
5235
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5236
|
+
if (bestRejectedPath !== void 0) {
|
|
5237
|
+
const finalizedRejected = finalizeRoute(
|
|
5238
|
+
bestRejectedPath,
|
|
5003
5239
|
softObstacles,
|
|
5004
5240
|
hardObstacles,
|
|
5005
5241
|
diagnostics
|
|
5006
|
-
)
|
|
5242
|
+
);
|
|
5243
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5244
|
+
finalizedRejected,
|
|
5245
|
+
softObstacles
|
|
5246
|
+
);
|
|
5247
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5248
|
+
finalizedBestPoints,
|
|
5249
|
+
softObstacles
|
|
5250
|
+
);
|
|
5251
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5252
|
+
fallbackPoints = finalizedRejected;
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
checkBacktracking(
|
|
5256
|
+
fallbackPoints,
|
|
5257
|
+
fallbackPoints[0],
|
|
5258
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5259
|
+
diagnostics,
|
|
5260
|
+
input.maxBacktrackingRatio
|
|
5261
|
+
);
|
|
5262
|
+
return {
|
|
5263
|
+
points: fallbackPoints,
|
|
5007
5264
|
diagnostics
|
|
5008
5265
|
};
|
|
5009
5266
|
}
|
|
@@ -5502,6 +5759,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
5502
5759
|
}
|
|
5503
5760
|
return false;
|
|
5504
5761
|
}
|
|
5762
|
+
function countObstacleCrossings(points, obstacles) {
|
|
5763
|
+
let count = 0;
|
|
5764
|
+
for (const obstacle of obstacles) {
|
|
5765
|
+
validateBox(obstacle);
|
|
5766
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
5767
|
+
const a = points[pointIndex];
|
|
5768
|
+
const b = points[pointIndex + 1];
|
|
5769
|
+
if (a === void 0 || b === void 0) {
|
|
5770
|
+
continue;
|
|
5771
|
+
}
|
|
5772
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
5773
|
+
count += 1;
|
|
5774
|
+
break;
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
return count;
|
|
5779
|
+
}
|
|
5505
5780
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
5506
5781
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5507
5782
|
const a = points[index];
|
|
@@ -5674,7 +5949,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5674
5949
|
edges: styledEdges
|
|
5675
5950
|
});
|
|
5676
5951
|
diagnostics.push(...layout2.diagnostics);
|
|
5677
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5952
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5678
5953
|
layout2.boxes,
|
|
5679
5954
|
styledNodes,
|
|
5680
5955
|
styledEdges,
|
|
@@ -5682,7 +5957,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5682
5957
|
options,
|
|
5683
5958
|
diagnostics
|
|
5684
5959
|
);
|
|
5685
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
5960
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
5961
|
+
const diagCountBefore = diagnostics.length;
|
|
5686
5962
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
5687
5963
|
initialNodeBoxes,
|
|
5688
5964
|
styledNodes,
|
|
@@ -5693,6 +5969,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5693
5969
|
for (const [id, box] of rewrapped) {
|
|
5694
5970
|
initialNodeBoxes.set(id, box);
|
|
5695
5971
|
}
|
|
5972
|
+
if (diagnostics.length > diagCountBefore) {
|
|
5973
|
+
for (const node of styledNodes) {
|
|
5974
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
5975
|
+
const rwBox = rewrapped.get(node.id);
|
|
5976
|
+
const idx = styledNodes.indexOf(node);
|
|
5977
|
+
if (idx !== -1) {
|
|
5978
|
+
styledNodes[idx] = {
|
|
5979
|
+
...node,
|
|
5980
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
5981
|
+
};
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5696
5986
|
}
|
|
5697
5987
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
5698
5988
|
const recursiveLayout = layout2;
|
|
@@ -5706,7 +5996,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5706
5996
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
5707
5997
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
5708
5998
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
5709
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
5999
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
5710
6000
|
swimlanes: styledSwimlanes,
|
|
5711
6001
|
boxes: initialNodeBoxes,
|
|
5712
6002
|
nodes: styledNodes,
|
|
@@ -5721,7 +6011,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5721
6011
|
constrained.boxes,
|
|
5722
6012
|
constrained.locks,
|
|
5723
6013
|
options?.overlapSpacing ?? 40,
|
|
5724
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6014
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6015
|
+
options.distributeContainedChildren ?? true
|
|
5725
6016
|
);
|
|
5726
6017
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
5727
6018
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -5888,7 +6179,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5888
6179
|
diagram.direction,
|
|
5889
6180
|
options,
|
|
5890
6181
|
diagnostics,
|
|
5891
|
-
coordinatedGroups
|
|
6182
|
+
coordinatedGroups,
|
|
6183
|
+
contentBounds
|
|
5892
6184
|
);
|
|
5893
6185
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
5894
6186
|
coordinatedEdges,
|
|
@@ -6307,7 +6599,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
6307
6599
|
function containsCjk(value) {
|
|
6308
6600
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
6309
6601
|
}
|
|
6310
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
6602
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
6311
6603
|
const layouts = /* @__PURE__ */ new Map();
|
|
6312
6604
|
const diagnostics = [];
|
|
6313
6605
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -6326,7 +6618,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
6326
6618
|
locks,
|
|
6327
6619
|
diagnostics,
|
|
6328
6620
|
movedChildIds,
|
|
6329
|
-
laneGutter
|
|
6621
|
+
laneGutter,
|
|
6622
|
+
constraints,
|
|
6623
|
+
distributeContainedChildren
|
|
6330
6624
|
);
|
|
6331
6625
|
if (layout2 !== void 0) {
|
|
6332
6626
|
layouts.set(swimlane.id, layout2);
|
|
@@ -6422,9 +6716,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
6422
6716
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
6423
6717
|
return new Map(boxes);
|
|
6424
6718
|
}
|
|
6425
|
-
|
|
6426
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
6427
|
-
|
|
6719
|
+
let maxRowDepth = options.maxRowDepth;
|
|
6720
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
6721
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
6722
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
6723
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
6724
|
+
} else {
|
|
6725
|
+
return new Map(boxes);
|
|
6726
|
+
}
|
|
6428
6727
|
}
|
|
6429
6728
|
const ordered = [...nodes].sort((a, b) => {
|
|
6430
6729
|
const ba = boxes.get(a.id);
|
|
@@ -6485,10 +6784,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
6485
6784
|
});
|
|
6486
6785
|
}
|
|
6487
6786
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
6488
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
6787
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
6489
6788
|
return false;
|
|
6490
6789
|
}
|
|
6491
|
-
if (nodes.length < 2
|
|
6790
|
+
if (nodes.length < 2) {
|
|
6492
6791
|
return false;
|
|
6493
6792
|
}
|
|
6494
6793
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -6496,17 +6795,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6496
6795
|
return false;
|
|
6497
6796
|
}
|
|
6498
6797
|
const bounds = unionBoxes(nodeBoxes);
|
|
6499
|
-
const
|
|
6500
|
-
const
|
|
6501
|
-
|
|
6798
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
6799
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
6800
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
6801
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
6502
6802
|
return false;
|
|
6503
6803
|
}
|
|
6804
|
+
if (isHorizontal) {
|
|
6805
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
6806
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
6807
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
6808
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
6809
|
+
}
|
|
6504
6810
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
6505
6811
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
6506
6812
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
6507
6813
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
6508
6814
|
}
|
|
6509
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6815
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6510
6816
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
6511
6817
|
const padding = swimlane.padding ?? 16;
|
|
6512
6818
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -6531,7 +6837,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6531
6837
|
locks,
|
|
6532
6838
|
diagnostics,
|
|
6533
6839
|
movedChildIds,
|
|
6534
|
-
laneGutter
|
|
6840
|
+
laneGutter,
|
|
6841
|
+
constraints,
|
|
6842
|
+
distributeContainedChildren
|
|
6535
6843
|
);
|
|
6536
6844
|
}
|
|
6537
6845
|
return applyHorizontalSwimlaneContract(
|
|
@@ -6546,13 +6854,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6546
6854
|
laneGutter
|
|
6547
6855
|
);
|
|
6548
6856
|
}
|
|
6549
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6857
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6550
6858
|
const populatedBounds = laneBounds.filter(
|
|
6551
6859
|
(box) => box !== void 0
|
|
6552
6860
|
);
|
|
6553
6861
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
6554
6862
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
6555
6863
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
6864
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
6865
|
+
if (distributeContainedChildren) {
|
|
6866
|
+
for (const c of constraints) {
|
|
6867
|
+
if (c.kind !== "containment") continue;
|
|
6868
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
6869
|
+
const distributable = c.childIds.filter((childId) => {
|
|
6870
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
6871
|
+
const lock = locks.get(childId);
|
|
6872
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
6873
|
+
});
|
|
6874
|
+
if (distributable.length < 2) continue;
|
|
6875
|
+
for (const childId of distributable) {
|
|
6876
|
+
containedChildIds.add(childId);
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6556
6880
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
6557
6881
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
6558
6882
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -6564,7 +6888,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6564
6888
|
);
|
|
6565
6889
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
6566
6890
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
6567
|
-
const
|
|
6891
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
6892
|
+
swimlane,
|
|
6893
|
+
nodeBoxes,
|
|
6894
|
+
flowRanks,
|
|
6895
|
+
locks,
|
|
6896
|
+
rankStackGap,
|
|
6897
|
+
containedChildIds
|
|
6898
|
+
);
|
|
6899
|
+
const slotWidth = Math.max(
|
|
6900
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
6901
|
+
spreadWidth
|
|
6902
|
+
) + padding * 2;
|
|
6568
6903
|
const laneStep = slotWidth + laneGutter;
|
|
6569
6904
|
const laneContentTop = top + headerHeight + padding;
|
|
6570
6905
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -6578,6 +6913,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6578
6913
|
y: laneContentTop
|
|
6579
6914
|
};
|
|
6580
6915
|
if (maxRank === 0) {
|
|
6916
|
+
const distributable = lane.children.filter(
|
|
6917
|
+
(childId) => !locks.has(childId)
|
|
6918
|
+
);
|
|
6919
|
+
const coveredByContainment = lane.children.some(
|
|
6920
|
+
(childId) => containedChildIds.has(childId)
|
|
6921
|
+
);
|
|
6922
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
6923
|
+
moveRankedVerticalLaneChildren(
|
|
6924
|
+
lane.children,
|
|
6925
|
+
nodeBoxes,
|
|
6926
|
+
locks,
|
|
6927
|
+
diagnostics,
|
|
6928
|
+
movedChildIds,
|
|
6929
|
+
flowRanks,
|
|
6930
|
+
rankSpacing,
|
|
6931
|
+
rankStackGap,
|
|
6932
|
+
{ x: target.x, y: laneContentTop },
|
|
6933
|
+
slotWidth - padding * 2
|
|
6934
|
+
);
|
|
6935
|
+
continue;
|
|
6936
|
+
}
|
|
6581
6937
|
moveLaneChildren(
|
|
6582
6938
|
lane.children,
|
|
6583
6939
|
nodeBoxes,
|
|
@@ -6591,6 +6947,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6591
6947
|
);
|
|
6592
6948
|
continue;
|
|
6593
6949
|
}
|
|
6950
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
6951
|
+
(childId) => containedChildIds.has(childId)
|
|
6952
|
+
);
|
|
6594
6953
|
moveRankedVerticalLaneChildren(
|
|
6595
6954
|
lane.children,
|
|
6596
6955
|
nodeBoxes,
|
|
@@ -6600,10 +6959,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6600
6959
|
flowRanks,
|
|
6601
6960
|
rankSpacing,
|
|
6602
6961
|
rankStackGap,
|
|
6603
|
-
{
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
}
|
|
6962
|
+
{ x: target.x, y: laneContentTop },
|
|
6963
|
+
slotWidth - padding * 2,
|
|
6964
|
+
rankedCoveredByContainment
|
|
6607
6965
|
);
|
|
6608
6966
|
}
|
|
6609
6967
|
return {
|
|
@@ -6712,31 +7070,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
6712
7070
|
}
|
|
6713
7071
|
return maxHeight;
|
|
6714
7072
|
}
|
|
6715
|
-
|
|
7073
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7074
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7075
|
+
return items.reduce(
|
|
7076
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7077
|
+
0
|
|
7078
|
+
);
|
|
7079
|
+
}
|
|
7080
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7081
|
+
let maxWidth = 0;
|
|
7082
|
+
for (const lane of swimlane.lanes) {
|
|
7083
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7084
|
+
continue;
|
|
7085
|
+
}
|
|
7086
|
+
for (const stack of rankStacks(
|
|
7087
|
+
lane.children,
|
|
7088
|
+
nodeBoxes,
|
|
7089
|
+
flowRanks
|
|
7090
|
+
).values()) {
|
|
7091
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7092
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7093
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
return maxWidth;
|
|
7097
|
+
}
|
|
7098
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
6716
7099
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6717
|
-
|
|
7100
|
+
const unlocked = [];
|
|
6718
7101
|
for (const item of stack) {
|
|
6719
|
-
|
|
6720
|
-
if (locks.has(childId)) {
|
|
7102
|
+
if (locks.has(item.childId)) {
|
|
6721
7103
|
diagnostics.push({
|
|
6722
7104
|
severity: "warning",
|
|
6723
7105
|
code: "constraints.locked-target-not-moved",
|
|
6724
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7106
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
6725
7107
|
path: ["swimlanes"],
|
|
6726
|
-
detail: { nodeId: childId }
|
|
7108
|
+
detail: { nodeId: item.childId }
|
|
6727
7109
|
});
|
|
6728
|
-
|
|
7110
|
+
} else {
|
|
7111
|
+
unlocked.push(item);
|
|
6729
7112
|
}
|
|
7113
|
+
}
|
|
7114
|
+
if (unlocked.length === 0) continue;
|
|
7115
|
+
if (unlocked.length === 1) {
|
|
7116
|
+
const { childId, box } = unlocked[0];
|
|
6730
7117
|
const next = {
|
|
6731
7118
|
...box,
|
|
6732
|
-
x:
|
|
6733
|
-
y: target.y + rank * rankSpacing
|
|
7119
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7120
|
+
y: target.y + rank * rankSpacing
|
|
6734
7121
|
};
|
|
6735
7122
|
if (next.x !== box.x || next.y !== box.y) {
|
|
6736
7123
|
movedChildIds.add(childId);
|
|
6737
7124
|
}
|
|
6738
7125
|
nodeBoxes.set(childId, next);
|
|
6739
|
-
|
|
7126
|
+
} else {
|
|
7127
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7128
|
+
if (!shouldSpread) {
|
|
7129
|
+
let yOffset = 0;
|
|
7130
|
+
for (const { childId, box } of unlocked) {
|
|
7131
|
+
const next = {
|
|
7132
|
+
...box,
|
|
7133
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7134
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7135
|
+
};
|
|
7136
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7137
|
+
movedChildIds.add(childId);
|
|
7138
|
+
}
|
|
7139
|
+
nodeBoxes.set(childId, next);
|
|
7140
|
+
yOffset += box.height + rankStackGap;
|
|
7141
|
+
}
|
|
7142
|
+
} else {
|
|
7143
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
7144
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
7145
|
+
for (const { childId, box } of unlocked) {
|
|
7146
|
+
const next = {
|
|
7147
|
+
...box,
|
|
7148
|
+
x: xCursor,
|
|
7149
|
+
y: target.y + rank * rankSpacing
|
|
7150
|
+
};
|
|
7151
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7152
|
+
movedChildIds.add(childId);
|
|
7153
|
+
}
|
|
7154
|
+
nodeBoxes.set(childId, next);
|
|
7155
|
+
xCursor += box.width + rankStackGap;
|
|
7156
|
+
}
|
|
7157
|
+
diagnostics.push({
|
|
7158
|
+
severity: "info",
|
|
7159
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
7160
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
7161
|
+
path: ["swimlanes"],
|
|
7162
|
+
detail: {
|
|
7163
|
+
rank,
|
|
7164
|
+
childCount: unlocked.length,
|
|
7165
|
+
contentWidth
|
|
7166
|
+
}
|
|
7167
|
+
});
|
|
7168
|
+
}
|
|
6740
7169
|
}
|
|
6741
7170
|
}
|
|
6742
7171
|
}
|
|
@@ -7075,7 +7504,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7075
7504
|
});
|
|
7076
7505
|
continue;
|
|
7077
7506
|
}
|
|
7078
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
7507
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7079
7508
|
const geometry = computeShapeGeometry({
|
|
7080
7509
|
shape: node.shape,
|
|
7081
7510
|
box,
|
|
@@ -7169,7 +7598,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
7169
7598
|
}
|
|
7170
7599
|
}
|
|
7171
7600
|
}
|
|
7172
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
7601
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
7173
7602
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
7174
7603
|
for (const port of node.ports ?? []) {
|
|
7175
7604
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -7192,9 +7621,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7192
7621
|
side,
|
|
7193
7622
|
index,
|
|
7194
7623
|
sorted.length,
|
|
7195
|
-
portShifting
|
|
7196
|
-
diagnostics,
|
|
7197
|
-
node.id
|
|
7624
|
+
portShifting
|
|
7198
7625
|
);
|
|
7199
7626
|
const box = portBox(anchor);
|
|
7200
7627
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -7202,32 +7629,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7202
7629
|
}
|
|
7203
7630
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
7204
7631
|
}
|
|
7205
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
7632
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
7206
7633
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
7207
7634
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
7208
7635
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
7209
7636
|
const availableSpan = 2 * maxOffset;
|
|
7210
7637
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
7211
|
-
const
|
|
7638
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
7212
7639
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
7213
7640
|
minSpacing
|
|
7214
7641
|
) : requestedSpacing;
|
|
7215
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
7216
|
-
diagnostics.push({
|
|
7217
|
-
severity: "warning",
|
|
7218
|
-
code: "port_constraint_overlap",
|
|
7219
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
7220
|
-
path: ["nodes", nodeId, "ports"],
|
|
7221
|
-
detail: {
|
|
7222
|
-
nodeId,
|
|
7223
|
-
side,
|
|
7224
|
-
requestedSpacing,
|
|
7225
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
7226
|
-
portCount: count
|
|
7227
|
-
}
|
|
7228
|
-
});
|
|
7229
|
-
}
|
|
7230
|
-
const spacing = effectiveSpacing;
|
|
7231
7642
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
7232
7643
|
switch (side) {
|
|
7233
7644
|
case "left":
|
|
@@ -7842,14 +8253,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
7842
8253
|
}
|
|
7843
8254
|
};
|
|
7844
8255
|
}
|
|
7845
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
8256
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
7846
8257
|
const coordinated = [];
|
|
7847
8258
|
const coordinatedNodeById = new Map(
|
|
7848
8259
|
coordinatedNodes.map((node) => [node.id, node])
|
|
7849
8260
|
);
|
|
8261
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
8262
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
8263
|
+
200,
|
|
8264
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
8265
|
+
);
|
|
8266
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
8267
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
7850
8268
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
7851
8269
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
7852
|
-
|
|
8270
|
+
queryGutter
|
|
7853
8271
|
);
|
|
7854
8272
|
for (const edge of edges) {
|
|
7855
8273
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -7871,11 +8289,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7871
8289
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
7872
8290
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
7873
8291
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
7874
|
-
const corridor = edgeCorridorBox(
|
|
7875
|
-
source.box,
|
|
7876
|
-
target.box,
|
|
7877
|
-
options.routingGutter ?? 160
|
|
7878
|
-
);
|
|
8292
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
7879
8293
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
7880
8294
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
7881
8295
|
);
|
|
@@ -7893,7 +8307,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7893
8307
|
...routeTextObstacles
|
|
7894
8308
|
],
|
|
7895
8309
|
hardObstacles,
|
|
7896
|
-
|
|
8310
|
+
corridorMargin,
|
|
8311
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
8312
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
7897
8313
|
});
|
|
7898
8314
|
diagnostics.push(
|
|
7899
8315
|
...route.diagnostics.map((diagnostic) => ({
|