@crazyhappyone/auto-graph 0.2.7 → 0.2.9
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 +316 -63
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +316 -63
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +316 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +316 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -1394,6 +1394,28 @@ function applyLayoutConstraints(input) {
|
|
|
1394
1394
|
if (input.distributeContainedChildren) {
|
|
1395
1395
|
yieldFixedPositionLocks(input, boxes, locks);
|
|
1396
1396
|
}
|
|
1397
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1398
|
+
for (const swimlane of input.swimlanes) {
|
|
1399
|
+
if (swimlane.layout === "contract") continue;
|
|
1400
|
+
for (const lane of swimlane.lanes) {
|
|
1401
|
+
const fixedChildren = [];
|
|
1402
|
+
let participantCount = 0;
|
|
1403
|
+
for (const childId of lane.children) {
|
|
1404
|
+
const lock = locks.get(childId);
|
|
1405
|
+
if (lock === void 0) {
|
|
1406
|
+
participantCount += 1;
|
|
1407
|
+
} else if (lock.source === "fixed-position") {
|
|
1408
|
+
participantCount += 1;
|
|
1409
|
+
fixedChildren.push(childId);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (participantCount < 2) continue;
|
|
1413
|
+
for (const childId of fixedChildren) {
|
|
1414
|
+
locks.delete(childId);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1397
1419
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
1398
1420
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
1399
1421
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -1413,6 +1435,9 @@ function applyLayoutConstraints(input) {
|
|
|
1413
1435
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1414
1436
|
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
1415
1437
|
}
|
|
1438
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1439
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1440
|
+
}
|
|
1416
1441
|
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
1417
1442
|
reportOverlaps(
|
|
1418
1443
|
boxes,
|
|
@@ -2252,9 +2277,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
2252
2277
|
}
|
|
2253
2278
|
});
|
|
2254
2279
|
}
|
|
2255
|
-
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
2256
|
-
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
2257
|
-
}
|
|
2258
2280
|
}
|
|
2259
2281
|
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
2260
2282
|
const spread = input.distributeSwimlaneChildren === "spread";
|
|
@@ -2294,6 +2316,7 @@ function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
|
2294
2316
|
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
2295
2317
|
}
|
|
2296
2318
|
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
2319
|
+
reserved.sort((a, b) => a.start - b.start);
|
|
2297
2320
|
let pos = contentStart;
|
|
2298
2321
|
for (const child of unlocked) {
|
|
2299
2322
|
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
@@ -4085,15 +4108,12 @@ var BinaryHeap = class {
|
|
|
4085
4108
|
let smallestIdx = idx;
|
|
4086
4109
|
const leftIdx = (idx << 1) + 1;
|
|
4087
4110
|
const rightIdx = leftIdx + 1;
|
|
4088
|
-
if (leftIdx < size && this._less(
|
|
4089
|
-
this._data[leftIdx],
|
|
4090
|
-
this._data[smallestIdx]
|
|
4091
|
-
)) {
|
|
4111
|
+
if (leftIdx < size && this._less(this._data[leftIdx], entry)) {
|
|
4092
4112
|
smallestIdx = leftIdx;
|
|
4093
4113
|
}
|
|
4094
4114
|
if (rightIdx < size && this._less(
|
|
4095
4115
|
this._data[rightIdx],
|
|
4096
|
-
this._data[
|
|
4116
|
+
smallestIdx === leftIdx ? this._data[leftIdx] : entry
|
|
4097
4117
|
)) {
|
|
4098
4118
|
smallestIdx = rightIdx;
|
|
4099
4119
|
}
|
|
@@ -4160,8 +4180,59 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4160
4180
|
turnPenalty,
|
|
4161
4181
|
segmentPenalty
|
|
4162
4182
|
);
|
|
4163
|
-
if (path
|
|
4164
|
-
|
|
4183
|
+
if (path !== null) {
|
|
4184
|
+
const simplified = simplifyRoute(path);
|
|
4185
|
+
const filteredSet = new Set(filtered);
|
|
4186
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4187
|
+
let crossesExcluded = false;
|
|
4188
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4189
|
+
const a = simplified[i];
|
|
4190
|
+
const b = simplified[i + 1];
|
|
4191
|
+
for (const obs of obstacles) {
|
|
4192
|
+
if (filteredSet.has(obs)) continue;
|
|
4193
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4194
|
+
crossesExcluded = true;
|
|
4195
|
+
break;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
if (crossesExcluded) break;
|
|
4199
|
+
}
|
|
4200
|
+
if (!crossesExcluded) return simplified;
|
|
4201
|
+
} else {
|
|
4202
|
+
return simplified;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (!useCorridor) return null;
|
|
4206
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4207
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4208
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4209
|
+
diagnostics?.push({
|
|
4210
|
+
severity: "warning",
|
|
4211
|
+
code: "routing.astar.grid_overflow",
|
|
4212
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4213
|
+
detail: { xsCount: xsFull.length, ysCount: ysFull.length, maxNodes }
|
|
4214
|
+
});
|
|
4215
|
+
return null;
|
|
4216
|
+
}
|
|
4217
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4218
|
+
connectHorizontalEdges(
|
|
4219
|
+
nodesFull,
|
|
4220
|
+
ysFull,
|
|
4221
|
+
obstacles,
|
|
4222
|
+
endpointObstacles,
|
|
4223
|
+
margin
|
|
4224
|
+
);
|
|
4225
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4226
|
+
const pathFull = aStarSearch(
|
|
4227
|
+
nodesFull,
|
|
4228
|
+
idxFull,
|
|
4229
|
+
source,
|
|
4230
|
+
target,
|
|
4231
|
+
turnPenalty,
|
|
4232
|
+
segmentPenalty
|
|
4233
|
+
);
|
|
4234
|
+
if (pathFull === null) return null;
|
|
4235
|
+
return simplifyRoute(pathFull);
|
|
4165
4236
|
}
|
|
4166
4237
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4167
4238
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -4388,7 +4459,7 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
4388
4459
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
4389
4460
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
4390
4461
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
4391
|
-
const maxCorners = options.maxCorners ??
|
|
4462
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
4392
4463
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
4393
4464
|
if (vertices.length > maxCorners) {
|
|
4394
4465
|
diagnostics?.push({
|
|
@@ -4757,17 +4828,36 @@ function routeEdge(input) {
|
|
|
4757
4828
|
input.source.center,
|
|
4758
4829
|
targetAnchor
|
|
4759
4830
|
);
|
|
4760
|
-
const
|
|
4831
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4832
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
4761
4833
|
source,
|
|
4762
4834
|
target,
|
|
4763
|
-
|
|
4835
|
+
allObstacles,
|
|
4836
|
+
[],
|
|
4837
|
+
// endpointObstacles passed separately via options
|
|
4838
|
+
32
|
|
4839
|
+
);
|
|
4840
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4841
|
+
let cornerPath = findCornerGraphPath(
|
|
4842
|
+
source,
|
|
4843
|
+
target,
|
|
4844
|
+
cornerObstacles,
|
|
4764
4845
|
{ endpointObstacles, margin: 2 },
|
|
4765
4846
|
diagnostics
|
|
4766
4847
|
);
|
|
4848
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
4849
|
+
cornerPath = findCornerGraphPath(
|
|
4850
|
+
source,
|
|
4851
|
+
target,
|
|
4852
|
+
allObstacles,
|
|
4853
|
+
{ endpointObstacles, margin: 2 },
|
|
4854
|
+
diagnostics
|
|
4855
|
+
);
|
|
4856
|
+
}
|
|
4767
4857
|
const path = cornerPath ?? findObstacleFreePath(
|
|
4768
4858
|
source,
|
|
4769
4859
|
target,
|
|
4770
|
-
|
|
4860
|
+
allObstacles,
|
|
4771
4861
|
{ endpointObstacles, margin: 0 },
|
|
4772
4862
|
diagnostics
|
|
4773
4863
|
);
|
|
@@ -4788,6 +4878,66 @@ function routeEdge(input) {
|
|
|
4788
4878
|
checkBacktracking(finalized, source, target, diagnostics);
|
|
4789
4879
|
return { points: finalized, diagnostics };
|
|
4790
4880
|
}
|
|
4881
|
+
if (cornerPath !== null) {
|
|
4882
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4883
|
+
source,
|
|
4884
|
+
target,
|
|
4885
|
+
allObstacles,
|
|
4886
|
+
{ endpointObstacles, margin: 2 },
|
|
4887
|
+
diagnostics
|
|
4888
|
+
) : null;
|
|
4889
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
4890
|
+
const fullFinalized = finalizeRoute(
|
|
4891
|
+
fullCornerPath,
|
|
4892
|
+
softObstacles,
|
|
4893
|
+
hardObstacles,
|
|
4894
|
+
diagnostics,
|
|
4895
|
+
softObstacleIndex,
|
|
4896
|
+
hardObstacleIndex
|
|
4897
|
+
);
|
|
4898
|
+
if (!routeIntersectsObstacles(
|
|
4899
|
+
fullFinalized,
|
|
4900
|
+
softObstacles,
|
|
4901
|
+
softObstacleIndex
|
|
4902
|
+
) && !routeIntersectsObstacles(
|
|
4903
|
+
fullFinalized,
|
|
4904
|
+
hardObstacles,
|
|
4905
|
+
hardObstacleIndex
|
|
4906
|
+
)) {
|
|
4907
|
+
checkBacktracking(fullFinalized, source, target, diagnostics);
|
|
4908
|
+
return { points: fullFinalized, diagnostics };
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
const gridPath = findObstacleFreePath(
|
|
4912
|
+
source,
|
|
4913
|
+
target,
|
|
4914
|
+
allObstacles,
|
|
4915
|
+
{ endpointObstacles, margin: 0 },
|
|
4916
|
+
diagnostics
|
|
4917
|
+
);
|
|
4918
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
4919
|
+
const gridFinalized = finalizeRoute(
|
|
4920
|
+
gridPath,
|
|
4921
|
+
softObstacles,
|
|
4922
|
+
hardObstacles,
|
|
4923
|
+
diagnostics,
|
|
4924
|
+
softObstacleIndex,
|
|
4925
|
+
hardObstacleIndex
|
|
4926
|
+
);
|
|
4927
|
+
if (!routeIntersectsObstacles(
|
|
4928
|
+
gridFinalized,
|
|
4929
|
+
softObstacles,
|
|
4930
|
+
softObstacleIndex
|
|
4931
|
+
) && !routeIntersectsObstacles(
|
|
4932
|
+
gridFinalized,
|
|
4933
|
+
hardObstacles,
|
|
4934
|
+
hardObstacleIndex
|
|
4935
|
+
)) {
|
|
4936
|
+
checkBacktracking(gridFinalized, source, target, diagnostics);
|
|
4937
|
+
return { points: gridFinalized, diagnostics };
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4791
4941
|
}
|
|
4792
4942
|
}
|
|
4793
4943
|
}
|
|
@@ -5677,7 +5827,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5677
5827
|
edges: styledEdges
|
|
5678
5828
|
});
|
|
5679
5829
|
diagnostics.push(...layout2.diagnostics);
|
|
5680
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5830
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
5681
5831
|
layout2.boxes,
|
|
5682
5832
|
styledNodes,
|
|
5683
5833
|
styledEdges,
|
|
@@ -5685,7 +5835,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5685
5835
|
options,
|
|
5686
5836
|
diagnostics
|
|
5687
5837
|
);
|
|
5688
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
5838
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
5839
|
+
const diagCountBefore = diagnostics.length;
|
|
5689
5840
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
5690
5841
|
initialNodeBoxes,
|
|
5691
5842
|
styledNodes,
|
|
@@ -5696,6 +5847,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5696
5847
|
for (const [id, box] of rewrapped) {
|
|
5697
5848
|
initialNodeBoxes.set(id, box);
|
|
5698
5849
|
}
|
|
5850
|
+
if (diagnostics.length > diagCountBefore) {
|
|
5851
|
+
for (const node of styledNodes) {
|
|
5852
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
5853
|
+
const rwBox = rewrapped.get(node.id);
|
|
5854
|
+
const idx = styledNodes.indexOf(node);
|
|
5855
|
+
if (idx !== -1) {
|
|
5856
|
+
styledNodes[idx] = {
|
|
5857
|
+
...node,
|
|
5858
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
5859
|
+
};
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5699
5864
|
}
|
|
5700
5865
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
5701
5866
|
const recursiveLayout = layout2;
|
|
@@ -5709,7 +5874,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5709
5874
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
5710
5875
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
5711
5876
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
5712
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
5877
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
5713
5878
|
swimlanes: styledSwimlanes,
|
|
5714
5879
|
boxes: initialNodeBoxes,
|
|
5715
5880
|
nodes: styledNodes,
|
|
@@ -6425,9 +6590,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
6425
6590
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
6426
6591
|
return new Map(boxes);
|
|
6427
6592
|
}
|
|
6428
|
-
|
|
6429
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
6430
|
-
|
|
6593
|
+
let maxRowDepth = options.maxRowDepth;
|
|
6594
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
6595
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
6596
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
6597
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
6598
|
+
} else {
|
|
6599
|
+
return new Map(boxes);
|
|
6600
|
+
}
|
|
6431
6601
|
}
|
|
6432
6602
|
const ordered = [...nodes].sort((a, b) => {
|
|
6433
6603
|
const ba = boxes.get(a.id);
|
|
@@ -6488,10 +6658,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
6488
6658
|
});
|
|
6489
6659
|
}
|
|
6490
6660
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
6491
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
6661
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
6492
6662
|
return false;
|
|
6493
6663
|
}
|
|
6494
|
-
if (nodes.length < 2
|
|
6664
|
+
if (nodes.length < 2) {
|
|
6495
6665
|
return false;
|
|
6496
6666
|
}
|
|
6497
6667
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -6499,11 +6669,18 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6499
6669
|
return false;
|
|
6500
6670
|
}
|
|
6501
6671
|
const bounds = unionBoxes(nodeBoxes);
|
|
6502
|
-
const
|
|
6503
|
-
const
|
|
6504
|
-
|
|
6672
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
6673
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
6674
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
6675
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
6505
6676
|
return false;
|
|
6506
6677
|
}
|
|
6678
|
+
if (isHorizontal) {
|
|
6679
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
6680
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
6681
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
6682
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
6683
|
+
}
|
|
6507
6684
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
6508
6685
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
6509
6686
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
@@ -6567,7 +6744,17 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6567
6744
|
);
|
|
6568
6745
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
6569
6746
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
6570
|
-
const
|
|
6747
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
6748
|
+
swimlane,
|
|
6749
|
+
nodeBoxes,
|
|
6750
|
+
flowRanks,
|
|
6751
|
+
locks,
|
|
6752
|
+
rankStackGap
|
|
6753
|
+
);
|
|
6754
|
+
const slotWidth = Math.max(
|
|
6755
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
6756
|
+
spreadWidth
|
|
6757
|
+
) + padding * 2;
|
|
6571
6758
|
const laneStep = slotWidth + laneGutter;
|
|
6572
6759
|
const laneContentTop = top + headerHeight + padding;
|
|
6573
6760
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -6581,6 +6768,24 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6581
6768
|
y: laneContentTop
|
|
6582
6769
|
};
|
|
6583
6770
|
if (maxRank === 0) {
|
|
6771
|
+
const distributable = lane.children.filter(
|
|
6772
|
+
(childId) => !locks.has(childId)
|
|
6773
|
+
);
|
|
6774
|
+
if (distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
6775
|
+
moveRankedVerticalLaneChildren(
|
|
6776
|
+
lane.children,
|
|
6777
|
+
nodeBoxes,
|
|
6778
|
+
locks,
|
|
6779
|
+
diagnostics,
|
|
6780
|
+
movedChildIds,
|
|
6781
|
+
flowRanks,
|
|
6782
|
+
rankSpacing,
|
|
6783
|
+
rankStackGap,
|
|
6784
|
+
{ x: target.x, y: laneContentTop },
|
|
6785
|
+
slotWidth - padding * 2
|
|
6786
|
+
);
|
|
6787
|
+
continue;
|
|
6788
|
+
}
|
|
6584
6789
|
moveLaneChildren(
|
|
6585
6790
|
lane.children,
|
|
6586
6791
|
nodeBoxes,
|
|
@@ -6603,10 +6808,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6603
6808
|
flowRanks,
|
|
6604
6809
|
rankSpacing,
|
|
6605
6810
|
rankStackGap,
|
|
6606
|
-
{
|
|
6607
|
-
|
|
6608
|
-
y: laneContentTop
|
|
6609
|
-
}
|
|
6811
|
+
{ x: target.x, y: laneContentTop },
|
|
6812
|
+
slotWidth - padding * 2
|
|
6610
6813
|
);
|
|
6611
6814
|
}
|
|
6612
6815
|
return {
|
|
@@ -6715,31 +6918,99 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
6715
6918
|
}
|
|
6716
6919
|
return maxHeight;
|
|
6717
6920
|
}
|
|
6718
|
-
|
|
6921
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
6922
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
6923
|
+
return items.reduce(
|
|
6924
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
6925
|
+
0
|
|
6926
|
+
);
|
|
6927
|
+
}
|
|
6928
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
6929
|
+
let maxWidth = 0;
|
|
6930
|
+
for (const lane of swimlane.lanes) {
|
|
6931
|
+
for (const stack of rankStacks(
|
|
6932
|
+
lane.children,
|
|
6933
|
+
nodeBoxes,
|
|
6934
|
+
flowRanks
|
|
6935
|
+
).values()) {
|
|
6936
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
6937
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
6938
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
return maxWidth;
|
|
6942
|
+
}
|
|
6943
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth) {
|
|
6719
6944
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6720
|
-
|
|
6945
|
+
const unlocked = [];
|
|
6721
6946
|
for (const item of stack) {
|
|
6722
|
-
|
|
6723
|
-
if (locks.has(childId)) {
|
|
6947
|
+
if (locks.has(item.childId)) {
|
|
6724
6948
|
diagnostics.push({
|
|
6725
6949
|
severity: "warning",
|
|
6726
6950
|
code: "constraints.locked-target-not-moved",
|
|
6727
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
6951
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
6728
6952
|
path: ["swimlanes"],
|
|
6729
|
-
detail: { nodeId: childId }
|
|
6953
|
+
detail: { nodeId: item.childId }
|
|
6730
6954
|
});
|
|
6731
|
-
|
|
6955
|
+
} else {
|
|
6956
|
+
unlocked.push(item);
|
|
6732
6957
|
}
|
|
6958
|
+
}
|
|
6959
|
+
if (unlocked.length === 0) continue;
|
|
6960
|
+
if (unlocked.length === 1) {
|
|
6961
|
+
const { childId, box } = unlocked[0];
|
|
6733
6962
|
const next = {
|
|
6734
6963
|
...box,
|
|
6735
|
-
x:
|
|
6736
|
-
y: target.y + rank * rankSpacing
|
|
6964
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
6965
|
+
y: target.y + rank * rankSpacing
|
|
6737
6966
|
};
|
|
6738
6967
|
if (next.x !== box.x || next.y !== box.y) {
|
|
6739
6968
|
movedChildIds.add(childId);
|
|
6740
6969
|
}
|
|
6741
6970
|
nodeBoxes.set(childId, next);
|
|
6742
|
-
|
|
6971
|
+
} else {
|
|
6972
|
+
const shouldSpread = unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
6973
|
+
if (!shouldSpread) {
|
|
6974
|
+
let yOffset = 0;
|
|
6975
|
+
for (const { childId, box } of unlocked) {
|
|
6976
|
+
const next = {
|
|
6977
|
+
...box,
|
|
6978
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
6979
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
6980
|
+
};
|
|
6981
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
6982
|
+
movedChildIds.add(childId);
|
|
6983
|
+
}
|
|
6984
|
+
nodeBoxes.set(childId, next);
|
|
6985
|
+
yOffset += box.height + rankStackGap;
|
|
6986
|
+
}
|
|
6987
|
+
} else {
|
|
6988
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
6989
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
6990
|
+
for (const { childId, box } of unlocked) {
|
|
6991
|
+
const next = {
|
|
6992
|
+
...box,
|
|
6993
|
+
x: xCursor,
|
|
6994
|
+
y: target.y + rank * rankSpacing
|
|
6995
|
+
};
|
|
6996
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
6997
|
+
movedChildIds.add(childId);
|
|
6998
|
+
}
|
|
6999
|
+
nodeBoxes.set(childId, next);
|
|
7000
|
+
xCursor += box.width + rankStackGap;
|
|
7001
|
+
}
|
|
7002
|
+
diagnostics.push({
|
|
7003
|
+
severity: "info",
|
|
7004
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
7005
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
7006
|
+
path: ["swimlanes"],
|
|
7007
|
+
detail: {
|
|
7008
|
+
rank,
|
|
7009
|
+
childCount: unlocked.length,
|
|
7010
|
+
contentWidth
|
|
7011
|
+
}
|
|
7012
|
+
});
|
|
7013
|
+
}
|
|
6743
7014
|
}
|
|
6744
7015
|
}
|
|
6745
7016
|
}
|
|
@@ -7078,7 +7349,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7078
7349
|
});
|
|
7079
7350
|
continue;
|
|
7080
7351
|
}
|
|
7081
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
7352
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7082
7353
|
const geometry = computeShapeGeometry({
|
|
7083
7354
|
shape: node.shape,
|
|
7084
7355
|
box,
|
|
@@ -7172,7 +7443,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
7172
7443
|
}
|
|
7173
7444
|
}
|
|
7174
7445
|
}
|
|
7175
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
7446
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
7176
7447
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
7177
7448
|
for (const port of node.ports ?? []) {
|
|
7178
7449
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -7195,9 +7466,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7195
7466
|
side,
|
|
7196
7467
|
index,
|
|
7197
7468
|
sorted.length,
|
|
7198
|
-
portShifting
|
|
7199
|
-
diagnostics,
|
|
7200
|
-
node.id
|
|
7469
|
+
portShifting
|
|
7201
7470
|
);
|
|
7202
7471
|
const box = portBox(anchor);
|
|
7203
7472
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -7205,32 +7474,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
7205
7474
|
}
|
|
7206
7475
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
7207
7476
|
}
|
|
7208
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
7477
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
7209
7478
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
7210
7479
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
7211
7480
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
7212
7481
|
const availableSpan = 2 * maxOffset;
|
|
7213
7482
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
7214
|
-
const
|
|
7483
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
7215
7484
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
7216
7485
|
minSpacing
|
|
7217
7486
|
) : requestedSpacing;
|
|
7218
|
-
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
7219
|
-
diagnostics.push({
|
|
7220
|
-
severity: "warning",
|
|
7221
|
-
code: "port_constraint_overlap",
|
|
7222
|
-
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
7223
|
-
path: ["nodes", nodeId, "ports"],
|
|
7224
|
-
detail: {
|
|
7225
|
-
nodeId,
|
|
7226
|
-
side,
|
|
7227
|
-
requestedSpacing,
|
|
7228
|
-
effectiveSpacing: Math.round(effectiveSpacing),
|
|
7229
|
-
portCount: count
|
|
7230
|
-
}
|
|
7231
|
-
});
|
|
7232
|
-
}
|
|
7233
|
-
const spacing = effectiveSpacing;
|
|
7234
7487
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
7235
7488
|
switch (side) {
|
|
7236
7489
|
case "left":
|