@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.cjs
CHANGED
|
@@ -179,6 +179,28 @@ function applyLayoutConstraints(input) {
|
|
|
179
179
|
if (input.distributeContainedChildren) {
|
|
180
180
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
181
181
|
}
|
|
182
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
183
|
+
for (const swimlane of input.swimlanes) {
|
|
184
|
+
if (swimlane.layout === "contract") continue;
|
|
185
|
+
for (const lane of swimlane.lanes) {
|
|
186
|
+
const fixedChildren = [];
|
|
187
|
+
let participantCount = 0;
|
|
188
|
+
for (const childId of lane.children) {
|
|
189
|
+
const lock = locks.get(childId);
|
|
190
|
+
if (lock === void 0) {
|
|
191
|
+
participantCount += 1;
|
|
192
|
+
} else if (lock.source === "fixed-position") {
|
|
193
|
+
participantCount += 1;
|
|
194
|
+
fixedChildren.push(childId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (participantCount < 2) continue;
|
|
198
|
+
for (const childId of fixedChildren) {
|
|
199
|
+
locks.delete(childId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
182
204
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
183
205
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
184
206
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -198,6 +220,9 @@ function applyLayoutConstraints(input) {
|
|
|
198
220
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
199
221
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
200
222
|
}
|
|
223
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
224
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
225
|
+
}
|
|
201
226
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
202
227
|
reportOverlaps(
|
|
203
228
|
boxes,
|
|
@@ -1037,9 +1062,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1037
1062
|
}
|
|
1038
1063
|
});
|
|
1039
1064
|
}
|
|
1040
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1041
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1042
|
-
}
|
|
1043
1065
|
}
|
|
1044
1066
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
1045
1067
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -1079,6 +1101,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
1079
1101
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
1080
1102
|
}
|
|
1081
1103
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
1104
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
1082
1105
|
let pos = contentStart;
|
|
1083
1106
|
for (const child of unlocked) {
|
|
1084
1107
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4729,15 +4752,12 @@ var BinaryHeap = class {
|
|
|
4729
4752
|
let smallestIdx = idx;
|
|
4730
4753
|
const leftIdx = (idx << 1) + 1;
|
|
4731
4754
|
const rightIdx = leftIdx + 1;
|
|
4732
|
-
if (leftIdx < size && this._less(
|
|
4733
|
-
this._data[leftIdx],
|
|
4734
|
-
this._data[smallestIdx]
|
|
4735
|
-
)) {
|
|
4755
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4736
4756
|
smallestIdx = leftIdx;
|
|
4737
4757
|
}
|
|
4738
4758
|
if (rightIdx < size && this._less(
|
|
4739
4759
|
this._data[rightIdx],
|
|
4740
|
-
this._data[
|
|
4760
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4741
4761
|
)) {
|
|
4742
4762
|
smallestIdx = rightIdx;
|
|
4743
4763
|
}
|
|
@@ -4788,7 +4808,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4788
4808
|
detail: {
|
|
4789
4809
|
xsCount: xs.length,
|
|
4790
4810
|
ysCount: ys.length,
|
|
4791
|
-
maxNodes
|
|
4811
|
+
maxNodes,
|
|
4812
|
+
obstacleCount: obstacles.length,
|
|
4813
|
+
stage: "corridor-filtered",
|
|
4814
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4792
4815
|
}
|
|
4793
4816
|
});
|
|
4794
4817
|
return null;
|
|
@@ -4804,8 +4827,66 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4804
4827
|
turnPenalty,
|
|
4805
4828
|
segmentPenalty
|
|
4806
4829
|
);
|
|
4807
|
-
if (path
|
|
4808
|
-
|
|
4830
|
+
if (path !== null) {
|
|
4831
|
+
const simplified = simplifyRoute(path);
|
|
4832
|
+
const filteredSet = new Set(filtered);
|
|
4833
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4834
|
+
let crossesExcluded = false;
|
|
4835
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4836
|
+
const a = simplified[i];
|
|
4837
|
+
const b = simplified[i + 1];
|
|
4838
|
+
for (const obs of obstacles) {
|
|
4839
|
+
if (filteredSet.has(obs)) continue;
|
|
4840
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4841
|
+
crossesExcluded = true;
|
|
4842
|
+
break;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
if (crossesExcluded) break;
|
|
4846
|
+
}
|
|
4847
|
+
if (!crossesExcluded) return simplified;
|
|
4848
|
+
} else {
|
|
4849
|
+
return simplified;
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
if (!useCorridor) return null;
|
|
4853
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4854
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4855
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4856
|
+
diagnostics?.push({
|
|
4857
|
+
severity: "warning",
|
|
4858
|
+
code: "routing.astar.grid_overflow",
|
|
4859
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4860
|
+
detail: {
|
|
4861
|
+
xsCount: xsFull.length,
|
|
4862
|
+
ysCount: ysFull.length,
|
|
4863
|
+
maxNodes,
|
|
4864
|
+
obstacleCount: obstacles.length,
|
|
4865
|
+
stage: "full-retry",
|
|
4866
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
return null;
|
|
4870
|
+
}
|
|
4871
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4872
|
+
connectHorizontalEdges(
|
|
4873
|
+
nodesFull,
|
|
4874
|
+
ysFull,
|
|
4875
|
+
obstacles,
|
|
4876
|
+
endpointObstacles,
|
|
4877
|
+
margin
|
|
4878
|
+
);
|
|
4879
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4880
|
+
const pathFull = aStarSearch(
|
|
4881
|
+
nodesFull,
|
|
4882
|
+
idxFull,
|
|
4883
|
+
source,
|
|
4884
|
+
target,
|
|
4885
|
+
turnPenalty,
|
|
4886
|
+
segmentPenalty
|
|
4887
|
+
);
|
|
4888
|
+
if (pathFull === null) return null;
|
|
4889
|
+
return simplifyRoute(pathFull);
|
|
4809
4890
|
}
|
|
4810
4891
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4811
4892
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -5026,20 +5107,83 @@ function areCollinear(a, b, c) {
|
|
|
5026
5107
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
5027
5108
|
}
|
|
5028
5109
|
|
|
5110
|
+
// src/routing/budget.ts
|
|
5111
|
+
var MIN_CORNER_BUDGET = 600;
|
|
5112
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
5113
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
5114
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
5115
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
5116
|
+
var CORNER_HEADROOM = 2;
|
|
5117
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
5118
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
5119
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
5120
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
5121
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
5122
|
+
cornerObstacles.length,
|
|
5123
|
+
corridorMargin
|
|
5124
|
+
);
|
|
5125
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
5126
|
+
allObstacles.length,
|
|
5127
|
+
corridorMargin
|
|
5128
|
+
);
|
|
5129
|
+
return {
|
|
5130
|
+
maxCorners: resolveBudget(
|
|
5131
|
+
overrides.maxCorners,
|
|
5132
|
+
adaptiveMaxCorners,
|
|
5133
|
+
MIN_CORNER_BUDGET,
|
|
5134
|
+
MAX_CORNER_BUDGET
|
|
5135
|
+
),
|
|
5136
|
+
maxNodes: resolveBudget(
|
|
5137
|
+
overrides.maxNodes,
|
|
5138
|
+
adaptiveMaxNodes,
|
|
5139
|
+
MIN_NODE_BUDGET,
|
|
5140
|
+
MAX_NODE_BUDGET
|
|
5141
|
+
),
|
|
5142
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
5143
|
+
gridObstacleCount: allObstacles.length,
|
|
5144
|
+
corridorMargin
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
5148
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
5149
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
5150
|
+
return Math.ceil(base * corridorFactor);
|
|
5151
|
+
}
|
|
5152
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
5153
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
5154
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
5155
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
5156
|
+
}
|
|
5157
|
+
function corridorScalingFactor(corridorMargin) {
|
|
5158
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
5159
|
+
}
|
|
5160
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
5161
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
5162
|
+
return clamp(chosen, min, max);
|
|
5163
|
+
}
|
|
5164
|
+
function clamp(value, min, max) {
|
|
5165
|
+
return Math.max(min, Math.min(max, value));
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5029
5168
|
// src/routing/visibility-router.ts
|
|
5030
5169
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
5031
5170
|
const margin = options.margin ?? 0;
|
|
5032
5171
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
5033
5172
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
5034
5173
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
5035
|
-
const maxCorners = options.maxCorners ??
|
|
5174
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
5036
5175
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
5037
5176
|
if (vertices.length > maxCorners) {
|
|
5038
5177
|
diagnostics?.push({
|
|
5039
5178
|
severity: "warning",
|
|
5040
5179
|
code: "routing.visibility.corner_overflow",
|
|
5041
5180
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
5042
|
-
detail: {
|
|
5181
|
+
detail: {
|
|
5182
|
+
vertexCount: vertices.length,
|
|
5183
|
+
maxCorners,
|
|
5184
|
+
obstacleCount: obstacles.length,
|
|
5185
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
5186
|
+
}
|
|
5043
5187
|
});
|
|
5044
5188
|
return null;
|
|
5045
5189
|
}
|
|
@@ -5313,7 +5457,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
5313
5457
|
}
|
|
5314
5458
|
|
|
5315
5459
|
// src/routing/routes.ts
|
|
5316
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
5460
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
5317
5461
|
if (points.length < 2) return;
|
|
5318
5462
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
5319
5463
|
if (direct <= 0) return;
|
|
@@ -5323,7 +5467,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
5323
5467
|
const b = points[i + 1];
|
|
5324
5468
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
5325
5469
|
}
|
|
5326
|
-
const threshold =
|
|
5470
|
+
const threshold = maxRatio ?? 20;
|
|
5327
5471
|
if (routeLen > direct * threshold) {
|
|
5328
5472
|
diagnostics.push({
|
|
5329
5473
|
severity: "warning",
|
|
@@ -5341,8 +5485,20 @@ function routeEdge(input) {
|
|
|
5341
5485
|
const diagnostics = [];
|
|
5342
5486
|
const softObstacles = input.obstacles ?? [];
|
|
5343
5487
|
const hardObstacles = input.hardObstacles ?? [];
|
|
5488
|
+
let bestRejectedPath;
|
|
5489
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
5344
5490
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
5345
5491
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
5492
|
+
const recordRejected = (candidate) => {
|
|
5493
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
5494
|
+
return;
|
|
5495
|
+
}
|
|
5496
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
5497
|
+
if (crossings < bestRejectedCrossings) {
|
|
5498
|
+
bestRejectedCrossings = crossings;
|
|
5499
|
+
bestRejectedPath = candidate;
|
|
5500
|
+
}
|
|
5501
|
+
};
|
|
5346
5502
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
5347
5503
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
5348
5504
|
input.source.box,
|
|
@@ -5401,18 +5557,59 @@ function routeEdge(input) {
|
|
|
5401
5557
|
input.source.center,
|
|
5402
5558
|
targetAnchor
|
|
5403
5559
|
);
|
|
5404
|
-
const
|
|
5560
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
5561
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
5562
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
5405
5563
|
source,
|
|
5406
5564
|
target,
|
|
5407
|
-
|
|
5408
|
-
|
|
5565
|
+
allObstacles,
|
|
5566
|
+
[],
|
|
5567
|
+
// endpointObstacles passed separately via options
|
|
5568
|
+
corridorMargin
|
|
5569
|
+
);
|
|
5570
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
5571
|
+
const budget = computeRoutingBudget(
|
|
5572
|
+
cornerObstacles,
|
|
5573
|
+
allObstacles,
|
|
5574
|
+
corridorMargin,
|
|
5575
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
5576
|
+
);
|
|
5577
|
+
let cornerPath = findCornerGraphPath(
|
|
5578
|
+
source,
|
|
5579
|
+
target,
|
|
5580
|
+
cornerObstacles,
|
|
5581
|
+
{
|
|
5582
|
+
endpointObstacles,
|
|
5583
|
+
margin: 2,
|
|
5584
|
+
maxCorners: budget.maxCorners,
|
|
5585
|
+
corridorMargin
|
|
5586
|
+
},
|
|
5409
5587
|
diagnostics
|
|
5410
5588
|
);
|
|
5589
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
5590
|
+
cornerPath = findCornerGraphPath(
|
|
5591
|
+
source,
|
|
5592
|
+
target,
|
|
5593
|
+
allObstacles,
|
|
5594
|
+
{
|
|
5595
|
+
endpointObstacles,
|
|
5596
|
+
margin: 2,
|
|
5597
|
+
maxCorners: budget.maxCorners,
|
|
5598
|
+
corridorMargin
|
|
5599
|
+
},
|
|
5600
|
+
diagnostics
|
|
5601
|
+
);
|
|
5602
|
+
}
|
|
5411
5603
|
const path = cornerPath ?? findObstacleFreePath(
|
|
5412
5604
|
source,
|
|
5413
5605
|
target,
|
|
5414
|
-
|
|
5415
|
-
{
|
|
5606
|
+
allObstacles,
|
|
5607
|
+
{
|
|
5608
|
+
endpointObstacles,
|
|
5609
|
+
margin: 0,
|
|
5610
|
+
corridorMargin,
|
|
5611
|
+
maxNodes: budget.maxNodes
|
|
5612
|
+
},
|
|
5416
5613
|
diagnostics
|
|
5417
5614
|
);
|
|
5418
5615
|
if (path !== null && path.length >= 2) {
|
|
@@ -5429,9 +5626,100 @@ function routeEdge(input) {
|
|
|
5429
5626
|
softObstacles,
|
|
5430
5627
|
softObstacleIndex
|
|
5431
5628
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
5432
|
-
checkBacktracking(
|
|
5629
|
+
checkBacktracking(
|
|
5630
|
+
finalized,
|
|
5631
|
+
source,
|
|
5632
|
+
target,
|
|
5633
|
+
diagnostics,
|
|
5634
|
+
input.maxBacktrackingRatio
|
|
5635
|
+
);
|
|
5433
5636
|
return { points: finalized, diagnostics };
|
|
5434
5637
|
}
|
|
5638
|
+
recordRejected(finalized);
|
|
5639
|
+
if (cornerPath !== null) {
|
|
5640
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
5641
|
+
source,
|
|
5642
|
+
target,
|
|
5643
|
+
allObstacles,
|
|
5644
|
+
{
|
|
5645
|
+
endpointObstacles,
|
|
5646
|
+
margin: 2,
|
|
5647
|
+
maxCorners: budget.maxCorners,
|
|
5648
|
+
corridorMargin
|
|
5649
|
+
},
|
|
5650
|
+
diagnostics
|
|
5651
|
+
) : null;
|
|
5652
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5653
|
+
const fullFinalized = finalizeRoute(
|
|
5654
|
+
fullCornerPath,
|
|
5655
|
+
softObstacles,
|
|
5656
|
+
hardObstacles,
|
|
5657
|
+
diagnostics,
|
|
5658
|
+
softObstacleIndex,
|
|
5659
|
+
hardObstacleIndex
|
|
5660
|
+
);
|
|
5661
|
+
if (!routeIntersectsObstacles(
|
|
5662
|
+
fullFinalized,
|
|
5663
|
+
softObstacles,
|
|
5664
|
+
softObstacleIndex
|
|
5665
|
+
) && !routeIntersectsObstacles(
|
|
5666
|
+
fullFinalized,
|
|
5667
|
+
hardObstacles,
|
|
5668
|
+
hardObstacleIndex
|
|
5669
|
+
)) {
|
|
5670
|
+
checkBacktracking(
|
|
5671
|
+
fullFinalized,
|
|
5672
|
+
source,
|
|
5673
|
+
target,
|
|
5674
|
+
diagnostics,
|
|
5675
|
+
input.maxBacktrackingRatio
|
|
5676
|
+
);
|
|
5677
|
+
return { points: fullFinalized, diagnostics };
|
|
5678
|
+
}
|
|
5679
|
+
recordRejected(fullFinalized);
|
|
5680
|
+
}
|
|
5681
|
+
const gridPath = findObstacleFreePath(
|
|
5682
|
+
source,
|
|
5683
|
+
target,
|
|
5684
|
+
allObstacles,
|
|
5685
|
+
{
|
|
5686
|
+
endpointObstacles,
|
|
5687
|
+
margin: 0,
|
|
5688
|
+
corridorMargin,
|
|
5689
|
+
maxNodes: budget.maxNodes
|
|
5690
|
+
},
|
|
5691
|
+
diagnostics
|
|
5692
|
+
);
|
|
5693
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5694
|
+
const gridFinalized = finalizeRoute(
|
|
5695
|
+
gridPath,
|
|
5696
|
+
softObstacles,
|
|
5697
|
+
hardObstacles,
|
|
5698
|
+
diagnostics,
|
|
5699
|
+
softObstacleIndex,
|
|
5700
|
+
hardObstacleIndex
|
|
5701
|
+
);
|
|
5702
|
+
if (!routeIntersectsObstacles(
|
|
5703
|
+
gridFinalized,
|
|
5704
|
+
softObstacles,
|
|
5705
|
+
softObstacleIndex
|
|
5706
|
+
) && !routeIntersectsObstacles(
|
|
5707
|
+
gridFinalized,
|
|
5708
|
+
hardObstacles,
|
|
5709
|
+
hardObstacleIndex
|
|
5710
|
+
)) {
|
|
5711
|
+
checkBacktracking(
|
|
5712
|
+
gridFinalized,
|
|
5713
|
+
source,
|
|
5714
|
+
target,
|
|
5715
|
+
diagnostics,
|
|
5716
|
+
input.maxBacktrackingRatio
|
|
5717
|
+
);
|
|
5718
|
+
return { points: gridFinalized, diagnostics };
|
|
5719
|
+
}
|
|
5720
|
+
recordRejected(gridFinalized);
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5435
5723
|
}
|
|
5436
5724
|
}
|
|
5437
5725
|
}
|
|
@@ -5493,7 +5781,8 @@ function routeEdge(input) {
|
|
|
5493
5781
|
finalizedClean,
|
|
5494
5782
|
candidate.points[0],
|
|
5495
5783
|
candidate.points[candidate.points.length - 1],
|
|
5496
|
-
diagnostics
|
|
5784
|
+
diagnostics,
|
|
5785
|
+
input.maxBacktrackingRatio
|
|
5497
5786
|
);
|
|
5498
5787
|
return { points: finalizedClean, diagnostics };
|
|
5499
5788
|
}
|
|
@@ -5559,13 +5848,41 @@ function routeEdge(input) {
|
|
|
5559
5848
|
code: "routing.obstacle.unavoidable",
|
|
5560
5849
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5561
5850
|
});
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5851
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5852
|
+
bestPoints2,
|
|
5853
|
+
softObstacles,
|
|
5854
|
+
hardObstacles,
|
|
5855
|
+
diagnostics
|
|
5856
|
+
);
|
|
5857
|
+
let softFallback = finalizedSoftBest;
|
|
5858
|
+
if (bestRejectedPath !== void 0) {
|
|
5859
|
+
const finalizedRejected = finalizeRoute(
|
|
5860
|
+
bestRejectedPath,
|
|
5565
5861
|
softObstacles,
|
|
5566
5862
|
hardObstacles,
|
|
5567
5863
|
diagnostics
|
|
5568
|
-
)
|
|
5864
|
+
);
|
|
5865
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5866
|
+
finalizedRejected,
|
|
5867
|
+
softObstacles
|
|
5868
|
+
);
|
|
5869
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5870
|
+
finalizedSoftBest,
|
|
5871
|
+
softObstacles
|
|
5872
|
+
);
|
|
5873
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5874
|
+
softFallback = finalizedRejected;
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
checkBacktracking(
|
|
5878
|
+
softFallback,
|
|
5879
|
+
softFallback[0],
|
|
5880
|
+
softFallback[softFallback.length - 1],
|
|
5881
|
+
diagnostics,
|
|
5882
|
+
input.maxBacktrackingRatio
|
|
5883
|
+
);
|
|
5884
|
+
return {
|
|
5885
|
+
points: softFallback,
|
|
5569
5886
|
diagnostics
|
|
5570
5887
|
};
|
|
5571
5888
|
}
|
|
@@ -5597,6 +5914,22 @@ function routeEdge(input) {
|
|
|
5597
5914
|
maxAttempts
|
|
5598
5915
|
);
|
|
5599
5916
|
}
|
|
5917
|
+
if (bestRejectedPath !== void 0) {
|
|
5918
|
+
diagnostics.push({
|
|
5919
|
+
severity: "warning",
|
|
5920
|
+
code: "routing.obstacle.unavoidable",
|
|
5921
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5922
|
+
});
|
|
5923
|
+
return {
|
|
5924
|
+
points: finalizeRoute(
|
|
5925
|
+
bestRejectedPath,
|
|
5926
|
+
softObstacles,
|
|
5927
|
+
hardObstacles,
|
|
5928
|
+
diagnostics
|
|
5929
|
+
),
|
|
5930
|
+
diagnostics
|
|
5931
|
+
};
|
|
5932
|
+
}
|
|
5600
5933
|
diagnostics.push({
|
|
5601
5934
|
severity: "error",
|
|
5602
5935
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5644,13 +5977,41 @@ function routeEdge(input) {
|
|
|
5644
5977
|
code: "routing.obstacle.unavoidable",
|
|
5645
5978
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5646
5979
|
});
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5980
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5981
|
+
bestPoints,
|
|
5982
|
+
softObstacles,
|
|
5983
|
+
hardObstacles,
|
|
5984
|
+
diagnostics
|
|
5985
|
+
);
|
|
5986
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5987
|
+
if (bestRejectedPath !== void 0) {
|
|
5988
|
+
const finalizedRejected = finalizeRoute(
|
|
5989
|
+
bestRejectedPath,
|
|
5650
5990
|
softObstacles,
|
|
5651
5991
|
hardObstacles,
|
|
5652
5992
|
diagnostics
|
|
5653
|
-
)
|
|
5993
|
+
);
|
|
5994
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5995
|
+
finalizedRejected,
|
|
5996
|
+
softObstacles
|
|
5997
|
+
);
|
|
5998
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5999
|
+
finalizedBestPoints,
|
|
6000
|
+
softObstacles
|
|
6001
|
+
);
|
|
6002
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
6003
|
+
fallbackPoints = finalizedRejected;
|
|
6004
|
+
}
|
|
6005
|
+
}
|
|
6006
|
+
checkBacktracking(
|
|
6007
|
+
fallbackPoints,
|
|
6008
|
+
fallbackPoints[0],
|
|
6009
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
6010
|
+
diagnostics,
|
|
6011
|
+
input.maxBacktrackingRatio
|
|
6012
|
+
);
|
|
6013
|
+
return {
|
|
6014
|
+
points: fallbackPoints,
|
|
5654
6015
|
diagnostics
|
|
5655
6016
|
};
|
|
5656
6017
|
}
|
|
@@ -6149,6 +6510,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
6149
6510
|
}
|
|
6150
6511
|
return false;
|
|
6151
6512
|
}
|
|
6513
|
+
function countObstacleCrossings(points, obstacles) {
|
|
6514
|
+
let count = 0;
|
|
6515
|
+
for (const obstacle of obstacles) {
|
|
6516
|
+
validateBox(obstacle);
|
|
6517
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
6518
|
+
const a = points[pointIndex];
|
|
6519
|
+
const b = points[pointIndex + 1];
|
|
6520
|
+
if (a === void 0 || b === void 0) {
|
|
6521
|
+
continue;
|
|
6522
|
+
}
|
|
6523
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
6524
|
+
count += 1;
|
|
6525
|
+
break;
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
return count;
|
|
6530
|
+
}
|
|
6152
6531
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
6153
6532
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
6154
6533
|
const a = points[index];
|
|
@@ -6532,7 +6911,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6532
6911
|
edges: styledEdges
|
|
6533
6912
|
});
|
|
6534
6913
|
diagnostics.push(...layout2.diagnostics);
|
|
6535
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6914
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6536
6915
|
layout2.boxes,
|
|
6537
6916
|
styledNodes,
|
|
6538
6917
|
styledEdges,
|
|
@@ -6540,7 +6919,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6540
6919
|
options,
|
|
6541
6920
|
diagnostics
|
|
6542
6921
|
);
|
|
6543
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6922
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6923
|
+
const diagCountBefore = diagnostics.length;
|
|
6544
6924
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
6545
6925
|
initialNodeBoxes,
|
|
6546
6926
|
styledNodes,
|
|
@@ -6551,6 +6931,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6551
6931
|
for (const [id, box] of rewrapped) {
|
|
6552
6932
|
initialNodeBoxes.set(id, box);
|
|
6553
6933
|
}
|
|
6934
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6935
|
+
for (const node of styledNodes) {
|
|
6936
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6937
|
+
const rwBox = rewrapped.get(node.id);
|
|
6938
|
+
const idx = styledNodes.indexOf(node);
|
|
6939
|
+
if (idx !== -1) {
|
|
6940
|
+
styledNodes[idx] = {
|
|
6941
|
+
...node,
|
|
6942
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6943
|
+
};
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
}
|
|
6554
6948
|
}
|
|
6555
6949
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
6556
6950
|
const recursiveLayout = layout2;
|
|
@@ -6564,7 +6958,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6564
6958
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
6565
6959
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
6566
6960
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
6567
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6961
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
6568
6962
|
swimlanes: styledSwimlanes,
|
|
6569
6963
|
boxes: initialNodeBoxes,
|
|
6570
6964
|
nodes: styledNodes,
|
|
@@ -6579,7 +6973,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6579
6973
|
constrained.boxes,
|
|
6580
6974
|
constrained.locks,
|
|
6581
6975
|
options?.overlapSpacing ?? 40,
|
|
6582
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6976
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6977
|
+
options.distributeContainedChildren ?? true
|
|
6583
6978
|
);
|
|
6584
6979
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
6585
6980
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6746,7 +7141,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6746
7141
|
diagram.direction,
|
|
6747
7142
|
options,
|
|
6748
7143
|
diagnostics,
|
|
6749
|
-
coordinatedGroups
|
|
7144
|
+
coordinatedGroups,
|
|
7145
|
+
contentBounds
|
|
6750
7146
|
);
|
|
6751
7147
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6752
7148
|
coordinatedEdges,
|
|
@@ -7168,7 +7564,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
7168
7564
|
function containsCjk(value) {
|
|
7169
7565
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
7170
7566
|
}
|
|
7171
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
7567
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
7172
7568
|
const layouts = /* @__PURE__ */ new Map();
|
|
7173
7569
|
const diagnostics = [];
|
|
7174
7570
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -7187,7 +7583,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
7187
7583
|
locks,
|
|
7188
7584
|
diagnostics,
|
|
7189
7585
|
movedChildIds,
|
|
7190
|
-
laneGutter
|
|
7586
|
+
laneGutter,
|
|
7587
|
+
constraints,
|
|
7588
|
+
distributeContainedChildren
|
|
7191
7589
|
);
|
|
7192
7590
|
if (layout2 !== void 0) {
|
|
7193
7591
|
layouts.set(swimlane.id, layout2);
|
|
@@ -7283,9 +7681,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
7283
7681
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
7284
7682
|
return new Map(boxes);
|
|
7285
7683
|
}
|
|
7286
|
-
|
|
7287
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
7288
|
-
|
|
7684
|
+
let maxRowDepth = options.maxRowDepth;
|
|
7685
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
7686
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
7687
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
7688
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
7689
|
+
} else {
|
|
7690
|
+
return new Map(boxes);
|
|
7691
|
+
}
|
|
7289
7692
|
}
|
|
7290
7693
|
const ordered = [...nodes].sort((a, b) => {
|
|
7291
7694
|
const ba = boxes.get(a.id);
|
|
@@ -7346,10 +7749,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
7346
7749
|
});
|
|
7347
7750
|
}
|
|
7348
7751
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
7349
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
7752
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
7350
7753
|
return false;
|
|
7351
7754
|
}
|
|
7352
|
-
if (nodes.length < 2
|
|
7755
|
+
if (nodes.length < 2) {
|
|
7353
7756
|
return false;
|
|
7354
7757
|
}
|
|
7355
7758
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -7357,17 +7760,24 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
7357
7760
|
return false;
|
|
7358
7761
|
}
|
|
7359
7762
|
const bounds = unionBoxes(nodeBoxes);
|
|
7360
|
-
const
|
|
7361
|
-
const
|
|
7362
|
-
|
|
7763
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
7764
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
7765
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
7766
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
7363
7767
|
return false;
|
|
7364
7768
|
}
|
|
7769
|
+
if (isHorizontal) {
|
|
7770
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
7771
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
7772
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
7773
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
7774
|
+
}
|
|
7365
7775
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
7366
7776
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
7367
7777
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
7368
7778
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
7369
7779
|
}
|
|
7370
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7780
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7371
7781
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
7372
7782
|
const padding = swimlane.padding ?? 16;
|
|
7373
7783
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -7392,7 +7802,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7392
7802
|
locks,
|
|
7393
7803
|
diagnostics,
|
|
7394
7804
|
movedChildIds,
|
|
7395
|
-
laneGutter
|
|
7805
|
+
laneGutter,
|
|
7806
|
+
constraints,
|
|
7807
|
+
distributeContainedChildren
|
|
7396
7808
|
);
|
|
7397
7809
|
}
|
|
7398
7810
|
return applyHorizontalSwimlaneContract(
|
|
@@ -7407,13 +7819,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7407
7819
|
laneGutter
|
|
7408
7820
|
);
|
|
7409
7821
|
}
|
|
7410
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7822
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7411
7823
|
const populatedBounds = laneBounds.filter(
|
|
7412
7824
|
(box) => box !== void 0
|
|
7413
7825
|
);
|
|
7414
7826
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
7415
7827
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
7416
7828
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
7829
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
7830
|
+
if (distributeContainedChildren) {
|
|
7831
|
+
for (const c of constraints) {
|
|
7832
|
+
if (c.kind !== "containment") continue;
|
|
7833
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
7834
|
+
const distributable = c.childIds.filter((childId) => {
|
|
7835
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
7836
|
+
const lock = locks.get(childId);
|
|
7837
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
7838
|
+
});
|
|
7839
|
+
if (distributable.length < 2) continue;
|
|
7840
|
+
for (const childId of distributable) {
|
|
7841
|
+
containedChildIds.add(childId);
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7417
7845
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
7418
7846
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
7419
7847
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -7425,7 +7853,18 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7425
7853
|
);
|
|
7426
7854
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
7427
7855
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
7428
|
-
const
|
|
7856
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
7857
|
+
swimlane,
|
|
7858
|
+
nodeBoxes,
|
|
7859
|
+
flowRanks,
|
|
7860
|
+
locks,
|
|
7861
|
+
rankStackGap,
|
|
7862
|
+
containedChildIds
|
|
7863
|
+
);
|
|
7864
|
+
const slotWidth = Math.max(
|
|
7865
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7866
|
+
spreadWidth
|
|
7867
|
+
) + padding * 2;
|
|
7429
7868
|
const laneStep = slotWidth + laneGutter;
|
|
7430
7869
|
const laneContentTop = top + headerHeight + padding;
|
|
7431
7870
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -7439,6 +7878,27 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7439
7878
|
y: laneContentTop
|
|
7440
7879
|
};
|
|
7441
7880
|
if (maxRank === 0) {
|
|
7881
|
+
const distributable = lane.children.filter(
|
|
7882
|
+
(childId) => !locks.has(childId)
|
|
7883
|
+
);
|
|
7884
|
+
const coveredByContainment = lane.children.some(
|
|
7885
|
+
(childId) => containedChildIds.has(childId)
|
|
7886
|
+
);
|
|
7887
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7888
|
+
moveRankedVerticalLaneChildren(
|
|
7889
|
+
lane.children,
|
|
7890
|
+
nodeBoxes,
|
|
7891
|
+
locks,
|
|
7892
|
+
diagnostics,
|
|
7893
|
+
movedChildIds,
|
|
7894
|
+
flowRanks,
|
|
7895
|
+
rankSpacing,
|
|
7896
|
+
rankStackGap,
|
|
7897
|
+
{ x: target.x, y: laneContentTop },
|
|
7898
|
+
slotWidth - padding * 2
|
|
7899
|
+
);
|
|
7900
|
+
continue;
|
|
7901
|
+
}
|
|
7442
7902
|
moveLaneChildren(
|
|
7443
7903
|
lane.children,
|
|
7444
7904
|
nodeBoxes,
|
|
@@ -7452,6 +7912,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7452
7912
|
);
|
|
7453
7913
|
continue;
|
|
7454
7914
|
}
|
|
7915
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7916
|
+
(childId) => containedChildIds.has(childId)
|
|
7917
|
+
);
|
|
7455
7918
|
moveRankedVerticalLaneChildren(
|
|
7456
7919
|
lane.children,
|
|
7457
7920
|
nodeBoxes,
|
|
@@ -7461,10 +7924,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7461
7924
|
flowRanks,
|
|
7462
7925
|
rankSpacing,
|
|
7463
7926
|
rankStackGap,
|
|
7464
|
-
{
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
}
|
|
7927
|
+
{ x: target.x, y: laneContentTop },
|
|
7928
|
+
slotWidth - padding * 2,
|
|
7929
|
+
rankedCoveredByContainment
|
|
7468
7930
|
);
|
|
7469
7931
|
}
|
|
7470
7932
|
return {
|
|
@@ -7573,31 +8035,102 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
7573
8035
|
}
|
|
7574
8036
|
return maxHeight;
|
|
7575
8037
|
}
|
|
7576
|
-
|
|
8038
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
8039
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
8040
|
+
return items.reduce(
|
|
8041
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
8042
|
+
0
|
|
8043
|
+
);
|
|
8044
|
+
}
|
|
8045
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
8046
|
+
let maxWidth = 0;
|
|
8047
|
+
for (const lane of swimlane.lanes) {
|
|
8048
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
8049
|
+
continue;
|
|
8050
|
+
}
|
|
8051
|
+
for (const stack of rankStacks(
|
|
8052
|
+
lane.children,
|
|
8053
|
+
nodeBoxes,
|
|
8054
|
+
flowRanks
|
|
8055
|
+
).values()) {
|
|
8056
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
8057
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
8058
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
return maxWidth;
|
|
8062
|
+
}
|
|
8063
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
7577
8064
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
7578
|
-
|
|
8065
|
+
const unlocked = [];
|
|
7579
8066
|
for (const item of stack) {
|
|
7580
|
-
|
|
7581
|
-
if (locks.has(childId)) {
|
|
8067
|
+
if (locks.has(item.childId)) {
|
|
7582
8068
|
diagnostics.push({
|
|
7583
8069
|
severity: "warning",
|
|
7584
8070
|
code: "constraints.locked-target-not-moved",
|
|
7585
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
8071
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
7586
8072
|
path: ["swimlanes"],
|
|
7587
|
-
detail: { nodeId: childId }
|
|
8073
|
+
detail: { nodeId: item.childId }
|
|
7588
8074
|
});
|
|
7589
|
-
|
|
8075
|
+
} else {
|
|
8076
|
+
unlocked.push(item);
|
|
7590
8077
|
}
|
|
8078
|
+
}
|
|
8079
|
+
if (unlocked.length === 0) continue;
|
|
8080
|
+
if (unlocked.length === 1) {
|
|
8081
|
+
const { childId, box } = unlocked[0];
|
|
7591
8082
|
const next = {
|
|
7592
8083
|
...box,
|
|
7593
|
-
x:
|
|
7594
|
-
y: target.y + rank * rankSpacing
|
|
8084
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
8085
|
+
y: target.y + rank * rankSpacing
|
|
7595
8086
|
};
|
|
7596
8087
|
if (next.x !== box.x || next.y !== box.y) {
|
|
7597
8088
|
movedChildIds.add(childId);
|
|
7598
8089
|
}
|
|
7599
8090
|
nodeBoxes.set(childId, next);
|
|
7600
|
-
|
|
8091
|
+
} else {
|
|
8092
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
8093
|
+
if (!shouldSpread) {
|
|
8094
|
+
let yOffset = 0;
|
|
8095
|
+
for (const { childId, box } of unlocked) {
|
|
8096
|
+
const next = {
|
|
8097
|
+
...box,
|
|
8098
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
8099
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
8100
|
+
};
|
|
8101
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
8102
|
+
movedChildIds.add(childId);
|
|
8103
|
+
}
|
|
8104
|
+
nodeBoxes.set(childId, next);
|
|
8105
|
+
yOffset += box.height + rankStackGap;
|
|
8106
|
+
}
|
|
8107
|
+
} else {
|
|
8108
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
8109
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
8110
|
+
for (const { childId, box } of unlocked) {
|
|
8111
|
+
const next = {
|
|
8112
|
+
...box,
|
|
8113
|
+
x: xCursor,
|
|
8114
|
+
y: target.y + rank * rankSpacing
|
|
8115
|
+
};
|
|
8116
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
8117
|
+
movedChildIds.add(childId);
|
|
8118
|
+
}
|
|
8119
|
+
nodeBoxes.set(childId, next);
|
|
8120
|
+
xCursor += box.width + rankStackGap;
|
|
8121
|
+
}
|
|
8122
|
+
diagnostics.push({
|
|
8123
|
+
severity: "info",
|
|
8124
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
8125
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
8126
|
+
path: ["swimlanes"],
|
|
8127
|
+
detail: {
|
|
8128
|
+
rank,
|
|
8129
|
+
childCount: unlocked.length,
|
|
8130
|
+
contentWidth
|
|
8131
|
+
}
|
|
8132
|
+
});
|
|
8133
|
+
}
|
|
7601
8134
|
}
|
|
7602
8135
|
}
|
|
7603
8136
|
}
|
|
@@ -7936,7 +8469,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7936
8469
|
});
|
|
7937
8470
|
continue;
|
|
7938
8471
|
}
|
|
7939
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
8472
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7940
8473
|
const geometry = computeShapeGeometry({
|
|
7941
8474
|
shape: node.shape,
|
|
7942
8475
|
box,
|
|
@@ -8030,7 +8563,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
8030
8563
|
}
|
|
8031
8564
|
}
|
|
8032
8565
|
}
|
|
8033
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
8566
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
8034
8567
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
8035
8568
|
for (const port of node.ports ?? []) {
|
|
8036
8569
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -8053,9 +8586,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8053
8586
|
side,
|
|
8054
8587
|
index,
|
|
8055
8588
|
sorted.length,
|
|
8056
|
-
portShifting
|
|
8057
|
-
diagnostics,
|
|
8058
|
-
node.id
|
|
8589
|
+
portShifting
|
|
8059
8590
|
);
|
|
8060
8591
|
const box = portBox(anchor);
|
|
8061
8592
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -8063,32 +8594,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8063
8594
|
}
|
|
8064
8595
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
8065
8596
|
}
|
|
8066
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
8597
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
8067
8598
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
8068
8599
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
8069
8600
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
8070
8601
|
const availableSpan = 2 * maxOffset;
|
|
8071
8602
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
8072
|
-
const
|
|
8603
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
8073
8604
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
8074
8605
|
minSpacing
|
|
8075
8606
|
) : requestedSpacing;
|
|
8076
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
8077
|
-
diagnostics.push({
|
|
8078
|
-
severity: "warning",
|
|
8079
|
-
code: "port_constraint_overlap",
|
|
8080
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
8081
|
-
path: ["nodes", nodeId, "ports"],
|
|
8082
|
-
detail: {
|
|
8083
|
-
nodeId,
|
|
8084
|
-
side,
|
|
8085
|
-
requestedSpacing,
|
|
8086
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
8087
|
-
portCount: count
|
|
8088
|
-
}
|
|
8089
|
-
});
|
|
8090
|
-
}
|
|
8091
|
-
const spacing = effectiveSpacing;
|
|
8092
8607
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
8093
8608
|
switch (side) {
|
|
8094
8609
|
case "left":
|
|
@@ -8703,14 +9218,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8703
9218
|
}
|
|
8704
9219
|
};
|
|
8705
9220
|
}
|
|
8706
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
9221
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8707
9222
|
const coordinated = [];
|
|
8708
9223
|
const coordinatedNodeById = new Map(
|
|
8709
9224
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8710
9225
|
);
|
|
9226
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
9227
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
9228
|
+
200,
|
|
9229
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
9230
|
+
);
|
|
9231
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
9232
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8711
9233
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8712
9234
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8713
|
-
|
|
9235
|
+
queryGutter
|
|
8714
9236
|
);
|
|
8715
9237
|
for (const edge of edges) {
|
|
8716
9238
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8732,11 +9254,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8732
9254
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8733
9255
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8734
9256
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8735
|
-
const corridor = edgeCorridorBox(
|
|
8736
|
-
source.box,
|
|
8737
|
-
target.box,
|
|
8738
|
-
options.routingGutter ?? 160
|
|
8739
|
-
);
|
|
9257
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8740
9258
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8741
9259
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8742
9260
|
);
|
|
@@ -8754,7 +9272,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8754
9272
|
...routeTextObstacles
|
|
8755
9273
|
],
|
|
8756
9274
|
hardObstacles,
|
|
8757
|
-
|
|
9275
|
+
corridorMargin,
|
|
9276
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
9277
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
8758
9278
|
});
|
|
8759
9279
|
diagnostics.push(
|
|
8760
9280
|
...route.diagnostics.map((diagnostic) => ({
|