@crazyhappyone/auto-graph 0.2.8 → 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/index.d.cts
CHANGED
|
@@ -436,7 +436,8 @@ interface ConstraintSolverInput {
|
|
|
436
436
|
minSiblingGap?: number;
|
|
437
437
|
distributeContainedChildren?: boolean | "spread";
|
|
438
438
|
/** When "spread" or true, distribute children inside non-contract
|
|
439
|
-
* swimlane lane content boxes (Issue #60).
|
|
439
|
+
* swimlane lane content boxes (Issue #60). Opt-in: no redistribution
|
|
440
|
+
* occurs unless explicitly set. */
|
|
440
441
|
distributeSwimlaneChildren?: boolean | "spread";
|
|
441
442
|
boxes: ReadonlyMap<string, Box>;
|
|
442
443
|
/** Swimlanes for lane-aware child distribution (Issue #60). */
|
|
@@ -806,7 +807,7 @@ interface SolveDiagramOptions {
|
|
|
806
807
|
minSiblingGap?: number;
|
|
807
808
|
distributeContainedChildren?: boolean | "spread";
|
|
808
809
|
/** When "spread", distribute children within non-contract swimlane
|
|
809
|
-
* lanes (Issue #60).
|
|
810
|
+
* lanes (Issue #60). Opt-in: no redistribution occurs unless explicitly set. */
|
|
810
811
|
distributeSwimlaneChildren?: boolean | "spread";
|
|
811
812
|
pageBounds?: {
|
|
812
813
|
width: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -436,7 +436,8 @@ interface ConstraintSolverInput {
|
|
|
436
436
|
minSiblingGap?: number;
|
|
437
437
|
distributeContainedChildren?: boolean | "spread";
|
|
438
438
|
/** When "spread" or true, distribute children inside non-contract
|
|
439
|
-
* swimlane lane content boxes (Issue #60).
|
|
439
|
+
* swimlane lane content boxes (Issue #60). Opt-in: no redistribution
|
|
440
|
+
* occurs unless explicitly set. */
|
|
440
441
|
distributeSwimlaneChildren?: boolean | "spread";
|
|
441
442
|
boxes: ReadonlyMap<string, Box>;
|
|
442
443
|
/** Swimlanes for lane-aware child distribution (Issue #60). */
|
|
@@ -806,7 +807,7 @@ interface SolveDiagramOptions {
|
|
|
806
807
|
minSiblingGap?: number;
|
|
807
808
|
distributeContainedChildren?: boolean | "spread";
|
|
808
809
|
/** When "spread", distribute children within non-contract swimlane
|
|
809
|
-
* lanes (Issue #60).
|
|
810
|
+
* lanes (Issue #60). Opt-in: no redistribution occurs unless explicitly set. */
|
|
810
811
|
distributeSwimlaneChildren?: boolean | "spread";
|
|
811
812
|
pageBounds?: {
|
|
812
813
|
width: number;
|
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
|
}
|
|
@@ -4801,8 +4821,59 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4801
4821
|
turnPenalty,
|
|
4802
4822
|
segmentPenalty
|
|
4803
4823
|
);
|
|
4804
|
-
if (path
|
|
4805
|
-
|
|
4824
|
+
if (path !== null) {
|
|
4825
|
+
const simplified = simplifyRoute(path);
|
|
4826
|
+
const filteredSet = new Set(filtered);
|
|
4827
|
+
if (useCorridor && obstacles.some((o) => !filteredSet.has(o))) {
|
|
4828
|
+
let crossesExcluded = false;
|
|
4829
|
+
for (let i = 0; i < simplified.length - 1; i++) {
|
|
4830
|
+
const a = simplified[i];
|
|
4831
|
+
const b = simplified[i + 1];
|
|
4832
|
+
for (const obs of obstacles) {
|
|
4833
|
+
if (filteredSet.has(obs)) continue;
|
|
4834
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) {
|
|
4835
|
+
crossesExcluded = true;
|
|
4836
|
+
break;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
if (crossesExcluded) break;
|
|
4840
|
+
}
|
|
4841
|
+
if (!crossesExcluded) return simplified;
|
|
4842
|
+
} else {
|
|
4843
|
+
return simplified;
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
if (!useCorridor) return null;
|
|
4847
|
+
const xsFull = collectXs(source, target, obstacles, margin);
|
|
4848
|
+
const ysFull = collectYs(source, target, obstacles, margin);
|
|
4849
|
+
if (xsFull.length * ysFull.length > maxNodes) {
|
|
4850
|
+
diagnostics?.push({
|
|
4851
|
+
severity: "warning",
|
|
4852
|
+
code: "routing.astar.grid_overflow",
|
|
4853
|
+
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4854
|
+
detail: { xsCount: xsFull.length, ysCount: ysFull.length, maxNodes }
|
|
4855
|
+
});
|
|
4856
|
+
return null;
|
|
4857
|
+
}
|
|
4858
|
+
const { nodes: nodesFull, nodeIndex: idxFull } = buildGraph(xsFull, ysFull);
|
|
4859
|
+
connectHorizontalEdges(
|
|
4860
|
+
nodesFull,
|
|
4861
|
+
ysFull,
|
|
4862
|
+
obstacles,
|
|
4863
|
+
endpointObstacles,
|
|
4864
|
+
margin
|
|
4865
|
+
);
|
|
4866
|
+
connectVerticalEdges(nodesFull, xsFull, obstacles, endpointObstacles, margin);
|
|
4867
|
+
const pathFull = aStarSearch(
|
|
4868
|
+
nodesFull,
|
|
4869
|
+
idxFull,
|
|
4870
|
+
source,
|
|
4871
|
+
target,
|
|
4872
|
+
turnPenalty,
|
|
4873
|
+
segmentPenalty
|
|
4874
|
+
);
|
|
4875
|
+
if (pathFull === null) return null;
|
|
4876
|
+
return simplifyRoute(pathFull);
|
|
4806
4877
|
}
|
|
4807
4878
|
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4808
4879
|
const cx1 = Math.min(source.x, target.x) - margin;
|
|
@@ -5029,7 +5100,7 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
5029
5100
|
const turnPenalty = options.turnPenalty ?? 50;
|
|
5030
5101
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
5031
5102
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
5032
|
-
const maxCorners = options.maxCorners ??
|
|
5103
|
+
const maxCorners = options.maxCorners ?? 600;
|
|
5033
5104
|
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
5034
5105
|
if (vertices.length > maxCorners) {
|
|
5035
5106
|
diagnostics?.push({
|
|
@@ -5398,17 +5469,36 @@ function routeEdge(input) {
|
|
|
5398
5469
|
input.source.center,
|
|
5399
5470
|
targetAnchor
|
|
5400
5471
|
);
|
|
5401
|
-
const
|
|
5472
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
5473
|
+
const corridorObstacles = filterObstaclesByCorridor(
|
|
5402
5474
|
source,
|
|
5403
5475
|
target,
|
|
5404
|
-
|
|
5476
|
+
allObstacles,
|
|
5477
|
+
[],
|
|
5478
|
+
// endpointObstacles passed separately via options
|
|
5479
|
+
32
|
|
5480
|
+
);
|
|
5481
|
+
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
5482
|
+
let cornerPath = findCornerGraphPath(
|
|
5483
|
+
source,
|
|
5484
|
+
target,
|
|
5485
|
+
cornerObstacles,
|
|
5405
5486
|
{ endpointObstacles, margin: 2 },
|
|
5406
5487
|
diagnostics
|
|
5407
5488
|
);
|
|
5489
|
+
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
5490
|
+
cornerPath = findCornerGraphPath(
|
|
5491
|
+
source,
|
|
5492
|
+
target,
|
|
5493
|
+
allObstacles,
|
|
5494
|
+
{ endpointObstacles, margin: 2 },
|
|
5495
|
+
diagnostics
|
|
5496
|
+
);
|
|
5497
|
+
}
|
|
5408
5498
|
const path = cornerPath ?? findObstacleFreePath(
|
|
5409
5499
|
source,
|
|
5410
5500
|
target,
|
|
5411
|
-
|
|
5501
|
+
allObstacles,
|
|
5412
5502
|
{ endpointObstacles, margin: 0 },
|
|
5413
5503
|
diagnostics
|
|
5414
5504
|
);
|
|
@@ -5429,6 +5519,66 @@ function routeEdge(input) {
|
|
|
5429
5519
|
checkBacktracking(finalized, source, target, diagnostics);
|
|
5430
5520
|
return { points: finalized, diagnostics };
|
|
5431
5521
|
}
|
|
5522
|
+
if (cornerPath !== null) {
|
|
5523
|
+
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
5524
|
+
source,
|
|
5525
|
+
target,
|
|
5526
|
+
allObstacles,
|
|
5527
|
+
{ endpointObstacles, margin: 2 },
|
|
5528
|
+
diagnostics
|
|
5529
|
+
) : null;
|
|
5530
|
+
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
5531
|
+
const fullFinalized = finalizeRoute(
|
|
5532
|
+
fullCornerPath,
|
|
5533
|
+
softObstacles,
|
|
5534
|
+
hardObstacles,
|
|
5535
|
+
diagnostics,
|
|
5536
|
+
softObstacleIndex,
|
|
5537
|
+
hardObstacleIndex
|
|
5538
|
+
);
|
|
5539
|
+
if (!routeIntersectsObstacles(
|
|
5540
|
+
fullFinalized,
|
|
5541
|
+
softObstacles,
|
|
5542
|
+
softObstacleIndex
|
|
5543
|
+
) && !routeIntersectsObstacles(
|
|
5544
|
+
fullFinalized,
|
|
5545
|
+
hardObstacles,
|
|
5546
|
+
hardObstacleIndex
|
|
5547
|
+
)) {
|
|
5548
|
+
checkBacktracking(fullFinalized, source, target, diagnostics);
|
|
5549
|
+
return { points: fullFinalized, diagnostics };
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
const gridPath = findObstacleFreePath(
|
|
5553
|
+
source,
|
|
5554
|
+
target,
|
|
5555
|
+
allObstacles,
|
|
5556
|
+
{ endpointObstacles, margin: 0 },
|
|
5557
|
+
diagnostics
|
|
5558
|
+
);
|
|
5559
|
+
if (gridPath !== null && gridPath.length >= 2) {
|
|
5560
|
+
const gridFinalized = finalizeRoute(
|
|
5561
|
+
gridPath,
|
|
5562
|
+
softObstacles,
|
|
5563
|
+
hardObstacles,
|
|
5564
|
+
diagnostics,
|
|
5565
|
+
softObstacleIndex,
|
|
5566
|
+
hardObstacleIndex
|
|
5567
|
+
);
|
|
5568
|
+
if (!routeIntersectsObstacles(
|
|
5569
|
+
gridFinalized,
|
|
5570
|
+
softObstacles,
|
|
5571
|
+
softObstacleIndex
|
|
5572
|
+
) && !routeIntersectsObstacles(
|
|
5573
|
+
gridFinalized,
|
|
5574
|
+
hardObstacles,
|
|
5575
|
+
hardObstacleIndex
|
|
5576
|
+
)) {
|
|
5577
|
+
checkBacktracking(gridFinalized, source, target, diagnostics);
|
|
5578
|
+
return { points: gridFinalized, diagnostics };
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5432
5582
|
}
|
|
5433
5583
|
}
|
|
5434
5584
|
}
|
|
@@ -6529,7 +6679,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6529
6679
|
edges: styledEdges
|
|
6530
6680
|
});
|
|
6531
6681
|
diagnostics.push(...layout2.diagnostics);
|
|
6532
|
-
const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6682
|
+
const initialNodeBoxes = initialLayoutMode === "positions" || diagram.direction !== "LR" && diagram.direction !== "RL" ? layout2.boxes : wrapVerticalStackIfNeeded(
|
|
6533
6683
|
layout2.boxes,
|
|
6534
6684
|
styledNodes,
|
|
6535
6685
|
styledEdges,
|
|
@@ -6537,7 +6687,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6537
6687
|
options,
|
|
6538
6688
|
diagnostics
|
|
6539
6689
|
);
|
|
6540
|
-
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6690
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && (options.maxRowDepth !== void 0 || options.targetAspectRatio !== void 0)) {
|
|
6691
|
+
const diagCountBefore = diagnostics.length;
|
|
6541
6692
|
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
6542
6693
|
initialNodeBoxes,
|
|
6543
6694
|
styledNodes,
|
|
@@ -6548,6 +6699,20 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6548
6699
|
for (const [id, box] of rewrapped) {
|
|
6549
6700
|
initialNodeBoxes.set(id, box);
|
|
6550
6701
|
}
|
|
6702
|
+
if (diagnostics.length > diagCountBefore) {
|
|
6703
|
+
for (const node of styledNodes) {
|
|
6704
|
+
if (node.position !== void 0 && rewrapped.has(node.id)) {
|
|
6705
|
+
const rwBox = rewrapped.get(node.id);
|
|
6706
|
+
const idx = styledNodes.indexOf(node);
|
|
6707
|
+
if (idx !== -1) {
|
|
6708
|
+
styledNodes[idx] = {
|
|
6709
|
+
...node,
|
|
6710
|
+
position: { x: rwBox.x, y: rwBox.y }
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6551
6716
|
}
|
|
6552
6717
|
if (useRecursive && "groupBoxes" in layout2) {
|
|
6553
6718
|
const recursiveLayout = layout2;
|
|
@@ -6561,7 +6726,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6561
6726
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
6562
6727
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
6563
6728
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
6564
|
-
distributeSwimlaneChildren: options.distributeSwimlaneChildren
|
|
6729
|
+
...options.distributeSwimlaneChildren !== void 0 ? { distributeSwimlaneChildren: options.distributeSwimlaneChildren } : {},
|
|
6565
6730
|
swimlanes: styledSwimlanes,
|
|
6566
6731
|
boxes: initialNodeBoxes,
|
|
6567
6732
|
nodes: styledNodes,
|
|
@@ -7280,9 +7445,14 @@ function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnosti
|
|
|
7280
7445
|
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
7281
7446
|
return new Map(boxes);
|
|
7282
7447
|
}
|
|
7283
|
-
|
|
7284
|
-
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
7285
|
-
|
|
7448
|
+
let maxRowDepth = options.maxRowDepth;
|
|
7449
|
+
if (maxRowDepth === void 0 || maxRowDepth <= 0 || nodes.length <= maxRowDepth) {
|
|
7450
|
+
if (maxRowDepth === void 0 && options.targetAspectRatio !== void 0) {
|
|
7451
|
+
maxRowDepth = Math.ceil(Math.sqrt(nodes.length));
|
|
7452
|
+
if (nodes.length <= maxRowDepth) return new Map(boxes);
|
|
7453
|
+
} else {
|
|
7454
|
+
return new Map(boxes);
|
|
7455
|
+
}
|
|
7286
7456
|
}
|
|
7287
7457
|
const ordered = [...nodes].sort((a, b) => {
|
|
7288
7458
|
const ba = boxes.get(a.id);
|
|
@@ -7343,10 +7513,10 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
7343
7513
|
});
|
|
7344
7514
|
}
|
|
7345
7515
|
function isStackRunaway(boxes, nodes, direction, options) {
|
|
7346
|
-
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
7516
|
+
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0 && options.targetAspectRatio === void 0 && options.maxRowDepth === void 0) {
|
|
7347
7517
|
return false;
|
|
7348
7518
|
}
|
|
7349
|
-
if (nodes.length < 2
|
|
7519
|
+
if (nodes.length < 2) {
|
|
7350
7520
|
return false;
|
|
7351
7521
|
}
|
|
7352
7522
|
const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
|
|
@@ -7354,11 +7524,18 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
7354
7524
|
return false;
|
|
7355
7525
|
}
|
|
7356
7526
|
const bounds = unionBoxes(nodeBoxes);
|
|
7357
|
-
const
|
|
7358
|
-
const
|
|
7359
|
-
|
|
7527
|
+
const isHorizontal = direction === "TB" || direction === "BT";
|
|
7528
|
+
const aspectRatio = isHorizontal ? bounds.height <= 0 ? Number.POSITIVE_INFINITY : bounds.width / bounds.height : bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
|
|
7529
|
+
const preferred = isHorizontal ? options.targetAspectRatio ?? options.preferredAspectRatio ?? 3 : options.preferredAspectRatio ?? 3;
|
|
7530
|
+
if ((options.preferredAspectRatio !== void 0 || options.targetAspectRatio !== void 0) && aspectRatio < preferred) {
|
|
7360
7531
|
return false;
|
|
7361
7532
|
}
|
|
7533
|
+
if (isHorizontal) {
|
|
7534
|
+
const yCenters = nodeBoxes.map((box) => box.y + box.height / 2);
|
|
7535
|
+
const ySpread = Math.max(...yCenters) - Math.min(...yCenters);
|
|
7536
|
+
const maxHeight = Math.max(...nodeBoxes.map((box) => box.height));
|
|
7537
|
+
return ySpread <= Math.max(maxHeight, options.overlapSpacing ?? 40);
|
|
7538
|
+
}
|
|
7362
7539
|
const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
|
|
7363
7540
|
const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
|
|
7364
7541
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
@@ -7422,7 +7599,17 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7422
7599
|
);
|
|
7423
7600
|
const rankSpacing = Math.max(96, maxRankStackHeight + padding);
|
|
7424
7601
|
const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
|
|
7425
|
-
const
|
|
7602
|
+
const spreadWidth = maxCrossAxisSpreadWidth(
|
|
7603
|
+
swimlane,
|
|
7604
|
+
nodeBoxes,
|
|
7605
|
+
flowRanks,
|
|
7606
|
+
locks,
|
|
7607
|
+
rankStackGap
|
|
7608
|
+
);
|
|
7609
|
+
const slotWidth = Math.max(
|
|
7610
|
+
Math.max(...populatedBounds.map((box) => box.width)),
|
|
7611
|
+
spreadWidth
|
|
7612
|
+
) + padding * 2;
|
|
7426
7613
|
const laneStep = slotWidth + laneGutter;
|
|
7427
7614
|
const laneContentTop = top + headerHeight + padding;
|
|
7428
7615
|
for (let index = 0; index < swimlane.lanes.length; index += 1) {
|
|
@@ -7436,6 +7623,24 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7436
7623
|
y: laneContentTop
|
|
7437
7624
|
};
|
|
7438
7625
|
if (maxRank === 0) {
|
|
7626
|
+
const distributable = lane.children.filter(
|
|
7627
|
+
(childId) => !locks.has(childId)
|
|
7628
|
+
);
|
|
7629
|
+
if (distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7630
|
+
moveRankedVerticalLaneChildren(
|
|
7631
|
+
lane.children,
|
|
7632
|
+
nodeBoxes,
|
|
7633
|
+
locks,
|
|
7634
|
+
diagnostics,
|
|
7635
|
+
movedChildIds,
|
|
7636
|
+
flowRanks,
|
|
7637
|
+
rankSpacing,
|
|
7638
|
+
rankStackGap,
|
|
7639
|
+
{ x: target.x, y: laneContentTop },
|
|
7640
|
+
slotWidth - padding * 2
|
|
7641
|
+
);
|
|
7642
|
+
continue;
|
|
7643
|
+
}
|
|
7439
7644
|
moveLaneChildren(
|
|
7440
7645
|
lane.children,
|
|
7441
7646
|
nodeBoxes,
|
|
@@ -7458,10 +7663,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7458
7663
|
flowRanks,
|
|
7459
7664
|
rankSpacing,
|
|
7460
7665
|
rankStackGap,
|
|
7461
|
-
{
|
|
7462
|
-
|
|
7463
|
-
y: laneContentTop
|
|
7464
|
-
}
|
|
7666
|
+
{ x: target.x, y: laneContentTop },
|
|
7667
|
+
slotWidth - padding * 2
|
|
7465
7668
|
);
|
|
7466
7669
|
}
|
|
7467
7670
|
return {
|
|
@@ -7570,31 +7773,99 @@ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
|
|
|
7570
7773
|
}
|
|
7571
7774
|
return maxHeight;
|
|
7572
7775
|
}
|
|
7573
|
-
|
|
7776
|
+
var CROSS_AXIS_SPREAD_THRESHOLD = 3;
|
|
7777
|
+
function crossAxisSpreadWidth(items, gap) {
|
|
7778
|
+
return items.reduce(
|
|
7779
|
+
(sum, item, index) => sum + item.box.width + (index === 0 ? 0 : gap),
|
|
7780
|
+
0
|
|
7781
|
+
);
|
|
7782
|
+
}
|
|
7783
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
7784
|
+
let maxWidth = 0;
|
|
7785
|
+
for (const lane of swimlane.lanes) {
|
|
7786
|
+
for (const stack of rankStacks(
|
|
7787
|
+
lane.children,
|
|
7788
|
+
nodeBoxes,
|
|
7789
|
+
flowRanks
|
|
7790
|
+
).values()) {
|
|
7791
|
+
const unlocked = stack.filter((item) => !locks.has(item.childId));
|
|
7792
|
+
if (unlocked.length < CROSS_AXIS_SPREAD_THRESHOLD) continue;
|
|
7793
|
+
maxWidth = Math.max(maxWidth, crossAxisSpreadWidth(unlocked, gap));
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
return maxWidth;
|
|
7797
|
+
}
|
|
7798
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth) {
|
|
7574
7799
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
7575
|
-
|
|
7800
|
+
const unlocked = [];
|
|
7576
7801
|
for (const item of stack) {
|
|
7577
|
-
|
|
7578
|
-
if (locks.has(childId)) {
|
|
7802
|
+
if (locks.has(item.childId)) {
|
|
7579
7803
|
diagnostics.push({
|
|
7580
7804
|
severity: "warning",
|
|
7581
7805
|
code: "constraints.locked-target-not-moved",
|
|
7582
|
-
message: `Locked child ${childId} was not moved into contract swimlane slot.`,
|
|
7806
|
+
message: `Locked child ${item.childId} was not moved into contract swimlane slot.`,
|
|
7583
7807
|
path: ["swimlanes"],
|
|
7584
|
-
detail: { nodeId: childId }
|
|
7808
|
+
detail: { nodeId: item.childId }
|
|
7585
7809
|
});
|
|
7586
|
-
|
|
7810
|
+
} else {
|
|
7811
|
+
unlocked.push(item);
|
|
7587
7812
|
}
|
|
7813
|
+
}
|
|
7814
|
+
if (unlocked.length === 0) continue;
|
|
7815
|
+
if (unlocked.length === 1) {
|
|
7816
|
+
const { childId, box } = unlocked[0];
|
|
7588
7817
|
const next = {
|
|
7589
7818
|
...box,
|
|
7590
|
-
x:
|
|
7591
|
-
y: target.y + rank * rankSpacing
|
|
7819
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7820
|
+
y: target.y + rank * rankSpacing
|
|
7592
7821
|
};
|
|
7593
7822
|
if (next.x !== box.x || next.y !== box.y) {
|
|
7594
7823
|
movedChildIds.add(childId);
|
|
7595
7824
|
}
|
|
7596
7825
|
nodeBoxes.set(childId, next);
|
|
7597
|
-
|
|
7826
|
+
} else {
|
|
7827
|
+
const shouldSpread = unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7828
|
+
if (!shouldSpread) {
|
|
7829
|
+
let yOffset = 0;
|
|
7830
|
+
for (const { childId, box } of unlocked) {
|
|
7831
|
+
const next = {
|
|
7832
|
+
...box,
|
|
7833
|
+
x: target.x + (contentWidth - box.width) / 2,
|
|
7834
|
+
y: target.y + rank * rankSpacing + yOffset
|
|
7835
|
+
};
|
|
7836
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7837
|
+
movedChildIds.add(childId);
|
|
7838
|
+
}
|
|
7839
|
+
nodeBoxes.set(childId, next);
|
|
7840
|
+
yOffset += box.height + rankStackGap;
|
|
7841
|
+
}
|
|
7842
|
+
} else {
|
|
7843
|
+
const packedWidth = crossAxisSpreadWidth(unlocked, rankStackGap);
|
|
7844
|
+
let xCursor = target.x + Math.max(0, (contentWidth - packedWidth) / 2);
|
|
7845
|
+
for (const { childId, box } of unlocked) {
|
|
7846
|
+
const next = {
|
|
7847
|
+
...box,
|
|
7848
|
+
x: xCursor,
|
|
7849
|
+
y: target.y + rank * rankSpacing
|
|
7850
|
+
};
|
|
7851
|
+
if (next.x !== box.x || next.y !== box.y) {
|
|
7852
|
+
movedChildIds.add(childId);
|
|
7853
|
+
}
|
|
7854
|
+
nodeBoxes.set(childId, next);
|
|
7855
|
+
xCursor += box.width + rankStackGap;
|
|
7856
|
+
}
|
|
7857
|
+
diagnostics.push({
|
|
7858
|
+
severity: "info",
|
|
7859
|
+
code: "swimlane_contract.cross_axis_distributed",
|
|
7860
|
+
message: `Spread ${unlocked.length} same-rank children horizontally in contract lane (rank ${rank}).`,
|
|
7861
|
+
path: ["swimlanes"],
|
|
7862
|
+
detail: {
|
|
7863
|
+
rank,
|
|
7864
|
+
childCount: unlocked.length,
|
|
7865
|
+
contentWidth
|
|
7866
|
+
}
|
|
7867
|
+
});
|
|
7868
|
+
}
|
|
7598
7869
|
}
|
|
7599
7870
|
}
|
|
7600
7871
|
}
|
|
@@ -7933,7 +8204,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7933
8204
|
});
|
|
7934
8205
|
continue;
|
|
7935
8206
|
}
|
|
7936
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting
|
|
8207
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7937
8208
|
const geometry = computeShapeGeometry({
|
|
7938
8209
|
shape: node.shape,
|
|
7939
8210
|
box,
|
|
@@ -8027,7 +8298,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
8027
8298
|
}
|
|
8028
8299
|
}
|
|
8029
8300
|
}
|
|
8030
|
-
function coordinatePorts(node, nodeBox, portShifting
|
|
8301
|
+
function coordinatePorts(node, nodeBox, portShifting) {
|
|
8031
8302
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
8032
8303
|
for (const port of node.ports ?? []) {
|
|
8033
8304
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -8050,9 +8321,7 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8050
8321
|
side,
|
|
8051
8322
|
index,
|
|
8052
8323
|
sorted.length,
|
|
8053
|
-
portShifting
|
|
8054
|
-
diagnostics,
|
|
8055
|
-
node.id
|
|
8324
|
+
portShifting
|
|
8056
8325
|
);
|
|
8057
8326
|
const box = portBox(anchor);
|
|
8058
8327
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -8060,32 +8329,16 @@ function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
|
8060
8329
|
}
|
|
8061
8330
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
8062
8331
|
}
|
|
8063
|
-
function portAnchor(nodeBox, side, index, count, portShifting
|
|
8332
|
+
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
8064
8333
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
8065
8334
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
8066
8335
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
8067
8336
|
const availableSpan = 2 * maxOffset;
|
|
8068
8337
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
8069
|
-
const
|
|
8338
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
8070
8339
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
8071
8340
|
minSpacing
|
|
8072
8341
|
) : 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
8342
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
8090
8343
|
switch (side) {
|
|
8091
8344
|
case "left":
|