@crazyhappyone/auto-graph 0.2.8 → 0.2.10
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 +614 -94
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +614 -94
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +614 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +614 -94
- 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
|
}
|
|
@@ -4144,7 +4164,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4144
4164
|
detail: {
|
|
4145
4165
|
xsCount: xs.length,
|
|
4146
4166
|
ysCount: ys.length,
|
|
4147
|
-
maxNodes
|
|
4167
|
+
maxNodes,
|
|
4168
|
+
obstacleCount: obstacles.length,
|
|
4169
|
+
stage: "corridor-filtered",
|
|
4170
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4148
4171
|
}
|
|
4149
4172
|
});
|
|
4150
4173
|
return null;
|
|
@@ -4160,8 +4183,66 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4160
4183
|
turnPenalty,
|
|
4161
4184
|
segmentPenalty
|
|
4162
4185
|
);
|
|
4163
|
-
if (path
|
|
4164
|
-
|
|
4186
|
+
if (path !== null) {
|
|
4187
|
+
const simplified = simplifyRoute(path);
|
|
4188
|
+
const filteredSet = new Set(filtered);
|
|
4189
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4190
|
+
let crossesExcluded = false;
|
|
4191
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4192
|
+
const a = simplified[i];
|
|
4193
|
+
const b = simplified[i + 1];
|
|
4194
|
+
for (const obs of obstacles) {
|
|
4195
|
+
if (filteredSet.has(obs)) continue;
|
|
4196
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4197
|
+
crossesExcluded = true;
|
|
4198
|
+
break;
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
if (crossesExcluded) break;
|
|
4202
|
+
}
|
|
4203
|
+
if (!crossesExcluded) return simplified;
|
|
4204
|
+
} else {
|
|
4205
|
+
return simplified;
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
if (!useCorridor) return null;
|
|
4209
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4210
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4211
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4212
|
+
diagnostics?.push({
|
|
4213
|
+
severity: "warning",
|
|
4214
|
+
code: "routing.astar.grid_overflow",
|
|
4215
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4216
|
+
detail: {
|
|
4217
|
+
xsCount: xsFull.length,
|
|
4218
|
+
ysCount: ysFull.length,
|
|
4219
|
+
maxNodes,
|
|
4220
|
+
obstacleCount: obstacles.length,
|
|
4221
|
+
stage: "full-retry",
|
|
4222
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4223
|
+
}
|
|
4224
|
+
});
|
|
4225
|
+
return null;
|
|
4226
|
+
}
|
|
4227
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4228
|
+
connectHorizontalEdges(
|
|
4229
|
+
nodesFull,
|
|
4230
|
+
ysFull,
|
|
4231
|
+
obstacles,
|
|
4232
|
+
endpointObstacles,
|
|
4233
|
+
margin
|
|
4234
|
+
);
|
|
4235
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4236
|
+
const pathFull = aStarSearch(
|
|
4237
|
+
nodesFull,
|
|
4238
|
+
idxFull,
|
|
4239
|
+
source,
|
|
4240
|
+
target,
|
|
4241
|
+
turnPenalty,
|
|
4242
|
+
segmentPenalty
|
|
4243
|
+
);
|
|
4244
|
+
if (pathFull === null) return null;
|
|
4245
|
+
return simplifyRoute(pathFull);
|
|
4165
4246
|
}
|
|
4166
4247
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4167
4248
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -4382,20 +4463,83 @@ function areCollinear(a, b, c) {
|
|
|
4382
4463
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4383
4464
|
}
|
|
4384
4465
|
|
|
4466
|
+
// src/routing/budget.ts
|
|
4467
|
+
var MIN_CORNER_BUDGET = 600;
|
|
4468
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
4469
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
4470
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
4471
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
4472
|
+
var CORNER_HEADROOM = 2;
|
|
4473
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
4474
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
4475
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
4476
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
4477
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
4478
|
+
cornerObstacles.length,
|
|
4479
|
+
corridorMargin
|
|
4480
|
+
);
|
|
4481
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
4482
|
+
allObstacles.length,
|
|
4483
|
+
corridorMargin
|
|
4484
|
+
);
|
|
4485
|
+
return {
|
|
4486
|
+
maxCorners: resolveBudget(
|
|
4487
|
+
overrides.maxCorners,
|
|
4488
|
+
adaptiveMaxCorners,
|
|
4489
|
+
MIN_CORNER_BUDGET,
|
|
4490
|
+
MAX_CORNER_BUDGET
|
|
4491
|
+
),
|
|
4492
|
+
maxNodes: resolveBudget(
|
|
4493
|
+
overrides.maxNodes,
|
|
4494
|
+
adaptiveMaxNodes,
|
|
4495
|
+
MIN_NODE_BUDGET,
|
|
4496
|
+
MAX_NODE_BUDGET
|
|
4497
|
+
),
|
|
4498
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
4499
|
+
gridObstacleCount: allObstacles.length,
|
|
4500
|
+
corridorMargin
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
4504
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
4505
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4506
|
+
return Math.ceil(base * corridorFactor);
|
|
4507
|
+
}
|
|
4508
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
4509
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
4510
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4511
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
4512
|
+
}
|
|
4513
|
+
function corridorScalingFactor(corridorMargin) {
|
|
4514
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
4515
|
+
}
|
|
4516
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
4517
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
4518
|
+
return clamp(chosen, min, max);
|
|
4519
|
+
}
|
|
4520
|
+
function clamp(value, min, max) {
|
|
4521
|
+
return Math.max(min, Math.min(max, value));
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4385
4524
|
// src/routing/visibility-router.ts
|
|
4386
4525
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
4387
4526
|
const margin = options.margin ?? 0;
|
|
4388
4527
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
4389
4528
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
4390
4529
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
4391
|
-
const maxCorners = options.maxCorners ??
|
|
4530
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
4392
4531
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
4393
4532
|
if (vertices.length > maxCorners) {
|
|
4394
4533
|
diagnostics?.push({
|
|
4395
4534
|
severity: "warning",
|
|
4396
4535
|
code: "routing.visibility.corner_overflow",
|
|
4397
4536
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
4398
|
-
detail: {
|
|
4537
|
+
detail: {
|
|
4538
|
+
vertexCount: vertices.length,
|
|
4539
|
+
maxCorners,
|
|
4540
|
+
obstacleCount: obstacles.length,
|
|
4541
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
4542
|
+
}
|
|
4399
4543
|
});
|
|
4400
4544
|
return null;
|
|
4401
4545
|
}
|
|
@@ -4669,7 +4813,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
4669
4813
|
}
|
|
4670
4814
|
|
|
4671
4815
|
// src/routing/routes.ts
|
|
4672
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
4816
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
4673
4817
|
if (points.length < 2) return;
|
|
4674
4818
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
4675
4819
|
if (direct <= 0) return;
|
|
@@ -4679,7 +4823,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
4679
4823
|
const b = points[i + 1];
|
|
4680
4824
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
4681
4825
|
}
|
|
4682
|
-
const threshold =
|
|
4826
|
+
const threshold = maxRatio ?? 20;
|
|
4683
4827
|
if (routeLen > direct * threshold) {
|
|
4684
4828
|
diagnostics.push({
|
|
4685
4829
|
severity: "warning",
|
|
@@ -4697,8 +4841,20 @@ function routeEdge(input) {
|
|
|
4697
4841
|
const diagnostics = [];
|
|
4698
4842
|
const softObstacles = input.obstacles ?? [];
|
|
4699
4843
|
const hardObstacles = input.hardObstacles ?? [];
|
|
4844
|
+
let bestRejectedPath;
|
|
4845
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
4700
4846
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
4701
4847
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
4848
|
+
const recordRejected = (candidate) => {
|
|
4849
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
4853
|
+
if (crossings < bestRejectedCrossings) {
|
|
4854
|
+
bestRejectedCrossings = crossings;
|
|
4855
|
+
bestRejectedPath = candidate;
|
|
4856
|
+
}
|
|
4857
|
+
};
|
|
4702
4858
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
4703
4859
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
4704
4860
|
input.source.box,
|
|
@@ -4757,18 +4913,59 @@ function routeEdge(input) {
|
|
|
4757
4913
|
input.source.center,
|
|
4758
4914
|
targetAnchor
|
|
4759
4915
|
);
|
|
4760
|
-
const
|
|
4916
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4917
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
4918
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
4761
4919
|
source,
|
|
4762
4920
|
target,
|
|
4763
|
-
|
|
4764
|
-
|
|
4921
|
+
allObstacles,
|
|
4922
|
+
[],
|
|
4923
|
+
// endpointObstacles passed separately via options
|
|
4924
|
+
corridorMargin
|
|
4925
|
+
);
|
|
4926
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4927
|
+
const budget = computeRoutingBudget(
|
|
4928
|
+
cornerObstacles,
|
|
4929
|
+
allObstacles,
|
|
4930
|
+
corridorMargin,
|
|
4931
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
4932
|
+
);
|
|
4933
|
+
let cornerPath = findCornerGraphPath(
|
|
4934
|
+
source,
|
|
4935
|
+
target,
|
|
4936
|
+
cornerObstacles,
|
|
4937
|
+
{
|
|
4938
|
+
endpointObstacles,
|
|
4939
|
+
margin: 2,
|
|
4940
|
+
maxCorners: budget.maxCorners,
|
|
4941
|
+
corridorMargin
|
|
4942
|
+
},
|
|
4765
4943
|
diagnostics
|
|
4766
4944
|
);
|
|
4945
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
4946
|
+
cornerPath = findCornerGraphPath(
|
|
4947
|
+
source,
|
|
4948
|
+
target,
|
|
4949
|
+
allObstacles,
|
|
4950
|
+
{
|
|
4951
|
+
endpointObstacles,
|
|
4952
|
+
margin: 2,
|
|
4953
|
+
maxCorners: budget.maxCorners,
|
|
4954
|
+
corridorMargin
|
|
4955
|
+
},
|
|
4956
|
+
diagnostics
|
|
4957
|
+
);
|
|
4958
|
+
}
|
|
4767
4959
|
const path = cornerPath ?? findObstacleFreePath(
|
|
4768
4960
|
source,
|
|
4769
4961
|
target,
|
|
4770
|
-
|
|
4771
|
-
{
|
|
4962
|
+
allObstacles,
|
|
4963
|
+
{
|
|
4964
|
+
endpointObstacles,
|
|
4965
|
+
margin: 0,
|
|
4966
|
+
corridorMargin,
|
|
4967
|
+
maxNodes: budget.maxNodes
|
|
4968
|
+
},
|
|
4772
4969
|
diagnostics
|
|
4773
4970
|
);
|
|
4774
4971
|
if (path !== null && path.length >= 2) {
|
|
@@ -4785,9 +4982,100 @@ function routeEdge(input) {
|
|
|
4785
4982
|
softObstacles,
|
|
4786
4983
|
softObstacleIndex
|
|
4787
4984
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
4788
|
-
checkBacktracking(
|
|
4985
|
+
checkBacktracking(
|
|
4986
|
+
finalized,
|
|
4987
|
+
source,
|
|
4988
|
+
target,
|
|
4989
|
+
diagnostics,
|
|
4990
|
+
input.maxBacktrackingRatio
|
|
4991
|
+
);
|
|
4789
4992
|
return { points: finalized, diagnostics };
|
|
4790
4993
|
}
|
|
4994
|
+
recordRejected(finalized);
|
|
4995
|
+
if (cornerPath !== null) {
|
|
4996
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4997
|
+
source,
|
|
4998
|
+
target,
|
|
4999
|
+
allObstacles,
|
|
5000
|
+
{
|
|
5001
|
+
endpointObstacles,
|
|
5002
|
+
margin: 2,
|
|
5003
|
+
maxCorners: budget.maxCorners,
|
|
5004
|
+
corridorMargin
|
|
5005
|
+
},
|
|
5006
|
+
diagnostics
|
|
5007
|
+
) : null;
|
|
5008
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5009
|
+
const fullFinalized = finalizeRoute(
|
|
5010
|
+
fullCornerPath,
|
|
5011
|
+
softObstacles,
|
|
5012
|
+
hardObstacles,
|
|
5013
|
+
diagnostics,
|
|
5014
|
+
softObstacleIndex,
|
|
5015
|
+
hardObstacleIndex
|
|
5016
|
+
);
|
|
5017
|
+
if (!routeIntersectsObstacles(
|
|
5018
|
+
fullFinalized,
|
|
5019
|
+
softObstacles,
|
|
5020
|
+
softObstacleIndex
|
|
5021
|
+
) && !routeIntersectsObstacles(
|
|
5022
|
+
fullFinalized,
|
|
5023
|
+
hardObstacles,
|
|
5024
|
+
hardObstacleIndex
|
|
5025
|
+
)) {
|
|
5026
|
+
checkBacktracking(
|
|
5027
|
+
fullFinalized,
|
|
5028
|
+
source,
|
|
5029
|
+
target,
|
|
5030
|
+
diagnostics,
|
|
5031
|
+
input.maxBacktrackingRatio
|
|
5032
|
+
);
|
|
5033
|
+
return { points: fullFinalized, diagnostics };
|
|
5034
|
+
}
|
|
5035
|
+
recordRejected(fullFinalized);
|
|
5036
|
+
}
|
|
5037
|
+
const gridPath = findObstacleFreePath(
|
|
5038
|
+
source,
|
|
5039
|
+
target,
|
|
5040
|
+
allObstacles,
|
|
5041
|
+
{
|
|
5042
|
+
endpointObstacles,
|
|
5043
|
+
margin: 0,
|
|
5044
|
+
corridorMargin,
|
|
5045
|
+
maxNodes: budget.maxNodes
|
|
5046
|
+
},
|
|
5047
|
+
diagnostics
|
|
5048
|
+
);
|
|
5049
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5050
|
+
const gridFinalized = finalizeRoute(
|
|
5051
|
+
gridPath,
|
|
5052
|
+
softObstacles,
|
|
5053
|
+
hardObstacles,
|
|
5054
|
+
diagnostics,
|
|
5055
|
+
softObstacleIndex,
|
|
5056
|
+
hardObstacleIndex
|
|
5057
|
+
);
|
|
5058
|
+
if (!routeIntersectsObstacles(
|
|
5059
|
+
gridFinalized,
|
|
5060
|
+
softObstacles,
|
|
5061
|
+
softObstacleIndex
|
|
5062
|
+
) && !routeIntersectsObstacles(
|
|
5063
|
+
gridFinalized,
|
|
5064
|
+
hardObstacles,
|
|
5065
|
+
hardObstacleIndex
|
|
5066
|
+
)) {
|
|
5067
|
+
checkBacktracking(
|
|
5068
|
+
gridFinalized,
|
|
5069
|
+
source,
|
|
5070
|
+
target,
|
|
5071
|
+
diagnostics,
|
|
5072
|
+
input.maxBacktrackingRatio
|
|
5073
|
+
);
|
|
5074
|
+
return { points: gridFinalized, diagnostics };
|
|
5075
|
+
}
|
|
5076
|
+
recordRejected(gridFinalized);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
4791
5079
|
}
|
|
4792
5080
|
}
|
|
4793
5081
|
}
|
|
@@ -4849,7 +5137,8 @@ function routeEdge(input) {
|
|
|
4849
5137
|
finalizedClean,
|
|
4850
5138
|
candidate.points[0],
|
|
4851
5139
|
candidate.points[candidate.points.length - 1],
|
|
4852
|
-
diagnostics
|
|
5140
|
+
diagnostics,
|
|
5141
|
+
input.maxBacktrackingRatio
|
|
4853
5142
|
);
|
|
4854
5143
|
return { points: finalizedClean, diagnostics };
|
|
4855
5144
|
}
|
|
@@ -4915,13 +5204,41 @@ function routeEdge(input) {
|
|
|
4915
5204
|
code: "routing.obstacle.unavoidable",
|
|
4916
5205
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
4917
5206
|
});
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
5207
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5208
|
+
bestPoints2,
|
|
5209
|
+
softObstacles,
|
|
5210
|
+
hardObstacles,
|
|
5211
|
+
diagnostics
|
|
5212
|
+
);
|
|
5213
|
+
let softFallback = finalizedSoftBest;
|
|
5214
|
+
if (bestRejectedPath !== void 0) {
|
|
5215
|
+
const finalizedRejected = finalizeRoute(
|
|
5216
|
+
bestRejectedPath,
|
|
4921
5217
|
softObstacles,
|
|
4922
5218
|
hardObstacles,
|
|
4923
5219
|
diagnostics
|
|
4924
|
-
)
|
|
5220
|
+
);
|
|
5221
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5222
|
+
finalizedRejected,
|
|
5223
|
+
softObstacles
|
|
5224
|
+
);
|
|
5225
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5226
|
+
finalizedSoftBest,
|
|
5227
|
+
softObstacles
|
|
5228
|
+
);
|
|
5229
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5230
|
+
softFallback = finalizedRejected;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
checkBacktracking(
|
|
5234
|
+
softFallback,
|
|
5235
|
+
softFallback[0],
|
|
5236
|
+
softFallback[softFallback.length - 1],
|
|
5237
|
+
diagnostics,
|
|
5238
|
+
input.maxBacktrackingRatio
|
|
5239
|
+
);
|
|
5240
|
+
return {
|
|
5241
|
+
points: softFallback,
|
|
4925
5242
|
diagnostics
|
|
4926
5243
|
};
|
|
4927
5244
|
}
|
|
@@ -4953,6 +5270,22 @@ function routeEdge(input) {
|
|
|
4953
5270
|
maxAttempts
|
|
4954
5271
|
);
|
|
4955
5272
|
}
|
|
5273
|
+
if (bestRejectedPath !== void 0) {
|
|
5274
|
+
diagnostics.push({
|
|
5275
|
+
severity: "warning",
|
|
5276
|
+
code: "routing.obstacle.unavoidable",
|
|
5277
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5278
|
+
});
|
|
5279
|
+
return {
|
|
5280
|
+
points: finalizeRoute(
|
|
5281
|
+
bestRejectedPath,
|
|
5282
|
+
softObstacles,
|
|
5283
|
+
hardObstacles,
|
|
5284
|
+
diagnostics
|
|
5285
|
+
),
|
|
5286
|
+
diagnostics
|
|
5287
|
+
};
|
|
5288
|
+
}
|
|
4956
5289
|
diagnostics.push({
|
|
4957
5290
|
severity: "error",
|
|
4958
5291
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5000,13 +5333,41 @@ function routeEdge(input) {
|
|
|
5000
5333
|
code: "routing.obstacle.unavoidable",
|
|
5001
5334
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5002
5335
|
});
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5336
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5337
|
+
bestPoints,
|
|
5338
|
+
softObstacles,
|
|
5339
|
+
hardObstacles,
|
|
5340
|
+
diagnostics
|
|
5341
|
+
);
|
|
5342
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5343
|
+
if (bestRejectedPath !== void 0) {
|
|
5344
|
+
const finalizedRejected = finalizeRoute(
|
|
5345
|
+
bestRejectedPath,
|
|
5006
5346
|
softObstacles,
|
|
5007
5347
|
hardObstacles,
|
|
5008
5348
|
diagnostics
|
|
5009
|
-
)
|
|
5349
|
+
);
|
|
5350
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5351
|
+
finalizedRejected,
|
|
5352
|
+
softObstacles
|
|
5353
|
+
);
|
|
5354
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5355
|
+
finalizedBestPoints,
|
|
5356
|
+
softObstacles
|
|
5357
|
+
);
|
|
5358
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5359
|
+
fallbackPoints = finalizedRejected;
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
checkBacktracking(
|
|
5363
|
+
fallbackPoints,
|
|
5364
|
+
fallbackPoints[0],
|
|
5365
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5366
|
+
diagnostics,
|
|
5367
|
+
input.maxBacktrackingRatio
|
|
5368
|
+
);
|
|
5369
|
+
return {
|
|
5370
|
+
points: fallbackPoints,
|
|
5010
5371
|
diagnostics
|
|
5011
5372
|
};
|
|
5012
5373
|
}
|
|
@@ -5505,6 +5866,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
5505
5866
|
}
|
|
5506
5867
|
return false;
|
|
5507
5868
|
}
|
|
5869
|
+
function countObstacleCrossings(points, obstacles) {
|
|
5870
|
+
let count = 0;
|
|
5871
|
+
for (const obstacle of obstacles) {
|
|
5872
|
+
validateBox(obstacle);
|
|
5873
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
5874
|
+
const a = points[pointIndex];
|
|
5875
|
+
const b = points[pointIndex + 1];
|
|
5876
|
+
if (a === void 0 || b === void 0) {
|
|
5877
|
+
continue;
|
|
5878
|
+
}
|
|
5879
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
5880
|
+
count += 1;
|
|
5881
|
+
break;
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
return count;
|
|
5886
|
+
}
|
|
5508
5887
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
5509
5888
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5510
5889
|
const a = points[index];
|
|
@@ -5677,7 +6056,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5677
6056
|
edges: styledEdges
|
|
5678
6057
|
});
|
|
5679
6058
|
diagnostics.push(...layout2.diagnostics);
|
|
5680
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6059
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5681
6060
|
layout2.boxes,
|
|
5682
6061
|
styledNodes,
|
|
5683
6062
|
styledEdges,
|
|
@@ -5685,7 +6064,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5685
6064
|
options,
|
|
5686
6065
|
diagnostics
|
|
5687
6066
|
);
|
|
5688
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6067
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6068
|
+
const diagCountBefore = diagnostics.length;
|
|
5689
6069
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
5690
6070
|
initialNodeBoxes,
|
|
5691
6071
|
styledNodes,
|
|
@@ -5696,6 +6076,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5696
6076
|
for (const [id, box] of rewrapped) {
|
|
5697
6077
|
initialNodeBoxes.set(id, box);
|
|
5698
6078
|
}
|
|
6079
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6080
|
+
for (const node of styledNodes) {
|
|
6081
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6082
|
+
const rwBox = rewrapped.get(node.id);
|
|
6083
|
+
const idx = styledNodes.indexOf(node);
|
|
6084
|
+
if (idx !== -1) {
|
|
6085
|
+
styledNodes[idx] = {
|
|
6086
|
+
...node,
|
|
6087
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6088
|
+
};
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
5699
6093
|
}
|
|
5700
6094
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
5701
6095
|
const recursiveLayout = layout2;
|
|
@@ -5709,7 +6103,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5709
6103
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
5710
6104
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
5711
6105
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
5712
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6106
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
5713
6107
|
swimlanes: styledSwimlanes,
|
|
5714
6108
|
boxes: initialNodeBoxes,
|
|
5715
6109
|
nodes: styledNodes,
|
|
@@ -5724,7 +6118,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5724
6118
|
constrained.boxes,
|
|
5725
6119
|
constrained.locks,
|
|
5726
6120
|
options?.overlapSpacing ?? 40,
|
|
5727
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6121
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6122
|
+
options.distributeContainedChildren ?? true
|
|
5728
6123
|
);
|
|
5729
6124
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
5730
6125
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -5891,7 +6286,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5891
6286
|
diagram.direction,
|
|
5892
6287
|
options,
|
|
5893
6288
|
diagnostics,
|
|
5894
|
-
coordinatedGroups
|
|
6289
|
+
coordinatedGroups,
|
|
6290
|
+
contentBounds
|
|
5895
6291
|
);
|
|
5896
6292
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
5897
6293
|
coordinatedEdges,
|
|
@@ -6310,7 +6706,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
6310
6706
|
function containsCjk(value) {
|
|
6311
6707
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
6312
6708
|
}
|
|
6313
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
6709
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
6314
6710
|
const layouts = /* @__PURE__ */ new Map();
|
|
6315
6711
|
const diagnostics = [];
|
|
6316
6712
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -6329,7 +6725,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
6329
6725
|
locks,
|
|
6330
6726
|
diagnostics,
|
|
6331
6727
|
movedChildIds,
|
|
6332
|
-
laneGutter
|
|
6728
|
+
laneGutter,
|
|
6729
|
+
constraints,
|
|
6730
|
+
distributeContainedChildren
|
|
6333
6731
|
);
|
|
6334
6732
|
if (layout2 !== void 0) {
|
|
6335
6733
|
layouts.set(swimlane.id, layout2);
|
|
@@ -6425,9 +6823,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
6425
6823
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
6426
6824
|
return new Map(boxes);
|
|
6427
6825
|
}
|
|
6428
|
-
|
|
6429
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
6430
|
-
|
|
6826
|
+
let maxRowDepth = options.maxRowDepth;
|
|
6827
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
6828
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
6829
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
6830
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
6831
|
+
} else {
|
|
6832
|
+
return new Map(boxes);
|
|
6833
|
+
}
|
|
6431
6834
|
}
|
|
6432
6835
|
const ordered = [...nodes].sort((a, b) => {
|
|
6433
6836
|
const ba = boxes.get(a.id);
|
|
@@ -6488,10 +6891,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
6488
6891
|
});
|
|
6489
6892
|
}
|
|
6490
6893
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
6491
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
6894
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
6492
6895
|
return false;
|
|
6493
6896
|
}
|
|
6494
|
-
if (nodes.length < 2
|
|
6897
|
+
if (nodes.length < 2) {
|
|
6495
6898
|
return false;
|
|
6496
6899
|
}
|
|
6497
6900
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -6499,17 +6902,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6499
6902
|
return false;
|
|
6500
6903
|
}
|
|
6501
6904
|
const bounds = unionBoxes(nodeBoxes);
|
|
6502
|
-
const
|
|
6503
|
-
const
|
|
6504
|
-
|
|
6905
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
6906
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
6907
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
6908
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
6505
6909
|
return false;
|
|
6506
6910
|
}
|
|
6911
|
+
if (isHorizontal) {
|
|
6912
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
6913
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
6914
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
6915
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
6916
|
+
}
|
|
6507
6917
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
6508
6918
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
6509
6919
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
6510
6920
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
6511
6921
|
}
|
|
6512
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6922
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6513
6923
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
6514
6924
|
const padding = swimlane.padding ?? 16;
|
|
6515
6925
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -6534,7 +6944,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6534
6944
|
locks,
|
|
6535
6945
|
diagnostics,
|
|
6536
6946
|
movedChildIds,
|
|
6537
|
-
laneGutter
|
|
6947
|
+
laneGutter,
|
|
6948
|
+
constraints,
|
|
6949
|
+
distributeContainedChildren
|
|
6538
6950
|
);
|
|
6539
6951
|
}
|
|
6540
6952
|
return applyHorizontalSwimlaneContract(
|
|
@@ -6549,13 +6961,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6549
6961
|
laneGutter
|
|
6550
6962
|
);
|
|
6551
6963
|
}
|
|
6552
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6964
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6553
6965
|
const populatedBounds = laneBounds.filter(
|
|
6554
6966
|
(box) => box !== void 0
|
|
6555
6967
|
);
|
|
6556
6968
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
6557
6969
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
6558
6970
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
6971
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
6972
|
+
if (distributeContainedChildren) {
|
|
6973
|
+
for (const c of constraints) {
|
|
6974
|
+
if (c.kind !== "containment") continue;
|
|
6975
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
6976
|
+
const distributable = c.childIds.filter((childId) => {
|
|
6977
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
6978
|
+
const lock = locks.get(childId);
|
|
6979
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
6980
|
+
});
|
|
6981
|
+
if (distributable.length < 2) continue;
|
|
6982
|
+
for (const childId of distributable) {
|
|
6983
|
+
containedChildIds.add(childId);
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6559
6987
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
6560
6988
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
6561
6989
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -6567,7 +6995,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6567
6995
|
);
|
|
6568
6996
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
6569
6997
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
6570
|
-
const
|
|
6998
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
6999
|
+
swimlane,
|
|
7000
|
+
nodeBoxes,
|
|
7001
|
+
flowRanks,
|
|
7002
|
+
locks,
|
|
7003
|
+
rankStackGap,
|
|
7004
|
+
containedChildIds
|
|
7005
|
+
);
|
|
7006
|
+
const slotWidth = Math.max(
|
|
7007
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7008
|
+
spreadWidth
|
|
7009
|
+
) + padding * 2;
|
|
6571
7010
|
const laneStep = slotWidth + laneGutter;
|
|
6572
7011
|
const laneContentTop = top + headerHeight + padding;
|
|
6573
7012
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -6581,6 +7020,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6581
7020
|
y: laneContentTop
|
|
6582
7021
|
};
|
|
6583
7022
|
if (maxRank === 0) {
|
|
7023
|
+
const distributable = lane.children.filter(
|
|
7024
|
+
(childId) => !locks.has(childId)
|
|
7025
|
+
);
|
|
7026
|
+
const coveredByContainment = lane.children.some(
|
|
7027
|
+
(childId) => containedChildIds.has(childId)
|
|
7028
|
+
);
|
|
7029
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7030
|
+
moveRankedVerticalLaneChildren(
|
|
7031
|
+
lane.children,
|
|
7032
|
+
nodeBoxes,
|
|
7033
|
+
locks,
|
|
7034
|
+
diagnostics,
|
|
7035
|
+
movedChildIds,
|
|
7036
|
+
flowRanks,
|
|
7037
|
+
rankSpacing,
|
|
7038
|
+
rankStackGap,
|
|
7039
|
+
{ x: target.x, y: laneContentTop },
|
|
7040
|
+
slotWidth - padding * 2
|
|
7041
|
+
);
|
|
7042
|
+
continue;
|
|
7043
|
+
}
|
|
6584
7044
|
moveLaneChildren(
|
|
6585
7045
|
lane.children,
|
|
6586
7046
|
nodeBoxes,
|
|
@@ -6594,6 +7054,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6594
7054
|
);
|
|
6595
7055
|
continue;
|
|
6596
7056
|
}
|
|
7057
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7058
|
+
(childId) => containedChildIds.has(childId)
|
|
7059
|
+
);
|
|
6597
7060
|
moveRankedVerticalLaneChildren(
|
|
6598
7061
|
lane.children,
|
|
6599
7062
|
nodeBoxes,
|
|
@@ -6603,10 +7066,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6603
7066
|
flowRanks,
|
|
6604
7067
|
rankSpacing,
|
|
6605
7068
|
rankStackGap,
|
|
6606
|
-
{
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
}
|
|
7069
|
+
{ x: target.x, y: laneContentTop },
|
|
7070
|
+
slotWidth - padding * 2,
|
|
7071
|
+
rankedCoveredByContainment
|
|
6610
7072
|
);
|
|
6611
7073
|
}
|
|
6612
7074
|
return {
|
|
@@ -6715,31 +7177,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
6715
7177
|
}
|
|
6716
7178
|
return maxHeight;
|
|
6717
7179
|
}
|
|
6718
|
-
|
|
7180
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7181
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7182
|
+
return items.reduce(
|
|
7183
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7184
|
+
0
|
|
7185
|
+
);
|
|
7186
|
+
}
|
|
7187
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7188
|
+
let maxWidth = 0;
|
|
7189
|
+
for (const lane of swimlane.lanes) {
|
|
7190
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7191
|
+
continue;
|
|
7192
|
+
}
|
|
7193
|
+
for (const stack of rankStacks(
|
|
7194
|
+
lane.children,
|
|
7195
|
+
nodeBoxes,
|
|
7196
|
+
flowRanks
|
|
7197
|
+
).values()) {
|
|
7198
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7199
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7200
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7203
|
+
return maxWidth;
|
|
7204
|
+
}
|
|
7205
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
6719
7206
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6720
|
-
|
|
7207
|
+
const unlocked = [];
|
|
6721
7208
|
for (const item of stack) {
|
|
6722
|
-
|
|
6723
|
-
if (locks.has(childId)) {
|
|
7209
|
+
if (locks.has(item.childId)) {
|
|
6724
7210
|
diagnostics.push({
|
|
6725
7211
|
severity: "warning",
|
|
6726
7212
|
code: "constraints.locked-target-not-moved",
|
|
6727
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7213
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
6728
7214
|
path: ["swimlanes"],
|
|
6729
|
-
detail: { nodeId: childId }
|
|
7215
|
+
detail: { nodeId: item.childId }
|
|
6730
7216
|
});
|
|
6731
|
-
|
|
7217
|
+
} else {
|
|
7218
|
+
unlocked.push(item);
|
|
6732
7219
|
}
|
|
7220
|
+
}
|
|
7221
|
+
if (unlocked.length === 0) continue;
|
|
7222
|
+
if (unlocked.length === 1) {
|
|
7223
|
+
const { childId, box } = unlocked[0];
|
|
6733
7224
|
const next = {
|
|
6734
7225
|
...box,
|
|
6735
|
-
x:
|
|
6736
|
-
y: target.y + rank * rankSpacing
|
|
7226
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7227
|
+
y: target.y + rank * rankSpacing
|
|
6737
7228
|
};
|
|
6738
7229
|
if (next.x !== box.x || next.y !== box.y) {
|
|
6739
7230
|
movedChildIds.add(childId);
|
|
6740
7231
|
}
|
|
6741
7232
|
nodeBoxes.set(childId, next);
|
|
6742
|
-
|
|
7233
|
+
} else {
|
|
7234
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7235
|
+
if (!shouldSpread) {
|
|
7236
|
+
let yOffset = 0;
|
|
7237
|
+
for (const { childId, box } of unlocked) {
|
|
7238
|
+
const next = {
|
|
7239
|
+
...box,
|
|
7240
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7241
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7242
|
+
};
|
|
7243
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7244
|
+
movedChildIds.add(childId);
|
|
7245
|
+
}
|
|
7246
|
+
nodeBoxes.set(childId, next);
|
|
7247
|
+
yOffset += box.height + rankStackGap;
|
|
7248
|
+
}
|
|
7249
|
+
} else {
|
|
7250
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
7251
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
7252
|
+
for (const { childId, box } of unlocked) {
|
|
7253
|
+
const next = {
|
|
7254
|
+
...box,
|
|
7255
|
+
x: xCursor,
|
|
7256
|
+
y: target.y + rank * rankSpacing
|
|
7257
|
+
};
|
|
7258
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7259
|
+
movedChildIds.add(childId);
|
|
7260
|
+
}
|
|
7261
|
+
nodeBoxes.set(childId, next);
|
|
7262
|
+
xCursor += box.width + rankStackGap;
|
|
7263
|
+
}
|
|
7264
|
+
diagnostics.push({
|
|
7265
|
+
severity: "info",
|
|
7266
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
7267
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
7268
|
+
path: ["swimlanes"],
|
|
7269
|
+
detail: {
|
|
7270
|
+
rank,
|
|
7271
|
+
childCount: unlocked.length,
|
|
7272
|
+
contentWidth
|
|
7273
|
+
}
|
|
7274
|
+
});
|
|
7275
|
+
}
|
|
6743
7276
|
}
|
|
6744
7277
|
}
|
|
6745
7278
|
}
|
|
@@ -7078,7 +7611,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7078
7611
|
});
|
|
7079
7612
|
continue;
|
|
7080
7613
|
}
|
|
7081
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
7614
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7082
7615
|
const geometry = computeShapeGeometry({
|
|
7083
7616
|
shape: node.shape,
|
|
7084
7617
|
box,
|
|
@@ -7172,7 +7705,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
7172
7705
|
}
|
|
7173
7706
|
}
|
|
7174
7707
|
}
|
|
7175
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
7708
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
7176
7709
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
7177
7710
|
for (const port of node.ports ?? []) {
|
|
7178
7711
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -7195,9 +7728,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7195
7728
|
side,
|
|
7196
7729
|
index,
|
|
7197
7730
|
sorted.length,
|
|
7198
|
-
portShifting
|
|
7199
|
-
diagnostics,
|
|
7200
|
-
node.id
|
|
7731
|
+
portShifting
|
|
7201
7732
|
);
|
|
7202
7733
|
const box = portBox(anchor);
|
|
7203
7734
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -7205,32 +7736,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7205
7736
|
}
|
|
7206
7737
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
7207
7738
|
}
|
|
7208
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
7739
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
7209
7740
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
7210
7741
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
7211
7742
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
7212
7743
|
const availableSpan = 2 * maxOffset;
|
|
7213
7744
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
7214
|
-
const
|
|
7745
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
7215
7746
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
7216
7747
|
minSpacing
|
|
7217
7748
|
) : 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
7749
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
7235
7750
|
switch (side) {
|
|
7236
7751
|
case "left":
|
|
@@ -7845,14 +8360,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
7845
8360
|
}
|
|
7846
8361
|
};
|
|
7847
8362
|
}
|
|
7848
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
8363
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
7849
8364
|
const coordinated = [];
|
|
7850
8365
|
const coordinatedNodeById = new Map(
|
|
7851
8366
|
coordinatedNodes.map((node) => [node.id, node])
|
|
7852
8367
|
);
|
|
8368
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
8369
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
8370
|
+
200,
|
|
8371
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
8372
|
+
);
|
|
8373
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
8374
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
7853
8375
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
7854
8376
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
7855
|
-
|
|
8377
|
+
queryGutter
|
|
7856
8378
|
);
|
|
7857
8379
|
for (const edge of edges) {
|
|
7858
8380
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -7874,11 +8396,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7874
8396
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
7875
8397
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
7876
8398
|
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
|
-
);
|
|
8399
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
7882
8400
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
7883
8401
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
7884
8402
|
);
|
|
@@ -7896,7 +8414,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7896
8414
|
...routeTextObstacles
|
|
7897
8415
|
],
|
|
7898
8416
|
hardObstacles,
|
|
7899
|
-
|
|
8417
|
+
corridorMargin,
|
|
8418
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
8419
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
7900
8420
|
});
|
|
7901
8421
|
diagnostics.push(
|
|
7902
8422
|
...route.diagnostics.map((diagnostic) => ({
|