@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.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
|
}
|
|
@@ -4141,7 +4161,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4141
4161
|
detail: {
|
|
4142
4162
|
xsCount: xs.length,
|
|
4143
4163
|
ysCount: ys.length,
|
|
4144
|
-
maxNodes
|
|
4164
|
+
maxNodes,
|
|
4165
|
+
obstacleCount: obstacles.length,
|
|
4166
|
+
stage: "corridor-filtered",
|
|
4167
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4145
4168
|
}
|
|
4146
4169
|
});
|
|
4147
4170
|
return null;
|
|
@@ -4157,8 +4180,66 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4157
4180
|
turnPenalty,
|
|
4158
4181
|
segmentPenalty
|
|
4159
4182
|
);
|
|
4160
|
-
if (path
|
|
4161
|
-
|
|
4183
|
+
if (path !== null) {
|
|
4184
|
+
const simplified = simplifyRoute(path);
|
|
4185
|
+
const filteredSet = new Set(filtered);
|
|
4186
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4187
|
+
let crossesExcluded = false;
|
|
4188
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4189
|
+
const a = simplified[i];
|
|
4190
|
+
const b = simplified[i + 1];
|
|
4191
|
+
for (const obs of obstacles) {
|
|
4192
|
+
if (filteredSet.has(obs)) continue;
|
|
4193
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4194
|
+
crossesExcluded = true;
|
|
4195
|
+
break;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
if (crossesExcluded) break;
|
|
4199
|
+
}
|
|
4200
|
+
if (!crossesExcluded) return simplified;
|
|
4201
|
+
} else {
|
|
4202
|
+
return simplified;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (!useCorridor) return null;
|
|
4206
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4207
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4208
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4209
|
+
diagnostics?.push({
|
|
4210
|
+
severity: "warning",
|
|
4211
|
+
code: "routing.astar.grid_overflow",
|
|
4212
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4213
|
+
detail: {
|
|
4214
|
+
xsCount: xsFull.length,
|
|
4215
|
+
ysCount: ysFull.length,
|
|
4216
|
+
maxNodes,
|
|
4217
|
+
obstacleCount: obstacles.length,
|
|
4218
|
+
stage: "full-retry",
|
|
4219
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4220
|
+
}
|
|
4221
|
+
});
|
|
4222
|
+
return null;
|
|
4223
|
+
}
|
|
4224
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4225
|
+
connectHorizontalEdges(
|
|
4226
|
+
nodesFull,
|
|
4227
|
+
ysFull,
|
|
4228
|
+
obstacles,
|
|
4229
|
+
endpointObstacles,
|
|
4230
|
+
margin
|
|
4231
|
+
);
|
|
4232
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4233
|
+
const pathFull = aStarSearch(
|
|
4234
|
+
nodesFull,
|
|
4235
|
+
idxFull,
|
|
4236
|
+
source,
|
|
4237
|
+
target,
|
|
4238
|
+
turnPenalty,
|
|
4239
|
+
segmentPenalty
|
|
4240
|
+
);
|
|
4241
|
+
if (pathFull === null) return null;
|
|
4242
|
+
return simplifyRoute(pathFull);
|
|
4162
4243
|
}
|
|
4163
4244
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4164
4245
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -4379,20 +4460,83 @@ function areCollinear(a, b, c) {
|
|
|
4379
4460
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4380
4461
|
}
|
|
4381
4462
|
|
|
4463
|
+
// src/routing/budget.ts
|
|
4464
|
+
var MIN_CORNER_BUDGET = 600;
|
|
4465
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
4466
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
4467
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
4468
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
4469
|
+
var CORNER_HEADROOM = 2;
|
|
4470
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
4471
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
4472
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
4473
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
4474
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
4475
|
+
cornerObstacles.length,
|
|
4476
|
+
corridorMargin
|
|
4477
|
+
);
|
|
4478
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
4479
|
+
allObstacles.length,
|
|
4480
|
+
corridorMargin
|
|
4481
|
+
);
|
|
4482
|
+
return {
|
|
4483
|
+
maxCorners: resolveBudget(
|
|
4484
|
+
overrides.maxCorners,
|
|
4485
|
+
adaptiveMaxCorners,
|
|
4486
|
+
MIN_CORNER_BUDGET,
|
|
4487
|
+
MAX_CORNER_BUDGET
|
|
4488
|
+
),
|
|
4489
|
+
maxNodes: resolveBudget(
|
|
4490
|
+
overrides.maxNodes,
|
|
4491
|
+
adaptiveMaxNodes,
|
|
4492
|
+
MIN_NODE_BUDGET,
|
|
4493
|
+
MAX_NODE_BUDGET
|
|
4494
|
+
),
|
|
4495
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
4496
|
+
gridObstacleCount: allObstacles.length,
|
|
4497
|
+
corridorMargin
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
4501
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
4502
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4503
|
+
return Math.ceil(base * corridorFactor);
|
|
4504
|
+
}
|
|
4505
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
4506
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
4507
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4508
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
4509
|
+
}
|
|
4510
|
+
function corridorScalingFactor(corridorMargin) {
|
|
4511
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
4512
|
+
}
|
|
4513
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
4514
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
4515
|
+
return clamp(chosen, min, max);
|
|
4516
|
+
}
|
|
4517
|
+
function clamp(value, min, max) {
|
|
4518
|
+
return Math.max(min, Math.min(max, value));
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4382
4521
|
// src/routing/visibility-router.ts
|
|
4383
4522
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
4384
4523
|
const margin = options.margin ?? 0;
|
|
4385
4524
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
4386
4525
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
4387
4526
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
4388
|
-
const maxCorners = options.maxCorners ??
|
|
4527
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
4389
4528
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
4390
4529
|
if (vertices.length > maxCorners) {
|
|
4391
4530
|
diagnostics?.push({
|
|
4392
4531
|
severity: "warning",
|
|
4393
4532
|
code: "routing.visibility.corner_overflow",
|
|
4394
4533
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
4395
|
-
detail: {
|
|
4534
|
+
detail: {
|
|
4535
|
+
vertexCount: vertices.length,
|
|
4536
|
+
maxCorners,
|
|
4537
|
+
obstacleCount: obstacles.length,
|
|
4538
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
4539
|
+
}
|
|
4396
4540
|
});
|
|
4397
4541
|
return null;
|
|
4398
4542
|
}
|
|
@@ -4666,7 +4810,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
4666
4810
|
}
|
|
4667
4811
|
|
|
4668
4812
|
// src/routing/routes.ts
|
|
4669
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
4813
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
4670
4814
|
if (points.length < 2) return;
|
|
4671
4815
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
4672
4816
|
if (direct <= 0) return;
|
|
@@ -4676,7 +4820,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
4676
4820
|
const b = points[i + 1];
|
|
4677
4821
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
4678
4822
|
}
|
|
4679
|
-
const threshold =
|
|
4823
|
+
const threshold = maxRatio ?? 20;
|
|
4680
4824
|
if (routeLen > direct * threshold) {
|
|
4681
4825
|
diagnostics.push({
|
|
4682
4826
|
severity: "warning",
|
|
@@ -4694,8 +4838,20 @@ function routeEdge(input) {
|
|
|
4694
4838
|
const diagnostics = [];
|
|
4695
4839
|
const softObstacles = input.obstacles ?? [];
|
|
4696
4840
|
const hardObstacles = input.hardObstacles ?? [];
|
|
4841
|
+
let bestRejectedPath;
|
|
4842
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
4697
4843
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
4698
4844
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
4845
|
+
const recordRejected = (candidate) => {
|
|
4846
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
4849
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
4850
|
+
if (crossings < bestRejectedCrossings) {
|
|
4851
|
+
bestRejectedCrossings = crossings;
|
|
4852
|
+
bestRejectedPath = candidate;
|
|
4853
|
+
}
|
|
4854
|
+
};
|
|
4699
4855
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
4700
4856
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
4701
4857
|
input.source.box,
|
|
@@ -4754,18 +4910,59 @@ function routeEdge(input) {
|
|
|
4754
4910
|
input.source.center,
|
|
4755
4911
|
targetAnchor
|
|
4756
4912
|
);
|
|
4757
|
-
const
|
|
4913
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4914
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
4915
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
4758
4916
|
source,
|
|
4759
4917
|
target,
|
|
4760
|
-
|
|
4761
|
-
|
|
4918
|
+
allObstacles,
|
|
4919
|
+
[],
|
|
4920
|
+
// endpointObstacles passed separately via options
|
|
4921
|
+
corridorMargin
|
|
4922
|
+
);
|
|
4923
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4924
|
+
const budget = computeRoutingBudget(
|
|
4925
|
+
cornerObstacles,
|
|
4926
|
+
allObstacles,
|
|
4927
|
+
corridorMargin,
|
|
4928
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
4929
|
+
);
|
|
4930
|
+
let cornerPath = findCornerGraphPath(
|
|
4931
|
+
source,
|
|
4932
|
+
target,
|
|
4933
|
+
cornerObstacles,
|
|
4934
|
+
{
|
|
4935
|
+
endpointObstacles,
|
|
4936
|
+
margin: 2,
|
|
4937
|
+
maxCorners: budget.maxCorners,
|
|
4938
|
+
corridorMargin
|
|
4939
|
+
},
|
|
4762
4940
|
diagnostics
|
|
4763
4941
|
);
|
|
4942
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
4943
|
+
cornerPath = findCornerGraphPath(
|
|
4944
|
+
source,
|
|
4945
|
+
target,
|
|
4946
|
+
allObstacles,
|
|
4947
|
+
{
|
|
4948
|
+
endpointObstacles,
|
|
4949
|
+
margin: 2,
|
|
4950
|
+
maxCorners: budget.maxCorners,
|
|
4951
|
+
corridorMargin
|
|
4952
|
+
},
|
|
4953
|
+
diagnostics
|
|
4954
|
+
);
|
|
4955
|
+
}
|
|
4764
4956
|
const path = cornerPath ?? findObstacleFreePath(
|
|
4765
4957
|
source,
|
|
4766
4958
|
target,
|
|
4767
|
-
|
|
4768
|
-
{
|
|
4959
|
+
allObstacles,
|
|
4960
|
+
{
|
|
4961
|
+
endpointObstacles,
|
|
4962
|
+
margin: 0,
|
|
4963
|
+
corridorMargin,
|
|
4964
|
+
maxNodes: budget.maxNodes
|
|
4965
|
+
},
|
|
4769
4966
|
diagnostics
|
|
4770
4967
|
);
|
|
4771
4968
|
if (path !== null && path.length >= 2) {
|
|
@@ -4782,9 +4979,100 @@ function routeEdge(input) {
|
|
|
4782
4979
|
softObstacles,
|
|
4783
4980
|
softObstacleIndex
|
|
4784
4981
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
4785
|
-
checkBacktracking(
|
|
4982
|
+
checkBacktracking(
|
|
4983
|
+
finalized,
|
|
4984
|
+
source,
|
|
4985
|
+
target,
|
|
4986
|
+
diagnostics,
|
|
4987
|
+
input.maxBacktrackingRatio
|
|
4988
|
+
);
|
|
4786
4989
|
return { points: finalized, diagnostics };
|
|
4787
4990
|
}
|
|
4991
|
+
recordRejected(finalized);
|
|
4992
|
+
if (cornerPath !== null) {
|
|
4993
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4994
|
+
source,
|
|
4995
|
+
target,
|
|
4996
|
+
allObstacles,
|
|
4997
|
+
{
|
|
4998
|
+
endpointObstacles,
|
|
4999
|
+
margin: 2,
|
|
5000
|
+
maxCorners: budget.maxCorners,
|
|
5001
|
+
corridorMargin
|
|
5002
|
+
},
|
|
5003
|
+
diagnostics
|
|
5004
|
+
) : null;
|
|
5005
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5006
|
+
const fullFinalized = finalizeRoute(
|
|
5007
|
+
fullCornerPath,
|
|
5008
|
+
softObstacles,
|
|
5009
|
+
hardObstacles,
|
|
5010
|
+
diagnostics,
|
|
5011
|
+
softObstacleIndex,
|
|
5012
|
+
hardObstacleIndex
|
|
5013
|
+
);
|
|
5014
|
+
if (!routeIntersectsObstacles(
|
|
5015
|
+
fullFinalized,
|
|
5016
|
+
softObstacles,
|
|
5017
|
+
softObstacleIndex
|
|
5018
|
+
) && !routeIntersectsObstacles(
|
|
5019
|
+
fullFinalized,
|
|
5020
|
+
hardObstacles,
|
|
5021
|
+
hardObstacleIndex
|
|
5022
|
+
)) {
|
|
5023
|
+
checkBacktracking(
|
|
5024
|
+
fullFinalized,
|
|
5025
|
+
source,
|
|
5026
|
+
target,
|
|
5027
|
+
diagnostics,
|
|
5028
|
+
input.maxBacktrackingRatio
|
|
5029
|
+
);
|
|
5030
|
+
return { points: fullFinalized, diagnostics };
|
|
5031
|
+
}
|
|
5032
|
+
recordRejected(fullFinalized);
|
|
5033
|
+
}
|
|
5034
|
+
const gridPath = findObstacleFreePath(
|
|
5035
|
+
source,
|
|
5036
|
+
target,
|
|
5037
|
+
allObstacles,
|
|
5038
|
+
{
|
|
5039
|
+
endpointObstacles,
|
|
5040
|
+
margin: 0,
|
|
5041
|
+
corridorMargin,
|
|
5042
|
+
maxNodes: budget.maxNodes
|
|
5043
|
+
},
|
|
5044
|
+
diagnostics
|
|
5045
|
+
);
|
|
5046
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5047
|
+
const gridFinalized = finalizeRoute(
|
|
5048
|
+
gridPath,
|
|
5049
|
+
softObstacles,
|
|
5050
|
+
hardObstacles,
|
|
5051
|
+
diagnostics,
|
|
5052
|
+
softObstacleIndex,
|
|
5053
|
+
hardObstacleIndex
|
|
5054
|
+
);
|
|
5055
|
+
if (!routeIntersectsObstacles(
|
|
5056
|
+
gridFinalized,
|
|
5057
|
+
softObstacles,
|
|
5058
|
+
softObstacleIndex
|
|
5059
|
+
) && !routeIntersectsObstacles(
|
|
5060
|
+
gridFinalized,
|
|
5061
|
+
hardObstacles,
|
|
5062
|
+
hardObstacleIndex
|
|
5063
|
+
)) {
|
|
5064
|
+
checkBacktracking(
|
|
5065
|
+
gridFinalized,
|
|
5066
|
+
source,
|
|
5067
|
+
target,
|
|
5068
|
+
diagnostics,
|
|
5069
|
+
input.maxBacktrackingRatio
|
|
5070
|
+
);
|
|
5071
|
+
return { points: gridFinalized, diagnostics };
|
|
5072
|
+
}
|
|
5073
|
+
recordRejected(gridFinalized);
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
4788
5076
|
}
|
|
4789
5077
|
}
|
|
4790
5078
|
}
|
|
@@ -4846,7 +5134,8 @@ function routeEdge(input) {
|
|
|
4846
5134
|
finalizedClean,
|
|
4847
5135
|
candidate.points[0],
|
|
4848
5136
|
candidate.points[candidate.points.length - 1],
|
|
4849
|
-
diagnostics
|
|
5137
|
+
diagnostics,
|
|
5138
|
+
input.maxBacktrackingRatio
|
|
4850
5139
|
);
|
|
4851
5140
|
return { points: finalizedClean, diagnostics };
|
|
4852
5141
|
}
|
|
@@ -4912,13 +5201,41 @@ function routeEdge(input) {
|
|
|
4912
5201
|
code: "routing.obstacle.unavoidable",
|
|
4913
5202
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
4914
5203
|
});
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
5204
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5205
|
+
bestPoints2,
|
|
5206
|
+
softObstacles,
|
|
5207
|
+
hardObstacles,
|
|
5208
|
+
diagnostics
|
|
5209
|
+
);
|
|
5210
|
+
let softFallback = finalizedSoftBest;
|
|
5211
|
+
if (bestRejectedPath !== void 0) {
|
|
5212
|
+
const finalizedRejected = finalizeRoute(
|
|
5213
|
+
bestRejectedPath,
|
|
4918
5214
|
softObstacles,
|
|
4919
5215
|
hardObstacles,
|
|
4920
5216
|
diagnostics
|
|
4921
|
-
)
|
|
5217
|
+
);
|
|
5218
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5219
|
+
finalizedRejected,
|
|
5220
|
+
softObstacles
|
|
5221
|
+
);
|
|
5222
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5223
|
+
finalizedSoftBest,
|
|
5224
|
+
softObstacles
|
|
5225
|
+
);
|
|
5226
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5227
|
+
softFallback = finalizedRejected;
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
checkBacktracking(
|
|
5231
|
+
softFallback,
|
|
5232
|
+
softFallback[0],
|
|
5233
|
+
softFallback[softFallback.length - 1],
|
|
5234
|
+
diagnostics,
|
|
5235
|
+
input.maxBacktrackingRatio
|
|
5236
|
+
);
|
|
5237
|
+
return {
|
|
5238
|
+
points: softFallback,
|
|
4922
5239
|
diagnostics
|
|
4923
5240
|
};
|
|
4924
5241
|
}
|
|
@@ -4950,6 +5267,22 @@ function routeEdge(input) {
|
|
|
4950
5267
|
maxAttempts
|
|
4951
5268
|
);
|
|
4952
5269
|
}
|
|
5270
|
+
if (bestRejectedPath !== void 0) {
|
|
5271
|
+
diagnostics.push({
|
|
5272
|
+
severity: "warning",
|
|
5273
|
+
code: "routing.obstacle.unavoidable",
|
|
5274
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5275
|
+
});
|
|
5276
|
+
return {
|
|
5277
|
+
points: finalizeRoute(
|
|
5278
|
+
bestRejectedPath,
|
|
5279
|
+
softObstacles,
|
|
5280
|
+
hardObstacles,
|
|
5281
|
+
diagnostics
|
|
5282
|
+
),
|
|
5283
|
+
diagnostics
|
|
5284
|
+
};
|
|
5285
|
+
}
|
|
4953
5286
|
diagnostics.push({
|
|
4954
5287
|
severity: "error",
|
|
4955
5288
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -4997,13 +5330,41 @@ function routeEdge(input) {
|
|
|
4997
5330
|
code: "routing.obstacle.unavoidable",
|
|
4998
5331
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
4999
5332
|
});
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5333
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5334
|
+
bestPoints,
|
|
5335
|
+
softObstacles,
|
|
5336
|
+
hardObstacles,
|
|
5337
|
+
diagnostics
|
|
5338
|
+
);
|
|
5339
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5340
|
+
if (bestRejectedPath !== void 0) {
|
|
5341
|
+
const finalizedRejected = finalizeRoute(
|
|
5342
|
+
bestRejectedPath,
|
|
5003
5343
|
softObstacles,
|
|
5004
5344
|
hardObstacles,
|
|
5005
5345
|
diagnostics
|
|
5006
|
-
)
|
|
5346
|
+
);
|
|
5347
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5348
|
+
finalizedRejected,
|
|
5349
|
+
softObstacles
|
|
5350
|
+
);
|
|
5351
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5352
|
+
finalizedBestPoints,
|
|
5353
|
+
softObstacles
|
|
5354
|
+
);
|
|
5355
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5356
|
+
fallbackPoints = finalizedRejected;
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
checkBacktracking(
|
|
5360
|
+
fallbackPoints,
|
|
5361
|
+
fallbackPoints[0],
|
|
5362
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5363
|
+
diagnostics,
|
|
5364
|
+
input.maxBacktrackingRatio
|
|
5365
|
+
);
|
|
5366
|
+
return {
|
|
5367
|
+
points: fallbackPoints,
|
|
5007
5368
|
diagnostics
|
|
5008
5369
|
};
|
|
5009
5370
|
}
|
|
@@ -5502,6 +5863,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
5502
5863
|
}
|
|
5503
5864
|
return false;
|
|
5504
5865
|
}
|
|
5866
|
+
function countObstacleCrossings(points, obstacles) {
|
|
5867
|
+
let count = 0;
|
|
5868
|
+
for (const obstacle of obstacles) {
|
|
5869
|
+
validateBox(obstacle);
|
|
5870
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
5871
|
+
const a = points[pointIndex];
|
|
5872
|
+
const b = points[pointIndex + 1];
|
|
5873
|
+
if (a === void 0 || b === void 0) {
|
|
5874
|
+
continue;
|
|
5875
|
+
}
|
|
5876
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
5877
|
+
count += 1;
|
|
5878
|
+
break;
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
return count;
|
|
5883
|
+
}
|
|
5505
5884
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
5506
5885
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5507
5886
|
const a = points[index];
|
|
@@ -5674,7 +6053,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5674
6053
|
edges: styledEdges
|
|
5675
6054
|
});
|
|
5676
6055
|
diagnostics.push(...layout2.diagnostics);
|
|
5677
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6056
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5678
6057
|
layout2.boxes,
|
|
5679
6058
|
styledNodes,
|
|
5680
6059
|
styledEdges,
|
|
@@ -5682,7 +6061,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5682
6061
|
options,
|
|
5683
6062
|
diagnostics
|
|
5684
6063
|
);
|
|
5685
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6064
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6065
|
+
const diagCountBefore = diagnostics.length;
|
|
5686
6066
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
5687
6067
|
initialNodeBoxes,
|
|
5688
6068
|
styledNodes,
|
|
@@ -5693,6 +6073,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5693
6073
|
for (const [id, box] of rewrapped) {
|
|
5694
6074
|
initialNodeBoxes.set(id, box);
|
|
5695
6075
|
}
|
|
6076
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6077
|
+
for (const node of styledNodes) {
|
|
6078
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6079
|
+
const rwBox = rewrapped.get(node.id);
|
|
6080
|
+
const idx = styledNodes.indexOf(node);
|
|
6081
|
+
if (idx !== -1) {
|
|
6082
|
+
styledNodes[idx] = {
|
|
6083
|
+
...node,
|
|
6084
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6085
|
+
};
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
5696
6090
|
}
|
|
5697
6091
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
5698
6092
|
const recursiveLayout = layout2;
|
|
@@ -5706,7 +6100,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5706
6100
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
5707
6101
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
5708
6102
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
5709
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6103
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
5710
6104
|
swimlanes: styledSwimlanes,
|
|
5711
6105
|
boxes: initialNodeBoxes,
|
|
5712
6106
|
nodes: styledNodes,
|
|
@@ -5721,7 +6115,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5721
6115
|
constrained.boxes,
|
|
5722
6116
|
constrained.locks,
|
|
5723
6117
|
options?.overlapSpacing ?? 40,
|
|
5724
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6118
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6119
|
+
options.distributeContainedChildren ?? true
|
|
5725
6120
|
);
|
|
5726
6121
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
5727
6122
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -5888,7 +6283,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5888
6283
|
diagram.direction,
|
|
5889
6284
|
options,
|
|
5890
6285
|
diagnostics,
|
|
5891
|
-
coordinatedGroups
|
|
6286
|
+
coordinatedGroups,
|
|
6287
|
+
contentBounds
|
|
5892
6288
|
);
|
|
5893
6289
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
5894
6290
|
coordinatedEdges,
|
|
@@ -6307,7 +6703,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
6307
6703
|
function containsCjk(value) {
|
|
6308
6704
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
6309
6705
|
}
|
|
6310
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
6706
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
6311
6707
|
const layouts = /* @__PURE__ */ new Map();
|
|
6312
6708
|
const diagnostics = [];
|
|
6313
6709
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -6326,7 +6722,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
6326
6722
|
locks,
|
|
6327
6723
|
diagnostics,
|
|
6328
6724
|
movedChildIds,
|
|
6329
|
-
laneGutter
|
|
6725
|
+
laneGutter,
|
|
6726
|
+
constraints,
|
|
6727
|
+
distributeContainedChildren
|
|
6330
6728
|
);
|
|
6331
6729
|
if (layout2 !== void 0) {
|
|
6332
6730
|
layouts.set(swimlane.id, layout2);
|
|
@@ -6422,9 +6820,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
6422
6820
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
6423
6821
|
return new Map(boxes);
|
|
6424
6822
|
}
|
|
6425
|
-
|
|
6426
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
6427
|
-
|
|
6823
|
+
let maxRowDepth = options.maxRowDepth;
|
|
6824
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
6825
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
6826
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
6827
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
6828
|
+
} else {
|
|
6829
|
+
return new Map(boxes);
|
|
6830
|
+
}
|
|
6428
6831
|
}
|
|
6429
6832
|
const ordered = [...nodes].sort((a, b) => {
|
|
6430
6833
|
const ba = boxes.get(a.id);
|
|
@@ -6485,10 +6888,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
6485
6888
|
});
|
|
6486
6889
|
}
|
|
6487
6890
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
6488
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
6891
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
6489
6892
|
return false;
|
|
6490
6893
|
}
|
|
6491
|
-
if (nodes.length < 2
|
|
6894
|
+
if (nodes.length < 2) {
|
|
6492
6895
|
return false;
|
|
6493
6896
|
}
|
|
6494
6897
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -6496,17 +6899,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6496
6899
|
return false;
|
|
6497
6900
|
}
|
|
6498
6901
|
const bounds = unionBoxes(nodeBoxes);
|
|
6499
|
-
const
|
|
6500
|
-
const
|
|
6501
|
-
|
|
6902
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
6903
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
6904
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
6905
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
6502
6906
|
return false;
|
|
6503
6907
|
}
|
|
6908
|
+
if (isHorizontal) {
|
|
6909
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
6910
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
6911
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
6912
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
6913
|
+
}
|
|
6504
6914
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
6505
6915
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
6506
6916
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
6507
6917
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
6508
6918
|
}
|
|
6509
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6919
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6510
6920
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
6511
6921
|
const padding = swimlane.padding ?? 16;
|
|
6512
6922
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -6531,7 +6941,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6531
6941
|
locks,
|
|
6532
6942
|
diagnostics,
|
|
6533
6943
|
movedChildIds,
|
|
6534
|
-
laneGutter
|
|
6944
|
+
laneGutter,
|
|
6945
|
+
constraints,
|
|
6946
|
+
distributeContainedChildren
|
|
6535
6947
|
);
|
|
6536
6948
|
}
|
|
6537
6949
|
return applyHorizontalSwimlaneContract(
|
|
@@ -6546,13 +6958,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6546
6958
|
laneGutter
|
|
6547
6959
|
);
|
|
6548
6960
|
}
|
|
6549
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6961
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6550
6962
|
const populatedBounds = laneBounds.filter(
|
|
6551
6963
|
(box) => box !== void 0
|
|
6552
6964
|
);
|
|
6553
6965
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
6554
6966
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
6555
6967
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
6968
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
6969
|
+
if (distributeContainedChildren) {
|
|
6970
|
+
for (const c of constraints) {
|
|
6971
|
+
if (c.kind !== "containment") continue;
|
|
6972
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
6973
|
+
const distributable = c.childIds.filter((childId) => {
|
|
6974
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
6975
|
+
const lock = locks.get(childId);
|
|
6976
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
6977
|
+
});
|
|
6978
|
+
if (distributable.length < 2) continue;
|
|
6979
|
+
for (const childId of distributable) {
|
|
6980
|
+
containedChildIds.add(childId);
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6556
6984
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
6557
6985
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
6558
6986
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -6564,7 +6992,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6564
6992
|
);
|
|
6565
6993
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
6566
6994
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
6567
|
-
const
|
|
6995
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
6996
|
+
swimlane,
|
|
6997
|
+
nodeBoxes,
|
|
6998
|
+
flowRanks,
|
|
6999
|
+
locks,
|
|
7000
|
+
rankStackGap,
|
|
7001
|
+
containedChildIds
|
|
7002
|
+
);
|
|
7003
|
+
const slotWidth = Math.max(
|
|
7004
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7005
|
+
spreadWidth
|
|
7006
|
+
) + padding * 2;
|
|
6568
7007
|
const laneStep = slotWidth + laneGutter;
|
|
6569
7008
|
const laneContentTop = top + headerHeight + padding;
|
|
6570
7009
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -6578,6 +7017,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6578
7017
|
y: laneContentTop
|
|
6579
7018
|
};
|
|
6580
7019
|
if (maxRank === 0) {
|
|
7020
|
+
const distributable = lane.children.filter(
|
|
7021
|
+
(childId) => !locks.has(childId)
|
|
7022
|
+
);
|
|
7023
|
+
const coveredByContainment = lane.children.some(
|
|
7024
|
+
(childId) => containedChildIds.has(childId)
|
|
7025
|
+
);
|
|
7026
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7027
|
+
moveRankedVerticalLaneChildren(
|
|
7028
|
+
lane.children,
|
|
7029
|
+
nodeBoxes,
|
|
7030
|
+
locks,
|
|
7031
|
+
diagnostics,
|
|
7032
|
+
movedChildIds,
|
|
7033
|
+
flowRanks,
|
|
7034
|
+
rankSpacing,
|
|
7035
|
+
rankStackGap,
|
|
7036
|
+
{ x: target.x, y: laneContentTop },
|
|
7037
|
+
slotWidth - padding * 2
|
|
7038
|
+
);
|
|
7039
|
+
continue;
|
|
7040
|
+
}
|
|
6581
7041
|
moveLaneChildren(
|
|
6582
7042
|
lane.children,
|
|
6583
7043
|
nodeBoxes,
|
|
@@ -6591,6 +7051,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6591
7051
|
);
|
|
6592
7052
|
continue;
|
|
6593
7053
|
}
|
|
7054
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7055
|
+
(childId) => containedChildIds.has(childId)
|
|
7056
|
+
);
|
|
6594
7057
|
moveRankedVerticalLaneChildren(
|
|
6595
7058
|
lane.children,
|
|
6596
7059
|
nodeBoxes,
|
|
@@ -6600,10 +7063,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6600
7063
|
flowRanks,
|
|
6601
7064
|
rankSpacing,
|
|
6602
7065
|
rankStackGap,
|
|
6603
|
-
{
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
}
|
|
7066
|
+
{ x: target.x, y: laneContentTop },
|
|
7067
|
+
slotWidth - padding * 2,
|
|
7068
|
+
rankedCoveredByContainment
|
|
6607
7069
|
);
|
|
6608
7070
|
}
|
|
6609
7071
|
return {
|
|
@@ -6712,31 +7174,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
6712
7174
|
}
|
|
6713
7175
|
return maxHeight;
|
|
6714
7176
|
}
|
|
6715
|
-
|
|
7177
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7178
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7179
|
+
return items.reduce(
|
|
7180
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7181
|
+
0
|
|
7182
|
+
);
|
|
7183
|
+
}
|
|
7184
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7185
|
+
let maxWidth = 0;
|
|
7186
|
+
for (const lane of swimlane.lanes) {
|
|
7187
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7188
|
+
continue;
|
|
7189
|
+
}
|
|
7190
|
+
for (const stack of rankStacks(
|
|
7191
|
+
lane.children,
|
|
7192
|
+
nodeBoxes,
|
|
7193
|
+
flowRanks
|
|
7194
|
+
).values()) {
|
|
7195
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7196
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7197
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
return maxWidth;
|
|
7201
|
+
}
|
|
7202
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
6716
7203
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6717
|
-
|
|
7204
|
+
const unlocked = [];
|
|
6718
7205
|
for (const item of stack) {
|
|
6719
|
-
|
|
6720
|
-
if (locks.has(childId)) {
|
|
7206
|
+
if (locks.has(item.childId)) {
|
|
6721
7207
|
diagnostics.push({
|
|
6722
7208
|
severity: "warning",
|
|
6723
7209
|
code: "constraints.locked-target-not-moved",
|
|
6724
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7210
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
6725
7211
|
path: ["swimlanes"],
|
|
6726
|
-
detail: { nodeId: childId }
|
|
7212
|
+
detail: { nodeId: item.childId }
|
|
6727
7213
|
});
|
|
6728
|
-
|
|
7214
|
+
} else {
|
|
7215
|
+
unlocked.push(item);
|
|
6729
7216
|
}
|
|
7217
|
+
}
|
|
7218
|
+
if (unlocked.length === 0) continue;
|
|
7219
|
+
if (unlocked.length === 1) {
|
|
7220
|
+
const { childId, box } = unlocked[0];
|
|
6730
7221
|
const next = {
|
|
6731
7222
|
...box,
|
|
6732
|
-
x:
|
|
6733
|
-
y: target.y + rank * rankSpacing
|
|
7223
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7224
|
+
y: target.y + rank * rankSpacing
|
|
6734
7225
|
};
|
|
6735
7226
|
if (next.x !== box.x || next.y !== box.y) {
|
|
6736
7227
|
movedChildIds.add(childId);
|
|
6737
7228
|
}
|
|
6738
7229
|
nodeBoxes.set(childId, next);
|
|
6739
|
-
|
|
7230
|
+
} else {
|
|
7231
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7232
|
+
if (!shouldSpread) {
|
|
7233
|
+
let yOffset = 0;
|
|
7234
|
+
for (const { childId, box } of unlocked) {
|
|
7235
|
+
const next = {
|
|
7236
|
+
...box,
|
|
7237
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7238
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7239
|
+
};
|
|
7240
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7241
|
+
movedChildIds.add(childId);
|
|
7242
|
+
}
|
|
7243
|
+
nodeBoxes.set(childId, next);
|
|
7244
|
+
yOffset += box.height + rankStackGap;
|
|
7245
|
+
}
|
|
7246
|
+
} else {
|
|
7247
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
7248
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
7249
|
+
for (const { childId, box } of unlocked) {
|
|
7250
|
+
const next = {
|
|
7251
|
+
...box,
|
|
7252
|
+
x: xCursor,
|
|
7253
|
+
y: target.y + rank * rankSpacing
|
|
7254
|
+
};
|
|
7255
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7256
|
+
movedChildIds.add(childId);
|
|
7257
|
+
}
|
|
7258
|
+
nodeBoxes.set(childId, next);
|
|
7259
|
+
xCursor += box.width + rankStackGap;
|
|
7260
|
+
}
|
|
7261
|
+
diagnostics.push({
|
|
7262
|
+
severity: "info",
|
|
7263
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
7264
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
7265
|
+
path: ["swimlanes"],
|
|
7266
|
+
detail: {
|
|
7267
|
+
rank,
|
|
7268
|
+
childCount: unlocked.length,
|
|
7269
|
+
contentWidth
|
|
7270
|
+
}
|
|
7271
|
+
});
|
|
7272
|
+
}
|
|
6740
7273
|
}
|
|
6741
7274
|
}
|
|
6742
7275
|
}
|
|
@@ -7075,7 +7608,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7075
7608
|
});
|
|
7076
7609
|
continue;
|
|
7077
7610
|
}
|
|
7078
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
7611
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7079
7612
|
const geometry = computeShapeGeometry({
|
|
7080
7613
|
shape: node.shape,
|
|
7081
7614
|
box,
|
|
@@ -7169,7 +7702,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
7169
7702
|
}
|
|
7170
7703
|
}
|
|
7171
7704
|
}
|
|
7172
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
7705
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
7173
7706
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
7174
7707
|
for (const port of node.ports ?? []) {
|
|
7175
7708
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -7192,9 +7725,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7192
7725
|
side,
|
|
7193
7726
|
index,
|
|
7194
7727
|
sorted.length,
|
|
7195
|
-
portShifting
|
|
7196
|
-
diagnostics,
|
|
7197
|
-
node.id
|
|
7728
|
+
portShifting
|
|
7198
7729
|
);
|
|
7199
7730
|
const box = portBox(anchor);
|
|
7200
7731
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -7202,32 +7733,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7202
7733
|
}
|
|
7203
7734
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
7204
7735
|
}
|
|
7205
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
7736
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
7206
7737
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
7207
7738
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
7208
7739
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
7209
7740
|
const availableSpan = 2 * maxOffset;
|
|
7210
7741
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
7211
|
-
const
|
|
7742
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
7212
7743
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
7213
7744
|
minSpacing
|
|
7214
7745
|
) : 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
7746
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
7232
7747
|
switch (side) {
|
|
7233
7748
|
case "left":
|
|
@@ -7842,14 +8357,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
7842
8357
|
}
|
|
7843
8358
|
};
|
|
7844
8359
|
}
|
|
7845
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
8360
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
7846
8361
|
const coordinated = [];
|
|
7847
8362
|
const coordinatedNodeById = new Map(
|
|
7848
8363
|
coordinatedNodes.map((node) => [node.id, node])
|
|
7849
8364
|
);
|
|
8365
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
8366
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
8367
|
+
200,
|
|
8368
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
8369
|
+
);
|
|
8370
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
8371
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
7850
8372
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
7851
8373
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
7852
|
-
|
|
8374
|
+
queryGutter
|
|
7853
8375
|
);
|
|
7854
8376
|
for (const edge of edges) {
|
|
7855
8377
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -7871,11 +8393,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7871
8393
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
7872
8394
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
7873
8395
|
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
|
-
);
|
|
8396
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
7879
8397
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
7880
8398
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
7881
8399
|
);
|
|
@@ -7893,7 +8411,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
7893
8411
|
...routeTextObstacles
|
|
7894
8412
|
],
|
|
7895
8413
|
hardObstacles,
|
|
7896
|
-
|
|
8414
|
+
corridorMargin,
|
|
8415
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
8416
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
7897
8417
|
});
|
|
7898
8418
|
diagnostics.push(
|
|
7899
8419
|
...route.diagnostics.map((diagnostic) => ({
|