@crazyhappyone/auto-graph 0.1.4 → 0.2.0
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 +446 -48
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +446 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +447 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +447 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2001,7 +2001,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
2001
2001
|
const lock = locks.get(childId);
|
|
2002
2002
|
if (lock?.source === "fixed-position") {
|
|
2003
2003
|
unlocked.push({ id: childId, box });
|
|
2004
|
-
locks.delete(childId);
|
|
2005
2004
|
continue;
|
|
2006
2005
|
}
|
|
2007
2006
|
diagnostics.push({
|
|
@@ -3378,6 +3377,213 @@ function isValidDimension(value) {
|
|
|
3378
3377
|
return Number.isFinite(value) && value >= 0;
|
|
3379
3378
|
}
|
|
3380
3379
|
|
|
3380
|
+
// src/routing/astar.ts
|
|
3381
|
+
function findObstacleFreePath(source, target, obstacles, options = {}) {
|
|
3382
|
+
const margin = options.margin ?? 0;
|
|
3383
|
+
const turnPenalty = options.turnPenalty ?? 50;
|
|
3384
|
+
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
3385
|
+
const endpointObstacles = options.endpointObstacles ?? [];
|
|
3386
|
+
const maxNodes = options.maxNodes ?? 4e3;
|
|
3387
|
+
const xs = collectXs(source, target, obstacles, margin);
|
|
3388
|
+
const ys = collectYs(source, target, obstacles, margin);
|
|
3389
|
+
if (xs.length * ys.length > maxNodes) {
|
|
3390
|
+
return null;
|
|
3391
|
+
}
|
|
3392
|
+
const { nodes, nodeIndex } = buildGraph(xs, ys);
|
|
3393
|
+
connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin);
|
|
3394
|
+
connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin);
|
|
3395
|
+
const path = aStarSearch(
|
|
3396
|
+
nodes,
|
|
3397
|
+
nodeIndex,
|
|
3398
|
+
source,
|
|
3399
|
+
target,
|
|
3400
|
+
turnPenalty,
|
|
3401
|
+
segmentPenalty
|
|
3402
|
+
);
|
|
3403
|
+
if (path === null) return null;
|
|
3404
|
+
return simplifyRoute(path);
|
|
3405
|
+
}
|
|
3406
|
+
function collectXs(source, target, obstacles, margin) {
|
|
3407
|
+
const set = /* @__PURE__ */ new Set();
|
|
3408
|
+
set.add(source.x);
|
|
3409
|
+
set.add(target.x);
|
|
3410
|
+
for (const obs of obstacles) {
|
|
3411
|
+
set.add(obs.x - margin - 2);
|
|
3412
|
+
set.add(obs.x + obs.width + margin + 2);
|
|
3413
|
+
}
|
|
3414
|
+
return [...set].sort((a, b) => a - b);
|
|
3415
|
+
}
|
|
3416
|
+
function collectYs(source, target, obstacles, margin) {
|
|
3417
|
+
const set = /* @__PURE__ */ new Set();
|
|
3418
|
+
set.add(source.y);
|
|
3419
|
+
set.add(target.y);
|
|
3420
|
+
for (const obs of obstacles) {
|
|
3421
|
+
set.add(obs.y - margin - 2);
|
|
3422
|
+
set.add(obs.y + obs.height + margin + 2);
|
|
3423
|
+
}
|
|
3424
|
+
return [...set].sort((a, b) => a - b);
|
|
3425
|
+
}
|
|
3426
|
+
function buildGraph(xs, ys) {
|
|
3427
|
+
const nodes = [];
|
|
3428
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
3429
|
+
for (let xi = 0; xi < xs.length; xi++) {
|
|
3430
|
+
for (let yi = 0; yi < ys.length; yi++) {
|
|
3431
|
+
const x = xs[xi];
|
|
3432
|
+
const y = ys[yi];
|
|
3433
|
+
const id = nodes.length;
|
|
3434
|
+
nodes.push({ x, y, id, neighbors: /* @__PURE__ */ new Map() });
|
|
3435
|
+
nodeIndex.set(`${x},${y}`, id);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
return { nodes, nodeIndex };
|
|
3439
|
+
}
|
|
3440
|
+
function connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin) {
|
|
3441
|
+
for (const y of ys) {
|
|
3442
|
+
const row = nodes.filter((n) => n.y === y).sort((a, b) => a.x - b.x);
|
|
3443
|
+
for (let i = 0; i < row.length - 1; i++) {
|
|
3444
|
+
const a = row[i];
|
|
3445
|
+
const b = row[i + 1];
|
|
3446
|
+
const dx = b.x - a.x;
|
|
3447
|
+
if (dx <= 0) continue;
|
|
3448
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
3449
|
+
continue;
|
|
3450
|
+
}
|
|
3451
|
+
a.neighbors.set(b.id, dx);
|
|
3452
|
+
b.neighbors.set(a.id, dx);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin) {
|
|
3457
|
+
for (const x of xs) {
|
|
3458
|
+
const col = nodes.filter((n) => n.x === x).sort((a, b) => a.y - b.y);
|
|
3459
|
+
for (let i = 0; i < col.length - 1; i++) {
|
|
3460
|
+
const a = col[i];
|
|
3461
|
+
const b = col[i + 1];
|
|
3462
|
+
const dy = b.y - a.y;
|
|
3463
|
+
if (dy <= 0) continue;
|
|
3464
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
a.neighbors.set(b.id, dy);
|
|
3468
|
+
b.neighbors.set(a.id, dy);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
function segmentCrossesAny(a, b, obstacles, endpointObstacles, margin) {
|
|
3473
|
+
for (const obs of obstacles) {
|
|
3474
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) return true;
|
|
3475
|
+
}
|
|
3476
|
+
for (const ep of endpointObstacles) {
|
|
3477
|
+
if (segmentCrossesBoxStrict(a, b, ep, margin)) return true;
|
|
3478
|
+
}
|
|
3479
|
+
return false;
|
|
3480
|
+
}
|
|
3481
|
+
function segmentCrossesBoxStrict(start, end, box, margin) {
|
|
3482
|
+
const left = box.x - margin;
|
|
3483
|
+
const right = box.x + box.width + margin;
|
|
3484
|
+
const top = box.y - margin;
|
|
3485
|
+
const bottom = box.y + box.height + margin;
|
|
3486
|
+
if (pointInsideStrict(start, left, right, top, bottom)) return true;
|
|
3487
|
+
if (pointInsideStrict(end, left, right, top, bottom)) return true;
|
|
3488
|
+
if (start.x === end.x) {
|
|
3489
|
+
return start.x >= left && start.x <= right && rangesOverlap(start.y, end.y, top, bottom);
|
|
3490
|
+
}
|
|
3491
|
+
if (start.y === end.y) {
|
|
3492
|
+
return start.y >= top && start.y <= bottom && rangesOverlap(start.x, end.x, left, right);
|
|
3493
|
+
}
|
|
3494
|
+
return segmentEdgeIntersect(start, end, left, top, right, top) || segmentEdgeIntersect(start, end, right, top, right, bottom) || segmentEdgeIntersect(start, end, right, bottom, left, bottom) || segmentEdgeIntersect(start, end, left, bottom, left, top);
|
|
3495
|
+
}
|
|
3496
|
+
function pointInsideStrict(p, left, right, top, bottom) {
|
|
3497
|
+
return p.x > left && p.x < right && p.y > top && p.y < bottom;
|
|
3498
|
+
}
|
|
3499
|
+
function rangesOverlap(a, b, min, max) {
|
|
3500
|
+
const low = Math.min(a, b);
|
|
3501
|
+
const high = Math.max(a, b);
|
|
3502
|
+
return high > min && low < max;
|
|
3503
|
+
}
|
|
3504
|
+
function segmentEdgeIntersect(start, end, x1, y1, x2, y2) {
|
|
3505
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
3506
|
+
if (denominator === 0) return false;
|
|
3507
|
+
const t = ((start.x - x1) * (y2 - y1) - (start.y - y1) * (x2 - x1)) / denominator;
|
|
3508
|
+
const u = ((start.x - x1) * (end.y - start.y) - (start.y - y1) * (end.x - start.x)) / denominator;
|
|
3509
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
3510
|
+
}
|
|
3511
|
+
function aStarSearch(nodes, nodeIndex, source, target, turnPenalty, segmentPenalty) {
|
|
3512
|
+
const startId = nodeIndex.get(`${source.x},${source.y}`);
|
|
3513
|
+
const goalId = nodeIndex.get(`${target.x},${target.y}`);
|
|
3514
|
+
if (startId === void 0 || goalId === void 0) return null;
|
|
3515
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
3516
|
+
gScore.set(startId, 0);
|
|
3517
|
+
const cameFrom = /* @__PURE__ */ new Map();
|
|
3518
|
+
const cameFromDir = /* @__PURE__ */ new Map();
|
|
3519
|
+
const openSet = [];
|
|
3520
|
+
openSet.push({
|
|
3521
|
+
id: startId,
|
|
3522
|
+
f: manhattan(source, target)
|
|
3523
|
+
});
|
|
3524
|
+
while (openSet.length > 0) {
|
|
3525
|
+
let bestIdx = 0;
|
|
3526
|
+
for (let i = 1; i < openSet.length; i++) {
|
|
3527
|
+
if (openSet[i].f < openSet[bestIdx].f) {
|
|
3528
|
+
bestIdx = i;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
const current = openSet.splice(bestIdx, 1)[0];
|
|
3532
|
+
if (current.id === goalId) {
|
|
3533
|
+
return reconstructPath(nodes, cameFrom, goalId);
|
|
3534
|
+
}
|
|
3535
|
+
const node = nodes[current.id];
|
|
3536
|
+
const currentG = gScore.get(current.id);
|
|
3537
|
+
const prevDir = cameFromDir.get(current.id);
|
|
3538
|
+
for (const [neighborId, edgeCost] of node.neighbors) {
|
|
3539
|
+
const neighbor = nodes[neighborId];
|
|
3540
|
+
const tentativeG = currentG + edgeCost * segmentPenalty;
|
|
3541
|
+
const newDir = neighbor.y === node.y ? "h" : "v";
|
|
3542
|
+
const turnCost = prevDir !== void 0 && prevDir !== newDir ? turnPenalty : 0;
|
|
3543
|
+
const totalG = tentativeG + turnCost;
|
|
3544
|
+
const existingG = gScore.get(neighborId);
|
|
3545
|
+
if (existingG === void 0 || totalG < existingG) {
|
|
3546
|
+
gScore.set(neighborId, totalG);
|
|
3547
|
+
cameFrom.set(neighborId, current.id);
|
|
3548
|
+
cameFromDir.set(neighborId, newDir);
|
|
3549
|
+
const f = totalG + manhattan(neighbor, target);
|
|
3550
|
+
openSet.push({ id: neighborId, f });
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3556
|
+
function manhattan(a, b) {
|
|
3557
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
3558
|
+
}
|
|
3559
|
+
function reconstructPath(nodes, cameFrom, goalId) {
|
|
3560
|
+
const path = [];
|
|
3561
|
+
let current = goalId;
|
|
3562
|
+
while (current !== void 0) {
|
|
3563
|
+
const node = nodes[current];
|
|
3564
|
+
path.unshift({ x: node.x, y: node.y });
|
|
3565
|
+
current = cameFrom.get(current);
|
|
3566
|
+
}
|
|
3567
|
+
return path;
|
|
3568
|
+
}
|
|
3569
|
+
function simplifyRoute(points) {
|
|
3570
|
+
if (points.length <= 2) return [...points];
|
|
3571
|
+
const result = [points[0]];
|
|
3572
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
3573
|
+
const prev = result[result.length - 1];
|
|
3574
|
+
const curr = points[i];
|
|
3575
|
+
const next = points[i + 1];
|
|
3576
|
+
if (!areCollinear(prev, curr, next)) {
|
|
3577
|
+
result.push(curr);
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
result.push(points[points.length - 1]);
|
|
3581
|
+
return result;
|
|
3582
|
+
}
|
|
3583
|
+
function areCollinear(a, b, c) {
|
|
3584
|
+
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3381
3587
|
// src/routing/routes.ts
|
|
3382
3588
|
function routeEdge(input) {
|
|
3383
3589
|
const diagnostics = [];
|
|
@@ -3423,6 +3629,43 @@ function routeEdge(input) {
|
|
|
3423
3629
|
}
|
|
3424
3630
|
return { points, diagnostics };
|
|
3425
3631
|
}
|
|
3632
|
+
if ((input.kind ?? "orthogonal") === "obstacle-avoiding") {
|
|
3633
|
+
const endpointObstacles = endpointObstaclesForAutoAnchors(input);
|
|
3634
|
+
for (const { sourceAnchor, targetAnchor } of routeAnchorPairs(
|
|
3635
|
+
input,
|
|
3636
|
+
defaultAnchors
|
|
3637
|
+
)) {
|
|
3638
|
+
const source = getEdgePort(
|
|
3639
|
+
input.source,
|
|
3640
|
+
input.target.center,
|
|
3641
|
+
sourceAnchor
|
|
3642
|
+
);
|
|
3643
|
+
const target = getEdgePort(
|
|
3644
|
+
input.target,
|
|
3645
|
+
input.source.center,
|
|
3646
|
+
targetAnchor
|
|
3647
|
+
);
|
|
3648
|
+
const path = findObstacleFreePath(
|
|
3649
|
+
source,
|
|
3650
|
+
target,
|
|
3651
|
+
[...softObstacles, ...hardObstacles],
|
|
3652
|
+
{
|
|
3653
|
+
endpointObstacles
|
|
3654
|
+
}
|
|
3655
|
+
);
|
|
3656
|
+
if (path !== null && path.length >= 2) {
|
|
3657
|
+
const finalized = finalizeRoute(
|
|
3658
|
+
path,
|
|
3659
|
+
softObstacles,
|
|
3660
|
+
hardObstacles,
|
|
3661
|
+
diagnostics
|
|
3662
|
+
);
|
|
3663
|
+
if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
|
|
3664
|
+
return { points: finalized, diagnostics };
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3426
3669
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3427
3670
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3428
3671
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -3625,7 +3868,7 @@ function routeEdge(input) {
|
|
|
3625
3868
|
};
|
|
3626
3869
|
}
|
|
3627
3870
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
3628
|
-
const simplified =
|
|
3871
|
+
const simplified = simplifyRoute2(points);
|
|
3629
3872
|
if (simplified.length >= 3) {
|
|
3630
3873
|
return simplified;
|
|
3631
3874
|
}
|
|
@@ -3913,7 +4156,7 @@ function squaredDistance2(a, b) {
|
|
|
3913
4156
|
const dy = a.y - b.y;
|
|
3914
4157
|
return dx * dx + dy * dy;
|
|
3915
4158
|
}
|
|
3916
|
-
function
|
|
4159
|
+
function simplifyRoute2(points) {
|
|
3917
4160
|
const withoutDuplicates = [];
|
|
3918
4161
|
for (const point2 of points) {
|
|
3919
4162
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -3925,7 +4168,7 @@ function simplifyRoute(points) {
|
|
|
3925
4168
|
for (const point2 of withoutDuplicates) {
|
|
3926
4169
|
const previous = simplified.at(-1);
|
|
3927
4170
|
const beforePrevious = simplified.at(-2);
|
|
3928
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4171
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
3929
4172
|
simplified[simplified.length - 1] = { ...point2 };
|
|
3930
4173
|
} else {
|
|
3931
4174
|
simplified.push({ ...point2 });
|
|
@@ -4140,17 +4383,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4140
4383
|
return true;
|
|
4141
4384
|
}
|
|
4142
4385
|
if (start.x === end.x) {
|
|
4143
|
-
return start.x > left && start.x < right &&
|
|
4386
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4144
4387
|
}
|
|
4145
4388
|
if (start.y === end.y) {
|
|
4146
|
-
return start.y > top && start.y < bottom &&
|
|
4389
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4147
4390
|
}
|
|
4148
4391
|
return segmentIntersectsBoxEdge(start, end, left, top, right, top) || segmentIntersectsBoxEdge(start, end, right, top, right, bottom) || segmentIntersectsBoxEdge(start, end, right, bottom, left, bottom) || segmentIntersectsBoxEdge(start, end, left, bottom, left, top);
|
|
4149
4392
|
}
|
|
4150
4393
|
function pointInsideBox(point2, box) {
|
|
4151
4394
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4152
4395
|
}
|
|
4153
|
-
function
|
|
4396
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4154
4397
|
const low = Math.min(a, b);
|
|
4155
4398
|
const high = Math.max(a, b);
|
|
4156
4399
|
return high > min && low < max;
|
|
@@ -4174,7 +4417,7 @@ function segmentBox(a, b) {
|
|
|
4174
4417
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4175
4418
|
};
|
|
4176
4419
|
}
|
|
4177
|
-
function
|
|
4420
|
+
function areCollinear2(a, b, c) {
|
|
4178
4421
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4179
4422
|
}
|
|
4180
4423
|
|
|
@@ -4254,6 +4497,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4254
4497
|
options,
|
|
4255
4498
|
diagnostics
|
|
4256
4499
|
);
|
|
4500
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4257
4501
|
const constrained = applyLayoutConstraints({
|
|
4258
4502
|
direction: diagram.direction,
|
|
4259
4503
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4317,6 +4561,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4317
4561
|
swimlanes: coordinatedSwimlanes,
|
|
4318
4562
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4319
4563
|
});
|
|
4564
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
4565
|
+
styledEdges,
|
|
4566
|
+
nodeGeometryById,
|
|
4567
|
+
options.textMeasurer
|
|
4568
|
+
);
|
|
4320
4569
|
const layoutBoxes = [
|
|
4321
4570
|
...coordinatedNodes.map((node) => node.box),
|
|
4322
4571
|
...coordinatedNodes.flatMap(
|
|
@@ -4397,7 +4646,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4397
4646
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4398
4647
|
const routingTextObstacles = [
|
|
4399
4648
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4400
|
-
...frameTextAnnotation.filter(isPreRouteTextObstacle)
|
|
4649
|
+
...frameTextAnnotation.filter(isPreRouteTextObstacle),
|
|
4650
|
+
// Dry-run edge-label estimates so edges route around
|
|
4651
|
+
// each other's label areas (Issue #41).
|
|
4652
|
+
...edgeLabelEstimates
|
|
4401
4653
|
];
|
|
4402
4654
|
const margin = options.obstacleMargin ?? 0;
|
|
4403
4655
|
const softObstacles = [
|
|
@@ -4430,7 +4682,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4430
4682
|
hardObstacles,
|
|
4431
4683
|
diagram.direction,
|
|
4432
4684
|
options,
|
|
4433
|
-
diagnostics
|
|
4685
|
+
diagnostics,
|
|
4686
|
+
coordinatedGroups
|
|
4434
4687
|
);
|
|
4435
4688
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4436
4689
|
coordinatedEdges,
|
|
@@ -4543,22 +4796,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
4543
4796
|
y: layout2.box.y + offsetY,
|
|
4544
4797
|
width: layout2.box.width,
|
|
4545
4798
|
height: layout2.box.height
|
|
4546
|
-
}
|
|
4547
|
-
contentBox: {
|
|
4548
|
-
x: layout2.contentBox.x + offsetX,
|
|
4549
|
-
y: layout2.contentBox.y + offsetY,
|
|
4550
|
-
width: layout2.contentBox.width,
|
|
4551
|
-
height: layout2.contentBox.height
|
|
4552
|
-
},
|
|
4553
|
-
lines: layout2.lines.map((line) => ({
|
|
4554
|
-
...line,
|
|
4555
|
-
box: {
|
|
4556
|
-
x: line.box.x + offsetX,
|
|
4557
|
-
y: line.box.y + offsetY,
|
|
4558
|
-
width: line.box.width,
|
|
4559
|
-
height: line.box.height
|
|
4560
|
-
}
|
|
4561
|
-
}))
|
|
4799
|
+
}
|
|
4562
4800
|
};
|
|
4563
4801
|
}
|
|
4564
4802
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -5505,6 +5743,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5505
5743
|
});
|
|
5506
5744
|
continue;
|
|
5507
5745
|
}
|
|
5746
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
5508
5747
|
const geometry = computeShapeGeometry({
|
|
5509
5748
|
shape: node.shape,
|
|
5510
5749
|
box,
|
|
@@ -5514,7 +5753,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5514
5753
|
id: node.id,
|
|
5515
5754
|
...node.label === void 0 ? {} : { label: node.label },
|
|
5516
5755
|
...node.style === void 0 ? {} : { style: node.style },
|
|
5517
|
-
...
|
|
5756
|
+
...ports === void 0 ? {} : { ports },
|
|
5518
5757
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
5519
5758
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
5520
5759
|
shape: node.shape,
|
|
@@ -5526,6 +5765,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5526
5765
|
}
|
|
5527
5766
|
return coordinated;
|
|
5528
5767
|
}
|
|
5768
|
+
var PORT_BOX_SIZE = 10;
|
|
5769
|
+
var MIN_PORT_EDGE_GAP = 12;
|
|
5770
|
+
function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
5771
|
+
const shiftingEnabled = options.portShifting?.enabled ?? true;
|
|
5772
|
+
if (!shiftingEnabled) return;
|
|
5773
|
+
const requestedSpacing = options.portShifting?.spacing ?? 24;
|
|
5774
|
+
const minSpacing = Math.max(
|
|
5775
|
+
requestedSpacing,
|
|
5776
|
+
PORT_BOX_SIZE + MIN_PORT_EDGE_GAP
|
|
5777
|
+
);
|
|
5778
|
+
for (const node of nodes) {
|
|
5779
|
+
if (node.ports === void 0 || node.ports.length === 0) continue;
|
|
5780
|
+
const box = boxes.get(node.id);
|
|
5781
|
+
if (box === void 0) continue;
|
|
5782
|
+
let heightExpansion = 0;
|
|
5783
|
+
let widthExpansion = 0;
|
|
5784
|
+
const portsBySide = /* @__PURE__ */ new Map();
|
|
5785
|
+
for (const port of node.ports) {
|
|
5786
|
+
const list = portsBySide.get(port.side) ?? [];
|
|
5787
|
+
list.push(port);
|
|
5788
|
+
portsBySide.set(port.side, list);
|
|
5789
|
+
}
|
|
5790
|
+
for (const [side, ports] of portsBySide) {
|
|
5791
|
+
const count = (ports ?? []).length;
|
|
5792
|
+
if (count <= 1) continue;
|
|
5793
|
+
const isVertical = side === "left" || side === "right";
|
|
5794
|
+
const availableSpan = isVertical ? box.height : box.width;
|
|
5795
|
+
const requiredSpan = (count - 1) * minSpacing + PORT_BOX_SIZE;
|
|
5796
|
+
if (requiredSpan > availableSpan) {
|
|
5797
|
+
const expansion = requiredSpan - availableSpan;
|
|
5798
|
+
if (isVertical) {
|
|
5799
|
+
heightExpansion = Math.max(heightExpansion, expansion);
|
|
5800
|
+
} else {
|
|
5801
|
+
widthExpansion = Math.max(widthExpansion, expansion);
|
|
5802
|
+
}
|
|
5803
|
+
diagnostics.push({
|
|
5804
|
+
severity: "info",
|
|
5805
|
+
code: "port_capacity_overflow",
|
|
5806
|
+
message: `Expanded node ${node.id} ${isVertical ? "height" : "width"} by ${Math.ceil(expansion)} px to fit ${count} port(s) on ${side} side.`,
|
|
5807
|
+
path: ["nodes", node.id, "ports"],
|
|
5808
|
+
detail: {
|
|
5809
|
+
nodeId: node.id,
|
|
5810
|
+
side,
|
|
5811
|
+
portCount: count,
|
|
5812
|
+
expansion: Math.ceil(expansion)
|
|
5813
|
+
}
|
|
5814
|
+
});
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5817
|
+
if (heightExpansion > 0) {
|
|
5818
|
+
box.y -= heightExpansion / 2;
|
|
5819
|
+
box.height += heightExpansion;
|
|
5820
|
+
}
|
|
5821
|
+
if (widthExpansion > 0) {
|
|
5822
|
+
box.x -= widthExpansion / 2;
|
|
5823
|
+
box.width += widthExpansion;
|
|
5824
|
+
}
|
|
5825
|
+
if ((heightExpansion > 0 || widthExpansion > 0) && node.labelLayout !== void 0) {
|
|
5826
|
+
const layout2 = node.labelLayout;
|
|
5827
|
+
const newOffsetX = Math.max(0, (box.width - layout2.box.width) / 2);
|
|
5828
|
+
const newOffsetY = Math.max(0, (box.height - layout2.box.height) / 2);
|
|
5829
|
+
node.labelLayout = {
|
|
5830
|
+
...layout2,
|
|
5831
|
+
box: {
|
|
5832
|
+
...layout2.box,
|
|
5833
|
+
x: newOffsetX,
|
|
5834
|
+
y: newOffsetY
|
|
5835
|
+
}
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5529
5840
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
5530
5841
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
5531
5842
|
for (const port of node.ports ?? []) {
|
|
@@ -5562,7 +5873,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5562
5873
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
5563
5874
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
5564
5875
|
const availableSpan = 2 * maxOffset;
|
|
5565
|
-
const
|
|
5876
|
+
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
5877
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
5878
|
+
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
5879
|
+
minSpacing
|
|
5880
|
+
) : requestedSpacing;
|
|
5566
5881
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
5567
5882
|
switch (side) {
|
|
5568
5883
|
case "left":
|
|
@@ -5588,7 +5903,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5588
5903
|
}
|
|
5589
5904
|
}
|
|
5590
5905
|
function portBox(anchor) {
|
|
5591
|
-
const size =
|
|
5906
|
+
const size = PORT_BOX_SIZE;
|
|
5592
5907
|
return {
|
|
5593
5908
|
x: anchor.x - size / 2,
|
|
5594
5909
|
y: anchor.y - size / 2,
|
|
@@ -6177,7 +6492,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6177
6492
|
}
|
|
6178
6493
|
};
|
|
6179
6494
|
}
|
|
6180
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
|
|
6495
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
6181
6496
|
const coordinated = [];
|
|
6182
6497
|
const coordinatedNodeById = new Map(
|
|
6183
6498
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6201,8 +6516,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6201
6516
|
}
|
|
6202
6517
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6203
6518
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6204
|
-
const
|
|
6205
|
-
const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
|
|
6519
|
+
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
6206
6520
|
const route = routeEdge({
|
|
6207
6521
|
kind: options.routeKind ?? "orthogonal",
|
|
6208
6522
|
direction,
|
|
@@ -6215,6 +6529,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6215
6529
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6216
6530
|
),
|
|
6217
6531
|
...softObstacles,
|
|
6532
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6218
6533
|
...routeTextObstacles
|
|
6219
6534
|
],
|
|
6220
6535
|
hardObstacles,
|
|
@@ -6233,15 +6548,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6233
6548
|
}
|
|
6234
6549
|
return coordinated;
|
|
6235
6550
|
}
|
|
6236
|
-
function
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6551
|
+
function isEdgeConnectedTextAnnotation(edge, annotation) {
|
|
6552
|
+
switch (annotation.surfaceKind) {
|
|
6553
|
+
case "edge-label":
|
|
6554
|
+
return annotation.ownerId === edge.id;
|
|
6555
|
+
case "node-label":
|
|
6556
|
+
case "compartment-row":
|
|
6557
|
+
return annotation.ownerId === edge.source.nodeId || annotation.ownerId === edge.target.nodeId;
|
|
6558
|
+
case "port-label":
|
|
6559
|
+
return edge.source.portId !== void 0 && annotation.ownerId === `${edge.source.nodeId}.${edge.source.portId}` || edge.target.portId !== void 0 && annotation.ownerId === `${edge.target.nodeId}.${edge.target.portId}`;
|
|
6560
|
+
case "group-label":
|
|
6561
|
+
case "swimlane-label":
|
|
6562
|
+
case "frame-title":
|
|
6563
|
+
return false;
|
|
6240
6564
|
}
|
|
6241
|
-
|
|
6242
|
-
|
|
6565
|
+
}
|
|
6566
|
+
function ancestorGroupIds(groups, nodeId) {
|
|
6567
|
+
const direct = /* @__PURE__ */ new Set();
|
|
6568
|
+
for (const group of groups) {
|
|
6569
|
+
if (group.nodeIds.includes(nodeId)) {
|
|
6570
|
+
direct.add(group.id);
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
let previousSize = -1;
|
|
6574
|
+
const ancestors = new Set(direct);
|
|
6575
|
+
while (ancestors.size !== previousSize) {
|
|
6576
|
+
previousSize = ancestors.size;
|
|
6577
|
+
for (const group of groups) {
|
|
6578
|
+
for (const candidate of ancestors) {
|
|
6579
|
+
if (group.groupIds.includes(candidate)) {
|
|
6580
|
+
ancestors.add(group.id);
|
|
6581
|
+
break;
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6243
6585
|
}
|
|
6244
|
-
return
|
|
6586
|
+
return ancestors;
|
|
6587
|
+
}
|
|
6588
|
+
function groupObstaclesForEdge(edge, groups, margin) {
|
|
6589
|
+
const sourceAncestors = ancestorGroupIds(groups, edge.source.nodeId);
|
|
6590
|
+
const targetAncestors = ancestorGroupIds(groups, edge.target.nodeId);
|
|
6591
|
+
return groups.filter((group) => {
|
|
6592
|
+
if (sourceAncestors.has(group.id) || targetAncestors.has(group.id)) {
|
|
6593
|
+
return false;
|
|
6594
|
+
}
|
|
6595
|
+
return true;
|
|
6596
|
+
}).map((group) => margin === 0 ? group.box : expandBox(group.box, margin));
|
|
6245
6597
|
}
|
|
6246
6598
|
function coordinateBaseTextAnnotations(input) {
|
|
6247
6599
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -6429,6 +6781,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
6429
6781
|
}
|
|
6430
6782
|
return annotations;
|
|
6431
6783
|
}
|
|
6784
|
+
function estimateEdgeLabelAnnotations(edges, nodes, textMeasurer) {
|
|
6785
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
6786
|
+
const annotations = [];
|
|
6787
|
+
for (const edge of edges) {
|
|
6788
|
+
if (edge.label?.text === void 0) {
|
|
6789
|
+
continue;
|
|
6790
|
+
}
|
|
6791
|
+
const sourceGeom = nodes.get(edge.source.nodeId);
|
|
6792
|
+
const targetGeom = nodes.get(edge.target.nodeId);
|
|
6793
|
+
if (sourceGeom === void 0 || targetGeom === void 0) {
|
|
6794
|
+
continue;
|
|
6795
|
+
}
|
|
6796
|
+
const layout2 = fitLabel(
|
|
6797
|
+
edge.label.text,
|
|
6798
|
+
{
|
|
6799
|
+
font: typographyTextStyle(edge.label, {
|
|
6800
|
+
fontFamily: "Arial",
|
|
6801
|
+
fontSize: 12,
|
|
6802
|
+
lineHeight: 14
|
|
6803
|
+
}),
|
|
6804
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
6805
|
+
minSize: { width: 0, height: 0 },
|
|
6806
|
+
maxWidth: 200
|
|
6807
|
+
},
|
|
6808
|
+
measurer
|
|
6809
|
+
);
|
|
6810
|
+
const cx = (sourceGeom.center.x + targetGeom.center.x) / 2;
|
|
6811
|
+
const cy = (sourceGeom.center.y + targetGeom.center.y) / 2;
|
|
6812
|
+
const box = {
|
|
6813
|
+
x: cx - layout2.box.width / 2,
|
|
6814
|
+
y: cy - layout2.box.height / 2,
|
|
6815
|
+
width: layout2.box.width,
|
|
6816
|
+
height: layout2.box.height
|
|
6817
|
+
};
|
|
6818
|
+
annotations.push({
|
|
6819
|
+
text: layout2.text,
|
|
6820
|
+
ownerId: edge.id,
|
|
6821
|
+
surfaceKind: "edge-label",
|
|
6822
|
+
box,
|
|
6823
|
+
anchor: { x: cx, y: cy },
|
|
6824
|
+
paddings: layout2.padding,
|
|
6825
|
+
lines: layout2.lines,
|
|
6826
|
+
fontFamily: normalizeOutputFontFamily(layout2.font),
|
|
6827
|
+
fontSize: layout2.font.fontSize,
|
|
6828
|
+
textBackend: layout2.textBackend
|
|
6829
|
+
});
|
|
6830
|
+
}
|
|
6831
|
+
return annotations;
|
|
6832
|
+
}
|
|
6432
6833
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
6433
6834
|
const layout2 = fitLabel(
|
|
6434
6835
|
frame.titleTab,
|
|
@@ -6549,9 +6950,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6549
6950
|
const diagnostics = [];
|
|
6550
6951
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
6551
6952
|
for (const edge of edges) {
|
|
6552
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
6553
6953
|
for (const annotation of relevantAnnotations) {
|
|
6554
|
-
if (
|
|
6954
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
6555
6955
|
continue;
|
|
6556
6956
|
}
|
|
6557
6957
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -6575,9 +6975,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6575
6975
|
return diagnostics;
|
|
6576
6976
|
}
|
|
6577
6977
|
function isPreRouteTextObstacle(annotation) {
|
|
6578
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
6579
|
-
return false;
|
|
6580
|
-
}
|
|
6581
6978
|
return isRouteClearanceText(annotation);
|
|
6582
6979
|
}
|
|
6583
6980
|
function isRouteClearanceText(annotation) {
|
|
@@ -6588,8 +6985,9 @@ function isRouteClearanceText(annotation) {
|
|
|
6588
6985
|
case "frame-title":
|
|
6589
6986
|
return true;
|
|
6590
6987
|
case "node-label":
|
|
6591
|
-
case "group-label":
|
|
6592
6988
|
case "compartment-row":
|
|
6989
|
+
return true;
|
|
6990
|
+
case "group-label":
|
|
6593
6991
|
return textExtendsOutsideAnchor(annotation);
|
|
6594
6992
|
}
|
|
6595
6993
|
}
|
|
@@ -6622,17 +7020,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
6622
7020
|
return true;
|
|
6623
7021
|
}
|
|
6624
7022
|
if (start.x === end.x) {
|
|
6625
|
-
return start.x > left && start.x < right &&
|
|
7023
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
6626
7024
|
}
|
|
6627
7025
|
if (start.y === end.y) {
|
|
6628
|
-
return start.y > top && start.y < bottom &&
|
|
7026
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
6629
7027
|
}
|
|
6630
7028
|
return segmentIntersectsBoxEdge2(start, end, left, top, right, top) || segmentIntersectsBoxEdge2(start, end, right, top, right, bottom) || segmentIntersectsBoxEdge2(start, end, right, bottom, left, bottom) || segmentIntersectsBoxEdge2(start, end, left, bottom, left, top);
|
|
6631
7029
|
}
|
|
6632
7030
|
function pointInsideBox2(point2, box) {
|
|
6633
7031
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
6634
7032
|
}
|
|
6635
|
-
function
|
|
7033
|
+
function rangesOverlap3(a, b, min, max) {
|
|
6636
7034
|
const low = Math.min(a, b);
|
|
6637
7035
|
const high = Math.max(a, b);
|
|
6638
7036
|
return high > min && low < max;
|