@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/index.js
CHANGED
|
@@ -176,6 +176,28 @@ function applyLayoutConstraints(input) {
|
|
|
176
176
|
if (input.distributeContainedChildren) {
|
|
177
177
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
178
178
|
}
|
|
179
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
180
|
+
for (const swimlane of input.swimlanes) {
|
|
181
|
+
if (swimlane.layout === "contract") continue;
|
|
182
|
+
for (const lane of swimlane.lanes) {
|
|
183
|
+
const fixedChildren = [];
|
|
184
|
+
let participantCount = 0;
|
|
185
|
+
for (const childId of lane.children) {
|
|
186
|
+
const lock = locks.get(childId);
|
|
187
|
+
if (lock === void 0) {
|
|
188
|
+
participantCount += 1;
|
|
189
|
+
} else if (lock.source === "fixed-position") {
|
|
190
|
+
participantCount += 1;
|
|
191
|
+
fixedChildren.push(childId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (participantCount < 2) continue;
|
|
195
|
+
for (const childId of fixedChildren) {
|
|
196
|
+
locks.delete(childId);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
179
201
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
180
202
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
181
203
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -195,6 +217,9 @@ function applyLayoutConstraints(input) {
|
|
|
195
217
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
196
218
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
197
219
|
}
|
|
220
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
221
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
222
|
+
}
|
|
198
223
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
199
224
|
reportOverlaps(
|
|
200
225
|
boxes,
|
|
@@ -1034,9 +1059,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1034
1059
|
}
|
|
1035
1060
|
});
|
|
1036
1061
|
}
|
|
1037
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1038
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1039
|
-
}
|
|
1040
1062
|
}
|
|
1041
1063
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
1042
1064
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -1076,6 +1098,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
1076
1098
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
1077
1099
|
}
|
|
1078
1100
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
1101
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
1079
1102
|
let pos = contentStart;
|
|
1080
1103
|
for (const child of unlocked) {
|
|
1081
1104
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4726,15 +4749,12 @@ var BinaryHeap = class {
|
|
|
4726
4749
|
let smallestIdx = idx;
|
|
4727
4750
|
const leftIdx = (idx << 1) + 1;
|
|
4728
4751
|
const rightIdx = leftIdx + 1;
|
|
4729
|
-
if (leftIdx < size && this._less(
|
|
4730
|
-
this._data[leftIdx],
|
|
4731
|
-
this._data[smallestIdx]
|
|
4732
|
-
)) {
|
|
4752
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4733
4753
|
smallestIdx = leftIdx;
|
|
4734
4754
|
}
|
|
4735
4755
|
if (rightIdx < size && this._less(
|
|
4736
4756
|
this._data[rightIdx],
|
|
4737
|
-
this._data[
|
|
4757
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4738
4758
|
)) {
|
|
4739
4759
|
smallestIdx = rightIdx;
|
|
4740
4760
|
}
|
|
@@ -4785,7 +4805,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4785
4805
|
detail: {
|
|
4786
4806
|
xsCount: xs.length,
|
|
4787
4807
|
ysCount: ys.length,
|
|
4788
|
-
maxNodes
|
|
4808
|
+
maxNodes,
|
|
4809
|
+
obstacleCount: obstacles.length,
|
|
4810
|
+
stage: "corridor-filtered",
|
|
4811
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4789
4812
|
}
|
|
4790
4813
|
});
|
|
4791
4814
|
return null;
|
|
@@ -4801,8 +4824,66 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4801
4824
|
turnPenalty,
|
|
4802
4825
|
segmentPenalty
|
|
4803
4826
|
);
|
|
4804
|
-
if (path
|
|
4805
|
-
|
|
4827
|
+
if (path !== null) {
|
|
4828
|
+
const simplified = simplifyRoute(path);
|
|
4829
|
+
const filteredSet = new Set(filtered);
|
|
4830
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4831
|
+
let crossesExcluded = false;
|
|
4832
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4833
|
+
const a = simplified[i];
|
|
4834
|
+
const b = simplified[i + 1];
|
|
4835
|
+
for (const obs of obstacles) {
|
|
4836
|
+
if (filteredSet.has(obs)) continue;
|
|
4837
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4838
|
+
crossesExcluded = true;
|
|
4839
|
+
break;
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
if (crossesExcluded) break;
|
|
4843
|
+
}
|
|
4844
|
+
if (!crossesExcluded) return simplified;
|
|
4845
|
+
} else {
|
|
4846
|
+
return simplified;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
if (!useCorridor) return null;
|
|
4850
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4851
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4852
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4853
|
+
diagnostics?.push({
|
|
4854
|
+
severity: "warning",
|
|
4855
|
+
code: "routing.astar.grid_overflow",
|
|
4856
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4857
|
+
detail: {
|
|
4858
|
+
xsCount: xsFull.length,
|
|
4859
|
+
ysCount: ysFull.length,
|
|
4860
|
+
maxNodes,
|
|
4861
|
+
obstacleCount: obstacles.length,
|
|
4862
|
+
stage: "full-retry",
|
|
4863
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4864
|
+
}
|
|
4865
|
+
});
|
|
4866
|
+
return null;
|
|
4867
|
+
}
|
|
4868
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4869
|
+
connectHorizontalEdges(
|
|
4870
|
+
nodesFull,
|
|
4871
|
+
ysFull,
|
|
4872
|
+
obstacles,
|
|
4873
|
+
endpointObstacles,
|
|
4874
|
+
margin
|
|
4875
|
+
);
|
|
4876
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4877
|
+
const pathFull = aStarSearch(
|
|
4878
|
+
nodesFull,
|
|
4879
|
+
idxFull,
|
|
4880
|
+
source,
|
|
4881
|
+
target,
|
|
4882
|
+
turnPenalty,
|
|
4883
|
+
segmentPenalty
|
|
4884
|
+
);
|
|
4885
|
+
if (pathFull === null) return null;
|
|
4886
|
+
return simplifyRoute(pathFull);
|
|
4806
4887
|
}
|
|
4807
4888
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4808
4889
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -5023,20 +5104,83 @@ function areCollinear(a, b, c) {
|
|
|
5023
5104
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
5024
5105
|
}
|
|
5025
5106
|
|
|
5107
|
+
// src/routing/budget.ts
|
|
5108
|
+
var MIN_CORNER_BUDGET = 600;
|
|
5109
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
5110
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
5111
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
5112
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
5113
|
+
var CORNER_HEADROOM = 2;
|
|
5114
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
5115
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
5116
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
5117
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
5118
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
5119
|
+
cornerObstacles.length,
|
|
5120
|
+
corridorMargin
|
|
5121
|
+
);
|
|
5122
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
5123
|
+
allObstacles.length,
|
|
5124
|
+
corridorMargin
|
|
5125
|
+
);
|
|
5126
|
+
return {
|
|
5127
|
+
maxCorners: resolveBudget(
|
|
5128
|
+
overrides.maxCorners,
|
|
5129
|
+
adaptiveMaxCorners,
|
|
5130
|
+
MIN_CORNER_BUDGET,
|
|
5131
|
+
MAX_CORNER_BUDGET
|
|
5132
|
+
),
|
|
5133
|
+
maxNodes: resolveBudget(
|
|
5134
|
+
overrides.maxNodes,
|
|
5135
|
+
adaptiveMaxNodes,
|
|
5136
|
+
MIN_NODE_BUDGET,
|
|
5137
|
+
MAX_NODE_BUDGET
|
|
5138
|
+
),
|
|
5139
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
5140
|
+
gridObstacleCount: allObstacles.length,
|
|
5141
|
+
corridorMargin
|
|
5142
|
+
};
|
|
5143
|
+
}
|
|
5144
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
5145
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
5146
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
5147
|
+
return Math.ceil(base * corridorFactor);
|
|
5148
|
+
}
|
|
5149
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
5150
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
5151
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
5152
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
5153
|
+
}
|
|
5154
|
+
function corridorScalingFactor(corridorMargin) {
|
|
5155
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
5156
|
+
}
|
|
5157
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
5158
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
5159
|
+
return clamp(chosen, min, max);
|
|
5160
|
+
}
|
|
5161
|
+
function clamp(value, min, max) {
|
|
5162
|
+
return Math.max(min, Math.min(max, value));
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5026
5165
|
// src/routing/visibility-router.ts
|
|
5027
5166
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
5028
5167
|
const margin = options.margin ?? 0;
|
|
5029
5168
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
5030
5169
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
5031
5170
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
5032
|
-
const maxCorners = options.maxCorners ??
|
|
5171
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
5033
5172
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
5034
5173
|
if (vertices.length > maxCorners) {
|
|
5035
5174
|
diagnostics?.push({
|
|
5036
5175
|
severity: "warning",
|
|
5037
5176
|
code: "routing.visibility.corner_overflow",
|
|
5038
5177
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
5039
|
-
detail: {
|
|
5178
|
+
detail: {
|
|
5179
|
+
vertexCount: vertices.length,
|
|
5180
|
+
maxCorners,
|
|
5181
|
+
obstacleCount: obstacles.length,
|
|
5182
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
5183
|
+
}
|
|
5040
5184
|
});
|
|
5041
5185
|
return null;
|
|
5042
5186
|
}
|
|
@@ -5310,7 +5454,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
5310
5454
|
}
|
|
5311
5455
|
|
|
5312
5456
|
// src/routing/routes.ts
|
|
5313
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
5457
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
5314
5458
|
if (points.length < 2) return;
|
|
5315
5459
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
5316
5460
|
if (direct <= 0) return;
|
|
@@ -5320,7 +5464,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
5320
5464
|
const b = points[i + 1];
|
|
5321
5465
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
5322
5466
|
}
|
|
5323
|
-
const threshold =
|
|
5467
|
+
const threshold = maxRatio ?? 20;
|
|
5324
5468
|
if (routeLen > direct * threshold) {
|
|
5325
5469
|
diagnostics.push({
|
|
5326
5470
|
severity: "warning",
|
|
@@ -5338,8 +5482,20 @@ function routeEdge(input) {
|
|
|
5338
5482
|
const diagnostics = [];
|
|
5339
5483
|
const softObstacles = input.obstacles ?? [];
|
|
5340
5484
|
const hardObstacles = input.hardObstacles ?? [];
|
|
5485
|
+
let bestRejectedPath;
|
|
5486
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
5341
5487
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
5342
5488
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
5489
|
+
const recordRejected = (candidate) => {
|
|
5490
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
5491
|
+
return;
|
|
5492
|
+
}
|
|
5493
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
5494
|
+
if (crossings < bestRejectedCrossings) {
|
|
5495
|
+
bestRejectedCrossings = crossings;
|
|
5496
|
+
bestRejectedPath = candidate;
|
|
5497
|
+
}
|
|
5498
|
+
};
|
|
5343
5499
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
5344
5500
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
5345
5501
|
input.source.box,
|
|
@@ -5398,18 +5554,59 @@ function routeEdge(input) {
|
|
|
5398
5554
|
input.source.center,
|
|
5399
5555
|
targetAnchor
|
|
5400
5556
|
);
|
|
5401
|
-
const
|
|
5557
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
5558
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
5559
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
5402
5560
|
source,
|
|
5403
5561
|
target,
|
|
5404
|
-
|
|
5405
|
-
|
|
5562
|
+
allObstacles,
|
|
5563
|
+
[],
|
|
5564
|
+
// endpointObstacles passed separately via options
|
|
5565
|
+
corridorMargin
|
|
5566
|
+
);
|
|
5567
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
5568
|
+
const budget = computeRoutingBudget(
|
|
5569
|
+
cornerObstacles,
|
|
5570
|
+
allObstacles,
|
|
5571
|
+
corridorMargin,
|
|
5572
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
5573
|
+
);
|
|
5574
|
+
let cornerPath = findCornerGraphPath(
|
|
5575
|
+
source,
|
|
5576
|
+
target,
|
|
5577
|
+
cornerObstacles,
|
|
5578
|
+
{
|
|
5579
|
+
endpointObstacles,
|
|
5580
|
+
margin: 2,
|
|
5581
|
+
maxCorners: budget.maxCorners,
|
|
5582
|
+
corridorMargin
|
|
5583
|
+
},
|
|
5406
5584
|
diagnostics
|
|
5407
5585
|
);
|
|
5586
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
5587
|
+
cornerPath = findCornerGraphPath(
|
|
5588
|
+
source,
|
|
5589
|
+
target,
|
|
5590
|
+
allObstacles,
|
|
5591
|
+
{
|
|
5592
|
+
endpointObstacles,
|
|
5593
|
+
margin: 2,
|
|
5594
|
+
maxCorners: budget.maxCorners,
|
|
5595
|
+
corridorMargin
|
|
5596
|
+
},
|
|
5597
|
+
diagnostics
|
|
5598
|
+
);
|
|
5599
|
+
}
|
|
5408
5600
|
const path = cornerPath ?? findObstacleFreePath(
|
|
5409
5601
|
source,
|
|
5410
5602
|
target,
|
|
5411
|
-
|
|
5412
|
-
{
|
|
5603
|
+
allObstacles,
|
|
5604
|
+
{
|
|
5605
|
+
endpointObstacles,
|
|
5606
|
+
margin: 0,
|
|
5607
|
+
corridorMargin,
|
|
5608
|
+
maxNodes: budget.maxNodes
|
|
5609
|
+
},
|
|
5413
5610
|
diagnostics
|
|
5414
5611
|
);
|
|
5415
5612
|
if (path !== null && path.length >= 2) {
|
|
@@ -5426,9 +5623,100 @@ function routeEdge(input) {
|
|
|
5426
5623
|
softObstacles,
|
|
5427
5624
|
softObstacleIndex
|
|
5428
5625
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
5429
|
-
checkBacktracking(
|
|
5626
|
+
checkBacktracking(
|
|
5627
|
+
finalized,
|
|
5628
|
+
source,
|
|
5629
|
+
target,
|
|
5630
|
+
diagnostics,
|
|
5631
|
+
input.maxBacktrackingRatio
|
|
5632
|
+
);
|
|
5430
5633
|
return { points: finalized, diagnostics };
|
|
5431
5634
|
}
|
|
5635
|
+
recordRejected(finalized);
|
|
5636
|
+
if (cornerPath !== null) {
|
|
5637
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
5638
|
+
source,
|
|
5639
|
+
target,
|
|
5640
|
+
allObstacles,
|
|
5641
|
+
{
|
|
5642
|
+
endpointObstacles,
|
|
5643
|
+
margin: 2,
|
|
5644
|
+
maxCorners: budget.maxCorners,
|
|
5645
|
+
corridorMargin
|
|
5646
|
+
},
|
|
5647
|
+
diagnostics
|
|
5648
|
+
) : null;
|
|
5649
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5650
|
+
const fullFinalized = finalizeRoute(
|
|
5651
|
+
fullCornerPath,
|
|
5652
|
+
softObstacles,
|
|
5653
|
+
hardObstacles,
|
|
5654
|
+
diagnostics,
|
|
5655
|
+
softObstacleIndex,
|
|
5656
|
+
hardObstacleIndex
|
|
5657
|
+
);
|
|
5658
|
+
if (!routeIntersectsObstacles(
|
|
5659
|
+
fullFinalized,
|
|
5660
|
+
softObstacles,
|
|
5661
|
+
softObstacleIndex
|
|
5662
|
+
) && !routeIntersectsObstacles(
|
|
5663
|
+
fullFinalized,
|
|
5664
|
+
hardObstacles,
|
|
5665
|
+
hardObstacleIndex
|
|
5666
|
+
)) {
|
|
5667
|
+
checkBacktracking(
|
|
5668
|
+
fullFinalized,
|
|
5669
|
+
source,
|
|
5670
|
+
target,
|
|
5671
|
+
diagnostics,
|
|
5672
|
+
input.maxBacktrackingRatio
|
|
5673
|
+
);
|
|
5674
|
+
return { points: fullFinalized, diagnostics };
|
|
5675
|
+
}
|
|
5676
|
+
recordRejected(fullFinalized);
|
|
5677
|
+
}
|
|
5678
|
+
const gridPath = findObstacleFreePath(
|
|
5679
|
+
source,
|
|
5680
|
+
target,
|
|
5681
|
+
allObstacles,
|
|
5682
|
+
{
|
|
5683
|
+
endpointObstacles,
|
|
5684
|
+
margin: 0,
|
|
5685
|
+
corridorMargin,
|
|
5686
|
+
maxNodes: budget.maxNodes
|
|
5687
|
+
},
|
|
5688
|
+
diagnostics
|
|
5689
|
+
);
|
|
5690
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5691
|
+
const gridFinalized = finalizeRoute(
|
|
5692
|
+
gridPath,
|
|
5693
|
+
softObstacles,
|
|
5694
|
+
hardObstacles,
|
|
5695
|
+
diagnostics,
|
|
5696
|
+
softObstacleIndex,
|
|
5697
|
+
hardObstacleIndex
|
|
5698
|
+
);
|
|
5699
|
+
if (!routeIntersectsObstacles(
|
|
5700
|
+
gridFinalized,
|
|
5701
|
+
softObstacles,
|
|
5702
|
+
softObstacleIndex
|
|
5703
|
+
) && !routeIntersectsObstacles(
|
|
5704
|
+
gridFinalized,
|
|
5705
|
+
hardObstacles,
|
|
5706
|
+
hardObstacleIndex
|
|
5707
|
+
)) {
|
|
5708
|
+
checkBacktracking(
|
|
5709
|
+
gridFinalized,
|
|
5710
|
+
source,
|
|
5711
|
+
target,
|
|
5712
|
+
diagnostics,
|
|
5713
|
+
input.maxBacktrackingRatio
|
|
5714
|
+
);
|
|
5715
|
+
return { points: gridFinalized, diagnostics };
|
|
5716
|
+
}
|
|
5717
|
+
recordRejected(gridFinalized);
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5432
5720
|
}
|
|
5433
5721
|
}
|
|
5434
5722
|
}
|
|
@@ -5490,7 +5778,8 @@ function routeEdge(input) {
|
|
|
5490
5778
|
finalizedClean,
|
|
5491
5779
|
candidate.points[0],
|
|
5492
5780
|
candidate.points[candidate.points.length - 1],
|
|
5493
|
-
diagnostics
|
|
5781
|
+
diagnostics,
|
|
5782
|
+
input.maxBacktrackingRatio
|
|
5494
5783
|
);
|
|
5495
5784
|
return { points: finalizedClean, diagnostics };
|
|
5496
5785
|
}
|
|
@@ -5556,13 +5845,41 @@ function routeEdge(input) {
|
|
|
5556
5845
|
code: "routing.obstacle.unavoidable",
|
|
5557
5846
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5558
5847
|
});
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5848
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5849
|
+
bestPoints2,
|
|
5850
|
+
softObstacles,
|
|
5851
|
+
hardObstacles,
|
|
5852
|
+
diagnostics
|
|
5853
|
+
);
|
|
5854
|
+
let softFallback = finalizedSoftBest;
|
|
5855
|
+
if (bestRejectedPath !== void 0) {
|
|
5856
|
+
const finalizedRejected = finalizeRoute(
|
|
5857
|
+
bestRejectedPath,
|
|
5562
5858
|
softObstacles,
|
|
5563
5859
|
hardObstacles,
|
|
5564
5860
|
diagnostics
|
|
5565
|
-
)
|
|
5861
|
+
);
|
|
5862
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5863
|
+
finalizedRejected,
|
|
5864
|
+
softObstacles
|
|
5865
|
+
);
|
|
5866
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5867
|
+
finalizedSoftBest,
|
|
5868
|
+
softObstacles
|
|
5869
|
+
);
|
|
5870
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5871
|
+
softFallback = finalizedRejected;
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
checkBacktracking(
|
|
5875
|
+
softFallback,
|
|
5876
|
+
softFallback[0],
|
|
5877
|
+
softFallback[softFallback.length - 1],
|
|
5878
|
+
diagnostics,
|
|
5879
|
+
input.maxBacktrackingRatio
|
|
5880
|
+
);
|
|
5881
|
+
return {
|
|
5882
|
+
points: softFallback,
|
|
5566
5883
|
diagnostics
|
|
5567
5884
|
};
|
|
5568
5885
|
}
|
|
@@ -5594,6 +5911,22 @@ function routeEdge(input) {
|
|
|
5594
5911
|
maxAttempts
|
|
5595
5912
|
);
|
|
5596
5913
|
}
|
|
5914
|
+
if (bestRejectedPath !== void 0) {
|
|
5915
|
+
diagnostics.push({
|
|
5916
|
+
severity: "warning",
|
|
5917
|
+
code: "routing.obstacle.unavoidable",
|
|
5918
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5919
|
+
});
|
|
5920
|
+
return {
|
|
5921
|
+
points: finalizeRoute(
|
|
5922
|
+
bestRejectedPath,
|
|
5923
|
+
softObstacles,
|
|
5924
|
+
hardObstacles,
|
|
5925
|
+
diagnostics
|
|
5926
|
+
),
|
|
5927
|
+
diagnostics
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5597
5930
|
diagnostics.push({
|
|
5598
5931
|
severity: "error",
|
|
5599
5932
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5641,13 +5974,41 @@ function routeEdge(input) {
|
|
|
5641
5974
|
code: "routing.obstacle.unavoidable",
|
|
5642
5975
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5643
5976
|
});
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5977
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5978
|
+
bestPoints,
|
|
5979
|
+
softObstacles,
|
|
5980
|
+
hardObstacles,
|
|
5981
|
+
diagnostics
|
|
5982
|
+
);
|
|
5983
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5984
|
+
if (bestRejectedPath !== void 0) {
|
|
5985
|
+
const finalizedRejected = finalizeRoute(
|
|
5986
|
+
bestRejectedPath,
|
|
5647
5987
|
softObstacles,
|
|
5648
5988
|
hardObstacles,
|
|
5649
5989
|
diagnostics
|
|
5650
|
-
)
|
|
5990
|
+
);
|
|
5991
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5992
|
+
finalizedRejected,
|
|
5993
|
+
softObstacles
|
|
5994
|
+
);
|
|
5995
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5996
|
+
finalizedBestPoints,
|
|
5997
|
+
softObstacles
|
|
5998
|
+
);
|
|
5999
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
6000
|
+
fallbackPoints = finalizedRejected;
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
checkBacktracking(
|
|
6004
|
+
fallbackPoints,
|
|
6005
|
+
fallbackPoints[0],
|
|
6006
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
6007
|
+
diagnostics,
|
|
6008
|
+
input.maxBacktrackingRatio
|
|
6009
|
+
);
|
|
6010
|
+
return {
|
|
6011
|
+
points: fallbackPoints,
|
|
5651
6012
|
diagnostics
|
|
5652
6013
|
};
|
|
5653
6014
|
}
|
|
@@ -6146,6 +6507,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
6146
6507
|
}
|
|
6147
6508
|
return false;
|
|
6148
6509
|
}
|
|
6510
|
+
function countObstacleCrossings(points, obstacles) {
|
|
6511
|
+
let count = 0;
|
|
6512
|
+
for (const obstacle of obstacles) {
|
|
6513
|
+
validateBox(obstacle);
|
|
6514
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
6515
|
+
const a = points[pointIndex];
|
|
6516
|
+
const b = points[pointIndex + 1];
|
|
6517
|
+
if (a === void 0 || b === void 0) {
|
|
6518
|
+
continue;
|
|
6519
|
+
}
|
|
6520
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
6521
|
+
count += 1;
|
|
6522
|
+
break;
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
return count;
|
|
6527
|
+
}
|
|
6149
6528
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
6150
6529
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
6151
6530
|
const a = points[index];
|
|
@@ -6529,7 +6908,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6529
6908
|
edges: styledEdges
|
|
6530
6909
|
});
|
|
6531
6910
|
diagnostics.push(...layout2.diagnostics);
|
|
6532
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6911
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6533
6912
|
layout2.boxes,
|
|
6534
6913
|
styledNodes,
|
|
6535
6914
|
styledEdges,
|
|
@@ -6537,7 +6916,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6537
6916
|
options,
|
|
6538
6917
|
diagnostics
|
|
6539
6918
|
);
|
|
6540
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6919
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6920
|
+
const diagCountBefore = diagnostics.length;
|
|
6541
6921
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
6542
6922
|
initialNodeBoxes,
|
|
6543
6923
|
styledNodes,
|
|
@@ -6548,6 +6928,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6548
6928
|
for (const [id, box] of rewrapped) {
|
|
6549
6929
|
initialNodeBoxes.set(id, box);
|
|
6550
6930
|
}
|
|
6931
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6932
|
+
for (const node of styledNodes) {
|
|
6933
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6934
|
+
const rwBox = rewrapped.get(node.id);
|
|
6935
|
+
const idx = styledNodes.indexOf(node);
|
|
6936
|
+
if (idx !== -1) {
|
|
6937
|
+
styledNodes[idx] = {
|
|
6938
|
+
...node,
|
|
6939
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6940
|
+
};
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6551
6945
|
}
|
|
6552
6946
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
6553
6947
|
const recursiveLayout = layout2;
|
|
@@ -6561,7 +6955,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6561
6955
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
6562
6956
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
6563
6957
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
6564
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6958
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
6565
6959
|
swimlanes: styledSwimlanes,
|
|
6566
6960
|
boxes: initialNodeBoxes,
|
|
6567
6961
|
nodes: styledNodes,
|
|
@@ -6576,7 +6970,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6576
6970
|
constrained.boxes,
|
|
6577
6971
|
constrained.locks,
|
|
6578
6972
|
options?.overlapSpacing ?? 40,
|
|
6579
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6973
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6974
|
+
options.distributeContainedChildren ?? true
|
|
6580
6975
|
);
|
|
6581
6976
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
6582
6977
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6743,7 +7138,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6743
7138
|
diagram.direction,
|
|
6744
7139
|
options,
|
|
6745
7140
|
diagnostics,
|
|
6746
|
-
coordinatedGroups
|
|
7141
|
+
coordinatedGroups,
|
|
7142
|
+
contentBounds
|
|
6747
7143
|
);
|
|
6748
7144
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6749
7145
|
coordinatedEdges,
|
|
@@ -7165,7 +7561,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
7165
7561
|
function containsCjk(value) {
|
|
7166
7562
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
7167
7563
|
}
|
|
7168
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
7564
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
7169
7565
|
const layouts = /* @__PURE__ */ new Map();
|
|
7170
7566
|
const diagnostics = [];
|
|
7171
7567
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -7184,7 +7580,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
7184
7580
|
locks,
|
|
7185
7581
|
diagnostics,
|
|
7186
7582
|
movedChildIds,
|
|
7187
|
-
laneGutter
|
|
7583
|
+
laneGutter,
|
|
7584
|
+
constraints,
|
|
7585
|
+
distributeContainedChildren
|
|
7188
7586
|
);
|
|
7189
7587
|
if (layout2 !== void 0) {
|
|
7190
7588
|
layouts.set(swimlane.id, layout2);
|
|
@@ -7280,9 +7678,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
7280
7678
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
7281
7679
|
return new Map(boxes);
|
|
7282
7680
|
}
|
|
7283
|
-
|
|
7284
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
7285
|
-
|
|
7681
|
+
let maxRowDepth = options.maxRowDepth;
|
|
7682
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
7683
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
7684
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
7685
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
7686
|
+
} else {
|
|
7687
|
+
return new Map(boxes);
|
|
7688
|
+
}
|
|
7286
7689
|
}
|
|
7287
7690
|
const ordered = [...nodes].sort((a, b) => {
|
|
7288
7691
|
const ba = boxes.get(a.id);
|
|
@@ -7343,10 +7746,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
7343
7746
|
});
|
|
7344
7747
|
}
|
|
7345
7748
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
7346
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
7749
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
7347
7750
|
return false;
|
|
7348
7751
|
}
|
|
7349
|
-
if (nodes.length < 2
|
|
7752
|
+
if (nodes.length < 2) {
|
|
7350
7753
|
return false;
|
|
7351
7754
|
}
|
|
7352
7755
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -7354,17 +7757,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
7354
7757
|
return false;
|
|
7355
7758
|
}
|
|
7356
7759
|
const bounds = unionBoxes(nodeBoxes);
|
|
7357
|
-
const
|
|
7358
|
-
const
|
|
7359
|
-
|
|
7760
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
7761
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
7762
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
7763
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
7360
7764
|
return false;
|
|
7361
7765
|
}
|
|
7766
|
+
if (isHorizontal) {
|
|
7767
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
7768
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
7769
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
7770
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
7771
|
+
}
|
|
7362
7772
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
7363
7773
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
7364
7774
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
7365
7775
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
7366
7776
|
}
|
|
7367
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7777
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7368
7778
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
7369
7779
|
const padding = swimlane.padding ?? 16;
|
|
7370
7780
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -7389,7 +7799,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7389
7799
|
locks,
|
|
7390
7800
|
diagnostics,
|
|
7391
7801
|
movedChildIds,
|
|
7392
|
-
laneGutter
|
|
7802
|
+
laneGutter,
|
|
7803
|
+
constraints,
|
|
7804
|
+
distributeContainedChildren
|
|
7393
7805
|
);
|
|
7394
7806
|
}
|
|
7395
7807
|
return applyHorizontalSwimlaneContract(
|
|
@@ -7404,13 +7816,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7404
7816
|
laneGutter
|
|
7405
7817
|
);
|
|
7406
7818
|
}
|
|
7407
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7819
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7408
7820
|
const populatedBounds = laneBounds.filter(
|
|
7409
7821
|
(box) => box !== void 0
|
|
7410
7822
|
);
|
|
7411
7823
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
7412
7824
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
7413
7825
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
7826
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
7827
|
+
if (distributeContainedChildren) {
|
|
7828
|
+
for (const c of constraints) {
|
|
7829
|
+
if (c.kind !== "containment") continue;
|
|
7830
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
7831
|
+
const distributable = c.childIds.filter((childId) => {
|
|
7832
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
7833
|
+
const lock = locks.get(childId);
|
|
7834
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
7835
|
+
});
|
|
7836
|
+
if (distributable.length < 2) continue;
|
|
7837
|
+
for (const childId of distributable) {
|
|
7838
|
+
containedChildIds.add(childId);
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7414
7842
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
7415
7843
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
7416
7844
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -7422,7 +7850,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7422
7850
|
);
|
|
7423
7851
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
7424
7852
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
7425
|
-
const
|
|
7853
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
7854
|
+
swimlane,
|
|
7855
|
+
nodeBoxes,
|
|
7856
|
+
flowRanks,
|
|
7857
|
+
locks,
|
|
7858
|
+
rankStackGap,
|
|
7859
|
+
containedChildIds
|
|
7860
|
+
);
|
|
7861
|
+
const slotWidth = Math.max(
|
|
7862
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7863
|
+
spreadWidth
|
|
7864
|
+
) + padding * 2;
|
|
7426
7865
|
const laneStep = slotWidth + laneGutter;
|
|
7427
7866
|
const laneContentTop = top + headerHeight + padding;
|
|
7428
7867
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -7436,6 +7875,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7436
7875
|
y: laneContentTop
|
|
7437
7876
|
};
|
|
7438
7877
|
if (maxRank === 0) {
|
|
7878
|
+
const distributable = lane.children.filter(
|
|
7879
|
+
(childId) => !locks.has(childId)
|
|
7880
|
+
);
|
|
7881
|
+
const coveredByContainment = lane.children.some(
|
|
7882
|
+
(childId) => containedChildIds.has(childId)
|
|
7883
|
+
);
|
|
7884
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7885
|
+
moveRankedVerticalLaneChildren(
|
|
7886
|
+
lane.children,
|
|
7887
|
+
nodeBoxes,
|
|
7888
|
+
locks,
|
|
7889
|
+
diagnostics,
|
|
7890
|
+
movedChildIds,
|
|
7891
|
+
flowRanks,
|
|
7892
|
+
rankSpacing,
|
|
7893
|
+
rankStackGap,
|
|
7894
|
+
{ x: target.x, y: laneContentTop },
|
|
7895
|
+
slotWidth - padding * 2
|
|
7896
|
+
);
|
|
7897
|
+
continue;
|
|
7898
|
+
}
|
|
7439
7899
|
moveLaneChildren(
|
|
7440
7900
|
lane.children,
|
|
7441
7901
|
nodeBoxes,
|
|
@@ -7449,6 +7909,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7449
7909
|
);
|
|
7450
7910
|
continue;
|
|
7451
7911
|
}
|
|
7912
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7913
|
+
(childId) => containedChildIds.has(childId)
|
|
7914
|
+
);
|
|
7452
7915
|
moveRankedVerticalLaneChildren(
|
|
7453
7916
|
lane.children,
|
|
7454
7917
|
nodeBoxes,
|
|
@@ -7458,10 +7921,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7458
7921
|
flowRanks,
|
|
7459
7922
|
rankSpacing,
|
|
7460
7923
|
rankStackGap,
|
|
7461
|
-
{
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
}
|
|
7924
|
+
{ x: target.x, y: laneContentTop },
|
|
7925
|
+
slotWidth - padding * 2,
|
|
7926
|
+
rankedCoveredByContainment
|
|
7465
7927
|
);
|
|
7466
7928
|
}
|
|
7467
7929
|
return {
|
|
@@ -7570,31 +8032,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
7570
8032
|
}
|
|
7571
8033
|
return maxHeight;
|
|
7572
8034
|
}
|
|
7573
|
-
|
|
8035
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
8036
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
8037
|
+
return items.reduce(
|
|
8038
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
8039
|
+
0
|
|
8040
|
+
);
|
|
8041
|
+
}
|
|
8042
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
8043
|
+
let maxWidth = 0;
|
|
8044
|
+
for (const lane of swimlane.lanes) {
|
|
8045
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
8046
|
+
continue;
|
|
8047
|
+
}
|
|
8048
|
+
for (const stack of rankStacks(
|
|
8049
|
+
lane.children,
|
|
8050
|
+
nodeBoxes,
|
|
8051
|
+
flowRanks
|
|
8052
|
+
).values()) {
|
|
8053
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
8054
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
8055
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
8056
|
+
}
|
|
8057
|
+
}
|
|
8058
|
+
return maxWidth;
|
|
8059
|
+
}
|
|
8060
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
7574
8061
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
7575
|
-
|
|
8062
|
+
const unlocked = [];
|
|
7576
8063
|
for (const item of stack) {
|
|
7577
|
-
|
|
7578
|
-
if (locks.has(childId)) {
|
|
8064
|
+
if (locks.has(item.childId)) {
|
|
7579
8065
|
diagnostics.push({
|
|
7580
8066
|
severity: "warning",
|
|
7581
8067
|
code: "constraints.locked-target-not-moved",
|
|
7582
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
8068
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
7583
8069
|
path: ["swimlanes"],
|
|
7584
|
-
detail: { nodeId: childId }
|
|
8070
|
+
detail: { nodeId: item.childId }
|
|
7585
8071
|
});
|
|
7586
|
-
|
|
8072
|
+
} else {
|
|
8073
|
+
unlocked.push(item);
|
|
7587
8074
|
}
|
|
8075
|
+
}
|
|
8076
|
+
if (unlocked.length === 0) continue;
|
|
8077
|
+
if (unlocked.length === 1) {
|
|
8078
|
+
const { childId, box } = unlocked[0];
|
|
7588
8079
|
const next = {
|
|
7589
8080
|
...box,
|
|
7590
|
-
x:
|
|
7591
|
-
y: target.y + rank * rankSpacing
|
|
8081
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
8082
|
+
y: target.y + rank * rankSpacing
|
|
7592
8083
|
};
|
|
7593
8084
|
if (next.x !== box.x || next.y !== box.y) {
|
|
7594
8085
|
movedChildIds.add(childId);
|
|
7595
8086
|
}
|
|
7596
8087
|
nodeBoxes.set(childId, next);
|
|
7597
|
-
|
|
8088
|
+
} else {
|
|
8089
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
8090
|
+
if (!shouldSpread) {
|
|
8091
|
+
let yOffset = 0;
|
|
8092
|
+
for (const { childId, box } of unlocked) {
|
|
8093
|
+
const next = {
|
|
8094
|
+
...box,
|
|
8095
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
8096
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
8097
|
+
};
|
|
8098
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
8099
|
+
movedChildIds.add(childId);
|
|
8100
|
+
}
|
|
8101
|
+
nodeBoxes.set(childId, next);
|
|
8102
|
+
yOffset += box.height + rankStackGap;
|
|
8103
|
+
}
|
|
8104
|
+
} else {
|
|
8105
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
8106
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
8107
|
+
for (const { childId, box } of unlocked) {
|
|
8108
|
+
const next = {
|
|
8109
|
+
...box,
|
|
8110
|
+
x: xCursor,
|
|
8111
|
+
y: target.y + rank * rankSpacing
|
|
8112
|
+
};
|
|
8113
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
8114
|
+
movedChildIds.add(childId);
|
|
8115
|
+
}
|
|
8116
|
+
nodeBoxes.set(childId, next);
|
|
8117
|
+
xCursor += box.width + rankStackGap;
|
|
8118
|
+
}
|
|
8119
|
+
diagnostics.push({
|
|
8120
|
+
severity: "info",
|
|
8121
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
8122
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
8123
|
+
path: ["swimlanes"],
|
|
8124
|
+
detail: {
|
|
8125
|
+
rank,
|
|
8126
|
+
childCount: unlocked.length,
|
|
8127
|
+
contentWidth
|
|
8128
|
+
}
|
|
8129
|
+
});
|
|
8130
|
+
}
|
|
7598
8131
|
}
|
|
7599
8132
|
}
|
|
7600
8133
|
}
|
|
@@ -7933,7 +8466,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7933
8466
|
});
|
|
7934
8467
|
continue;
|
|
7935
8468
|
}
|
|
7936
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
8469
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7937
8470
|
const geometry = computeShapeGeometry({
|
|
7938
8471
|
shape: node.shape,
|
|
7939
8472
|
box,
|
|
@@ -8027,7 +8560,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
8027
8560
|
}
|
|
8028
8561
|
}
|
|
8029
8562
|
}
|
|
8030
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
8563
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
8031
8564
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
8032
8565
|
for (const port of node.ports ?? []) {
|
|
8033
8566
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -8050,9 +8583,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8050
8583
|
side,
|
|
8051
8584
|
index,
|
|
8052
8585
|
sorted.length,
|
|
8053
|
-
portShifting
|
|
8054
|
-
diagnostics,
|
|
8055
|
-
node.id
|
|
8586
|
+
portShifting
|
|
8056
8587
|
);
|
|
8057
8588
|
const box = portBox(anchor);
|
|
8058
8589
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -8060,32 +8591,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8060
8591
|
}
|
|
8061
8592
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
8062
8593
|
}
|
|
8063
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
8594
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
8064
8595
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
8065
8596
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
8066
8597
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
8067
8598
|
const availableSpan = 2 * maxOffset;
|
|
8068
8599
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
8069
|
-
const
|
|
8600
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
8070
8601
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
8071
8602
|
minSpacing
|
|
8072
8603
|
) : requestedSpacing;
|
|
8073
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
8074
|
-
diagnostics.push({
|
|
8075
|
-
severity: "warning",
|
|
8076
|
-
code: "port_constraint_overlap",
|
|
8077
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
8078
|
-
path: ["nodes", nodeId, "ports"],
|
|
8079
|
-
detail: {
|
|
8080
|
-
nodeId,
|
|
8081
|
-
side,
|
|
8082
|
-
requestedSpacing,
|
|
8083
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
8084
|
-
portCount: count
|
|
8085
|
-
}
|
|
8086
|
-
});
|
|
8087
|
-
}
|
|
8088
|
-
const spacing = effectiveSpacing;
|
|
8089
8604
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
8090
8605
|
switch (side) {
|
|
8091
8606
|
case "left":
|
|
@@ -8700,14 +9215,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8700
9215
|
}
|
|
8701
9216
|
};
|
|
8702
9217
|
}
|
|
8703
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
9218
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8704
9219
|
const coordinated = [];
|
|
8705
9220
|
const coordinatedNodeById = new Map(
|
|
8706
9221
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8707
9222
|
);
|
|
9223
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
9224
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
9225
|
+
200,
|
|
9226
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
9227
|
+
);
|
|
9228
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
9229
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8708
9230
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8709
9231
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8710
|
-
|
|
9232
|
+
queryGutter
|
|
8711
9233
|
);
|
|
8712
9234
|
for (const edge of edges) {
|
|
8713
9235
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8729,11 +9251,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8729
9251
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8730
9252
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8731
9253
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8732
|
-
const corridor = edgeCorridorBox(
|
|
8733
|
-
source.box,
|
|
8734
|
-
target.box,
|
|
8735
|
-
options.routingGutter ?? 160
|
|
8736
|
-
);
|
|
9254
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8737
9255
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8738
9256
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8739
9257
|
);
|
|
@@ -8751,7 +9269,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8751
9269
|
...routeTextObstacles
|
|
8752
9270
|
],
|
|
8753
9271
|
hardObstacles,
|
|
8754
|
-
|
|
9272
|
+
corridorMargin,
|
|
9273
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
9274
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
8755
9275
|
});
|
|
8756
9276
|
diagnostics.push(
|
|
8757
9277
|
...route.diagnostics.map((diagnostic) => ({
|