@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.cjs
CHANGED
|
@@ -2004,7 +2004,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
2004
2004
|
const lock = locks.get(childId);
|
|
2005
2005
|
if (lock?.source === "fixed-position") {
|
|
2006
2006
|
unlocked.push({ id: childId, box });
|
|
2007
|
-
locks.delete(childId);
|
|
2008
2007
|
continue;
|
|
2009
2008
|
}
|
|
2010
2009
|
diagnostics.push({
|
|
@@ -3381,6 +3380,213 @@ function isValidDimension(value) {
|
|
|
3381
3380
|
return Number.isFinite(value) && value >= 0;
|
|
3382
3381
|
}
|
|
3383
3382
|
|
|
3383
|
+
// src/routing/astar.ts
|
|
3384
|
+
function findObstacleFreePath(source, target, obstacles, options = {}) {
|
|
3385
|
+
const margin = options.margin ?? 0;
|
|
3386
|
+
const turnPenalty = options.turnPenalty ?? 50;
|
|
3387
|
+
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
3388
|
+
const endpointObstacles = options.endpointObstacles ?? [];
|
|
3389
|
+
const maxNodes = options.maxNodes ?? 4e3;
|
|
3390
|
+
const xs = collectXs(source, target, obstacles, margin);
|
|
3391
|
+
const ys = collectYs(source, target, obstacles, margin);
|
|
3392
|
+
if (xs.length * ys.length > maxNodes) {
|
|
3393
|
+
return null;
|
|
3394
|
+
}
|
|
3395
|
+
const { nodes, nodeIndex } = buildGraph(xs, ys);
|
|
3396
|
+
connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin);
|
|
3397
|
+
connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin);
|
|
3398
|
+
const path = aStarSearch(
|
|
3399
|
+
nodes,
|
|
3400
|
+
nodeIndex,
|
|
3401
|
+
source,
|
|
3402
|
+
target,
|
|
3403
|
+
turnPenalty,
|
|
3404
|
+
segmentPenalty
|
|
3405
|
+
);
|
|
3406
|
+
if (path === null) return null;
|
|
3407
|
+
return simplifyRoute(path);
|
|
3408
|
+
}
|
|
3409
|
+
function collectXs(source, target, obstacles, margin) {
|
|
3410
|
+
const set = /* @__PURE__ */ new Set();
|
|
3411
|
+
set.add(source.x);
|
|
3412
|
+
set.add(target.x);
|
|
3413
|
+
for (const obs of obstacles) {
|
|
3414
|
+
set.add(obs.x - margin - 2);
|
|
3415
|
+
set.add(obs.x + obs.width + margin + 2);
|
|
3416
|
+
}
|
|
3417
|
+
return [...set].sort((a, b) => a - b);
|
|
3418
|
+
}
|
|
3419
|
+
function collectYs(source, target, obstacles, margin) {
|
|
3420
|
+
const set = /* @__PURE__ */ new Set();
|
|
3421
|
+
set.add(source.y);
|
|
3422
|
+
set.add(target.y);
|
|
3423
|
+
for (const obs of obstacles) {
|
|
3424
|
+
set.add(obs.y - margin - 2);
|
|
3425
|
+
set.add(obs.y + obs.height + margin + 2);
|
|
3426
|
+
}
|
|
3427
|
+
return [...set].sort((a, b) => a - b);
|
|
3428
|
+
}
|
|
3429
|
+
function buildGraph(xs, ys) {
|
|
3430
|
+
const nodes = [];
|
|
3431
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
3432
|
+
for (let xi = 0; xi < xs.length; xi++) {
|
|
3433
|
+
for (let yi = 0; yi < ys.length; yi++) {
|
|
3434
|
+
const x = xs[xi];
|
|
3435
|
+
const y = ys[yi];
|
|
3436
|
+
const id = nodes.length;
|
|
3437
|
+
nodes.push({ x, y, id, neighbors: /* @__PURE__ */ new Map() });
|
|
3438
|
+
nodeIndex.set(`${x},${y}`, id);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return { nodes, nodeIndex };
|
|
3442
|
+
}
|
|
3443
|
+
function connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin) {
|
|
3444
|
+
for (const y of ys) {
|
|
3445
|
+
const row = nodes.filter((n) => n.y === y).sort((a, b) => a.x - b.x);
|
|
3446
|
+
for (let i = 0; i < row.length - 1; i++) {
|
|
3447
|
+
const a = row[i];
|
|
3448
|
+
const b = row[i + 1];
|
|
3449
|
+
const dx = b.x - a.x;
|
|
3450
|
+
if (dx <= 0) continue;
|
|
3451
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
3452
|
+
continue;
|
|
3453
|
+
}
|
|
3454
|
+
a.neighbors.set(b.id, dx);
|
|
3455
|
+
b.neighbors.set(a.id, dx);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
function connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin) {
|
|
3460
|
+
for (const x of xs) {
|
|
3461
|
+
const col = nodes.filter((n) => n.x === x).sort((a, b) => a.y - b.y);
|
|
3462
|
+
for (let i = 0; i < col.length - 1; i++) {
|
|
3463
|
+
const a = col[i];
|
|
3464
|
+
const b = col[i + 1];
|
|
3465
|
+
const dy = b.y - a.y;
|
|
3466
|
+
if (dy <= 0) continue;
|
|
3467
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
3468
|
+
continue;
|
|
3469
|
+
}
|
|
3470
|
+
a.neighbors.set(b.id, dy);
|
|
3471
|
+
b.neighbors.set(a.id, dy);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function segmentCrossesAny(a, b, obstacles, endpointObstacles, margin) {
|
|
3476
|
+
for (const obs of obstacles) {
|
|
3477
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) return true;
|
|
3478
|
+
}
|
|
3479
|
+
for (const ep of endpointObstacles) {
|
|
3480
|
+
if (segmentCrossesBoxStrict(a, b, ep, margin)) return true;
|
|
3481
|
+
}
|
|
3482
|
+
return false;
|
|
3483
|
+
}
|
|
3484
|
+
function segmentCrossesBoxStrict(start, end, box, margin) {
|
|
3485
|
+
const left = box.x - margin;
|
|
3486
|
+
const right = box.x + box.width + margin;
|
|
3487
|
+
const top = box.y - margin;
|
|
3488
|
+
const bottom = box.y + box.height + margin;
|
|
3489
|
+
if (pointInsideStrict(start, left, right, top, bottom)) return true;
|
|
3490
|
+
if (pointInsideStrict(end, left, right, top, bottom)) return true;
|
|
3491
|
+
if (start.x === end.x) {
|
|
3492
|
+
return start.x >= left && start.x <= right && rangesOverlap(start.y, end.y, top, bottom);
|
|
3493
|
+
}
|
|
3494
|
+
if (start.y === end.y) {
|
|
3495
|
+
return start.y >= top && start.y <= bottom && rangesOverlap(start.x, end.x, left, right);
|
|
3496
|
+
}
|
|
3497
|
+
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);
|
|
3498
|
+
}
|
|
3499
|
+
function pointInsideStrict(p, left, right, top, bottom) {
|
|
3500
|
+
return p.x > left && p.x < right && p.y > top && p.y < bottom;
|
|
3501
|
+
}
|
|
3502
|
+
function rangesOverlap(a, b, min, max) {
|
|
3503
|
+
const low = Math.min(a, b);
|
|
3504
|
+
const high = Math.max(a, b);
|
|
3505
|
+
return high > min && low < max;
|
|
3506
|
+
}
|
|
3507
|
+
function segmentEdgeIntersect(start, end, x1, y1, x2, y2) {
|
|
3508
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
3509
|
+
if (denominator === 0) return false;
|
|
3510
|
+
const t = ((start.x - x1) * (y2 - y1) - (start.y - y1) * (x2 - x1)) / denominator;
|
|
3511
|
+
const u = ((start.x - x1) * (end.y - start.y) - (start.y - y1) * (end.x - start.x)) / denominator;
|
|
3512
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
3513
|
+
}
|
|
3514
|
+
function aStarSearch(nodes, nodeIndex, source, target, turnPenalty, segmentPenalty) {
|
|
3515
|
+
const startId = nodeIndex.get(`${source.x},${source.y}`);
|
|
3516
|
+
const goalId = nodeIndex.get(`${target.x},${target.y}`);
|
|
3517
|
+
if (startId === void 0 || goalId === void 0) return null;
|
|
3518
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
3519
|
+
gScore.set(startId, 0);
|
|
3520
|
+
const cameFrom = /* @__PURE__ */ new Map();
|
|
3521
|
+
const cameFromDir = /* @__PURE__ */ new Map();
|
|
3522
|
+
const openSet = [];
|
|
3523
|
+
openSet.push({
|
|
3524
|
+
id: startId,
|
|
3525
|
+
f: manhattan(source, target)
|
|
3526
|
+
});
|
|
3527
|
+
while (openSet.length > 0) {
|
|
3528
|
+
let bestIdx = 0;
|
|
3529
|
+
for (let i = 1; i < openSet.length; i++) {
|
|
3530
|
+
if (openSet[i].f < openSet[bestIdx].f) {
|
|
3531
|
+
bestIdx = i;
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
const current = openSet.splice(bestIdx, 1)[0];
|
|
3535
|
+
if (current.id === goalId) {
|
|
3536
|
+
return reconstructPath(nodes, cameFrom, goalId);
|
|
3537
|
+
}
|
|
3538
|
+
const node = nodes[current.id];
|
|
3539
|
+
const currentG = gScore.get(current.id);
|
|
3540
|
+
const prevDir = cameFromDir.get(current.id);
|
|
3541
|
+
for (const [neighborId, edgeCost] of node.neighbors) {
|
|
3542
|
+
const neighbor = nodes[neighborId];
|
|
3543
|
+
const tentativeG = currentG + edgeCost * segmentPenalty;
|
|
3544
|
+
const newDir = neighbor.y === node.y ? "h" : "v";
|
|
3545
|
+
const turnCost = prevDir !== void 0 && prevDir !== newDir ? turnPenalty : 0;
|
|
3546
|
+
const totalG = tentativeG + turnCost;
|
|
3547
|
+
const existingG = gScore.get(neighborId);
|
|
3548
|
+
if (existingG === void 0 || totalG < existingG) {
|
|
3549
|
+
gScore.set(neighborId, totalG);
|
|
3550
|
+
cameFrom.set(neighborId, current.id);
|
|
3551
|
+
cameFromDir.set(neighborId, newDir);
|
|
3552
|
+
const f = totalG + manhattan(neighbor, target);
|
|
3553
|
+
openSet.push({ id: neighborId, f });
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
return null;
|
|
3558
|
+
}
|
|
3559
|
+
function manhattan(a, b) {
|
|
3560
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
3561
|
+
}
|
|
3562
|
+
function reconstructPath(nodes, cameFrom, goalId) {
|
|
3563
|
+
const path = [];
|
|
3564
|
+
let current = goalId;
|
|
3565
|
+
while (current !== void 0) {
|
|
3566
|
+
const node = nodes[current];
|
|
3567
|
+
path.unshift({ x: node.x, y: node.y });
|
|
3568
|
+
current = cameFrom.get(current);
|
|
3569
|
+
}
|
|
3570
|
+
return path;
|
|
3571
|
+
}
|
|
3572
|
+
function simplifyRoute(points) {
|
|
3573
|
+
if (points.length <= 2) return [...points];
|
|
3574
|
+
const result = [points[0]];
|
|
3575
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
3576
|
+
const prev = result[result.length - 1];
|
|
3577
|
+
const curr = points[i];
|
|
3578
|
+
const next = points[i + 1];
|
|
3579
|
+
if (!areCollinear(prev, curr, next)) {
|
|
3580
|
+
result.push(curr);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
result.push(points[points.length - 1]);
|
|
3584
|
+
return result;
|
|
3585
|
+
}
|
|
3586
|
+
function areCollinear(a, b, c) {
|
|
3587
|
+
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3384
3590
|
// src/routing/routes.ts
|
|
3385
3591
|
function routeEdge(input) {
|
|
3386
3592
|
const diagnostics = [];
|
|
@@ -3426,6 +3632,43 @@ function routeEdge(input) {
|
|
|
3426
3632
|
}
|
|
3427
3633
|
return { points, diagnostics };
|
|
3428
3634
|
}
|
|
3635
|
+
if ((input.kind ?? "orthogonal") === "obstacle-avoiding") {
|
|
3636
|
+
const endpointObstacles = endpointObstaclesForAutoAnchors(input);
|
|
3637
|
+
for (const { sourceAnchor, targetAnchor } of routeAnchorPairs(
|
|
3638
|
+
input,
|
|
3639
|
+
defaultAnchors
|
|
3640
|
+
)) {
|
|
3641
|
+
const source = getEdgePort(
|
|
3642
|
+
input.source,
|
|
3643
|
+
input.target.center,
|
|
3644
|
+
sourceAnchor
|
|
3645
|
+
);
|
|
3646
|
+
const target = getEdgePort(
|
|
3647
|
+
input.target,
|
|
3648
|
+
input.source.center,
|
|
3649
|
+
targetAnchor
|
|
3650
|
+
);
|
|
3651
|
+
const path = findObstacleFreePath(
|
|
3652
|
+
source,
|
|
3653
|
+
target,
|
|
3654
|
+
[...softObstacles, ...hardObstacles],
|
|
3655
|
+
{
|
|
3656
|
+
endpointObstacles
|
|
3657
|
+
}
|
|
3658
|
+
);
|
|
3659
|
+
if (path !== null && path.length >= 2) {
|
|
3660
|
+
const finalized = finalizeRoute(
|
|
3661
|
+
path,
|
|
3662
|
+
softObstacles,
|
|
3663
|
+
hardObstacles,
|
|
3664
|
+
diagnostics
|
|
3665
|
+
);
|
|
3666
|
+
if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
|
|
3667
|
+
return { points: finalized, diagnostics };
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3429
3672
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3430
3673
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3431
3674
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -3628,7 +3871,7 @@ function routeEdge(input) {
|
|
|
3628
3871
|
};
|
|
3629
3872
|
}
|
|
3630
3873
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
3631
|
-
const simplified =
|
|
3874
|
+
const simplified = simplifyRoute2(points);
|
|
3632
3875
|
if (simplified.length >= 3) {
|
|
3633
3876
|
return simplified;
|
|
3634
3877
|
}
|
|
@@ -3916,7 +4159,7 @@ function squaredDistance2(a, b) {
|
|
|
3916
4159
|
const dy = a.y - b.y;
|
|
3917
4160
|
return dx * dx + dy * dy;
|
|
3918
4161
|
}
|
|
3919
|
-
function
|
|
4162
|
+
function simplifyRoute2(points) {
|
|
3920
4163
|
const withoutDuplicates = [];
|
|
3921
4164
|
for (const point2 of points) {
|
|
3922
4165
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -3928,7 +4171,7 @@ function simplifyRoute(points) {
|
|
|
3928
4171
|
for (const point2 of withoutDuplicates) {
|
|
3929
4172
|
const previous = simplified.at(-1);
|
|
3930
4173
|
const beforePrevious = simplified.at(-2);
|
|
3931
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4174
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
3932
4175
|
simplified[simplified.length - 1] = { ...point2 };
|
|
3933
4176
|
} else {
|
|
3934
4177
|
simplified.push({ ...point2 });
|
|
@@ -4143,17 +4386,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4143
4386
|
return true;
|
|
4144
4387
|
}
|
|
4145
4388
|
if (start.x === end.x) {
|
|
4146
|
-
return start.x > left && start.x < right &&
|
|
4389
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4147
4390
|
}
|
|
4148
4391
|
if (start.y === end.y) {
|
|
4149
|
-
return start.y > top && start.y < bottom &&
|
|
4392
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4150
4393
|
}
|
|
4151
4394
|
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);
|
|
4152
4395
|
}
|
|
4153
4396
|
function pointInsideBox(point2, box) {
|
|
4154
4397
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4155
4398
|
}
|
|
4156
|
-
function
|
|
4399
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4157
4400
|
const low = Math.min(a, b);
|
|
4158
4401
|
const high = Math.max(a, b);
|
|
4159
4402
|
return high > min && low < max;
|
|
@@ -4177,7 +4420,7 @@ function segmentBox(a, b) {
|
|
|
4177
4420
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4178
4421
|
};
|
|
4179
4422
|
}
|
|
4180
|
-
function
|
|
4423
|
+
function areCollinear2(a, b, c) {
|
|
4181
4424
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4182
4425
|
}
|
|
4183
4426
|
|
|
@@ -4257,6 +4500,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4257
4500
|
options,
|
|
4258
4501
|
diagnostics
|
|
4259
4502
|
);
|
|
4503
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4260
4504
|
const constrained = applyLayoutConstraints({
|
|
4261
4505
|
direction: diagram.direction,
|
|
4262
4506
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4320,6 +4564,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4320
4564
|
swimlanes: coordinatedSwimlanes,
|
|
4321
4565
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4322
4566
|
});
|
|
4567
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
4568
|
+
styledEdges,
|
|
4569
|
+
nodeGeometryById,
|
|
4570
|
+
options.textMeasurer
|
|
4571
|
+
);
|
|
4323
4572
|
const layoutBoxes = [
|
|
4324
4573
|
...coordinatedNodes.map((node) => node.box),
|
|
4325
4574
|
...coordinatedNodes.flatMap(
|
|
@@ -4400,7 +4649,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4400
4649
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4401
4650
|
const routingTextObstacles = [
|
|
4402
4651
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4403
|
-
...frameTextAnnotation.filter(isPreRouteTextObstacle)
|
|
4652
|
+
...frameTextAnnotation.filter(isPreRouteTextObstacle),
|
|
4653
|
+
// Dry-run edge-label estimates so edges route around
|
|
4654
|
+
// each other's label areas (Issue #41).
|
|
4655
|
+
...edgeLabelEstimates
|
|
4404
4656
|
];
|
|
4405
4657
|
const margin = options.obstacleMargin ?? 0;
|
|
4406
4658
|
const softObstacles = [
|
|
@@ -4433,7 +4685,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4433
4685
|
hardObstacles,
|
|
4434
4686
|
diagram.direction,
|
|
4435
4687
|
options,
|
|
4436
|
-
diagnostics
|
|
4688
|
+
diagnostics,
|
|
4689
|
+
coordinatedGroups
|
|
4437
4690
|
);
|
|
4438
4691
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4439
4692
|
coordinatedEdges,
|
|
@@ -4546,22 +4799,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
4546
4799
|
y: layout2.box.y + offsetY,
|
|
4547
4800
|
width: layout2.box.width,
|
|
4548
4801
|
height: layout2.box.height
|
|
4549
|
-
}
|
|
4550
|
-
contentBox: {
|
|
4551
|
-
x: layout2.contentBox.x + offsetX,
|
|
4552
|
-
y: layout2.contentBox.y + offsetY,
|
|
4553
|
-
width: layout2.contentBox.width,
|
|
4554
|
-
height: layout2.contentBox.height
|
|
4555
|
-
},
|
|
4556
|
-
lines: layout2.lines.map((line) => ({
|
|
4557
|
-
...line,
|
|
4558
|
-
box: {
|
|
4559
|
-
x: line.box.x + offsetX,
|
|
4560
|
-
y: line.box.y + offsetY,
|
|
4561
|
-
width: line.box.width,
|
|
4562
|
-
height: line.box.height
|
|
4563
|
-
}
|
|
4564
|
-
}))
|
|
4802
|
+
}
|
|
4565
4803
|
};
|
|
4566
4804
|
}
|
|
4567
4805
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -5508,6 +5746,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5508
5746
|
});
|
|
5509
5747
|
continue;
|
|
5510
5748
|
}
|
|
5749
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
5511
5750
|
const geometry = computeShapeGeometry({
|
|
5512
5751
|
shape: node.shape,
|
|
5513
5752
|
box,
|
|
@@ -5517,7 +5756,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5517
5756
|
id: node.id,
|
|
5518
5757
|
...node.label === void 0 ? {} : { label: node.label },
|
|
5519
5758
|
...node.style === void 0 ? {} : { style: node.style },
|
|
5520
|
-
...
|
|
5759
|
+
...ports === void 0 ? {} : { ports },
|
|
5521
5760
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
5522
5761
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
5523
5762
|
shape: node.shape,
|
|
@@ -5529,6 +5768,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5529
5768
|
}
|
|
5530
5769
|
return coordinated;
|
|
5531
5770
|
}
|
|
5771
|
+
var PORT_BOX_SIZE = 10;
|
|
5772
|
+
var MIN_PORT_EDGE_GAP = 12;
|
|
5773
|
+
function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
5774
|
+
const shiftingEnabled = options.portShifting?.enabled ?? true;
|
|
5775
|
+
if (!shiftingEnabled) return;
|
|
5776
|
+
const requestedSpacing = options.portShifting?.spacing ?? 24;
|
|
5777
|
+
const minSpacing = Math.max(
|
|
5778
|
+
requestedSpacing,
|
|
5779
|
+
PORT_BOX_SIZE + MIN_PORT_EDGE_GAP
|
|
5780
|
+
);
|
|
5781
|
+
for (const node of nodes) {
|
|
5782
|
+
if (node.ports === void 0 || node.ports.length === 0) continue;
|
|
5783
|
+
const box = boxes.get(node.id);
|
|
5784
|
+
if (box === void 0) continue;
|
|
5785
|
+
let heightExpansion = 0;
|
|
5786
|
+
let widthExpansion = 0;
|
|
5787
|
+
const portsBySide = /* @__PURE__ */ new Map();
|
|
5788
|
+
for (const port of node.ports) {
|
|
5789
|
+
const list = portsBySide.get(port.side) ?? [];
|
|
5790
|
+
list.push(port);
|
|
5791
|
+
portsBySide.set(port.side, list);
|
|
5792
|
+
}
|
|
5793
|
+
for (const [side, ports] of portsBySide) {
|
|
5794
|
+
const count = (ports ?? []).length;
|
|
5795
|
+
if (count <= 1) continue;
|
|
5796
|
+
const isVertical = side === "left" || side === "right";
|
|
5797
|
+
const availableSpan = isVertical ? box.height : box.width;
|
|
5798
|
+
const requiredSpan = (count - 1) * minSpacing + PORT_BOX_SIZE;
|
|
5799
|
+
if (requiredSpan > availableSpan) {
|
|
5800
|
+
const expansion = requiredSpan - availableSpan;
|
|
5801
|
+
if (isVertical) {
|
|
5802
|
+
heightExpansion = Math.max(heightExpansion, expansion);
|
|
5803
|
+
} else {
|
|
5804
|
+
widthExpansion = Math.max(widthExpansion, expansion);
|
|
5805
|
+
}
|
|
5806
|
+
diagnostics.push({
|
|
5807
|
+
severity: "info",
|
|
5808
|
+
code: "port_capacity_overflow",
|
|
5809
|
+
message: `Expanded node ${node.id} ${isVertical ? "height" : "width"} by ${Math.ceil(expansion)} px to fit ${count} port(s) on ${side} side.`,
|
|
5810
|
+
path: ["nodes", node.id, "ports"],
|
|
5811
|
+
detail: {
|
|
5812
|
+
nodeId: node.id,
|
|
5813
|
+
side,
|
|
5814
|
+
portCount: count,
|
|
5815
|
+
expansion: Math.ceil(expansion)
|
|
5816
|
+
}
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
if (heightExpansion > 0) {
|
|
5821
|
+
box.y -= heightExpansion / 2;
|
|
5822
|
+
box.height += heightExpansion;
|
|
5823
|
+
}
|
|
5824
|
+
if (widthExpansion > 0) {
|
|
5825
|
+
box.x -= widthExpansion / 2;
|
|
5826
|
+
box.width += widthExpansion;
|
|
5827
|
+
}
|
|
5828
|
+
if ((heightExpansion > 0 || widthExpansion > 0) && node.labelLayout !== void 0) {
|
|
5829
|
+
const layout2 = node.labelLayout;
|
|
5830
|
+
const newOffsetX = Math.max(0, (box.width - layout2.box.width) / 2);
|
|
5831
|
+
const newOffsetY = Math.max(0, (box.height - layout2.box.height) / 2);
|
|
5832
|
+
node.labelLayout = {
|
|
5833
|
+
...layout2,
|
|
5834
|
+
box: {
|
|
5835
|
+
...layout2.box,
|
|
5836
|
+
x: newOffsetX,
|
|
5837
|
+
y: newOffsetY
|
|
5838
|
+
}
|
|
5839
|
+
};
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5532
5843
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
5533
5844
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
5534
5845
|
for (const port of node.ports ?? []) {
|
|
@@ -5565,7 +5876,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5565
5876
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
5566
5877
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
5567
5878
|
const availableSpan = 2 * maxOffset;
|
|
5568
|
-
const
|
|
5879
|
+
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
5880
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
5881
|
+
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
5882
|
+
minSpacing
|
|
5883
|
+
) : requestedSpacing;
|
|
5569
5884
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
5570
5885
|
switch (side) {
|
|
5571
5886
|
case "left":
|
|
@@ -5591,7 +5906,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5591
5906
|
}
|
|
5592
5907
|
}
|
|
5593
5908
|
function portBox(anchor) {
|
|
5594
|
-
const size =
|
|
5909
|
+
const size = PORT_BOX_SIZE;
|
|
5595
5910
|
return {
|
|
5596
5911
|
x: anchor.x - size / 2,
|
|
5597
5912
|
y: anchor.y - size / 2,
|
|
@@ -6180,7 +6495,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6180
6495
|
}
|
|
6181
6496
|
};
|
|
6182
6497
|
}
|
|
6183
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
|
|
6498
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
6184
6499
|
const coordinated = [];
|
|
6185
6500
|
const coordinatedNodeById = new Map(
|
|
6186
6501
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6204,8 +6519,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6204
6519
|
}
|
|
6205
6520
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6206
6521
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6207
|
-
const
|
|
6208
|
-
const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
|
|
6522
|
+
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
6209
6523
|
const route = routeEdge({
|
|
6210
6524
|
kind: options.routeKind ?? "orthogonal",
|
|
6211
6525
|
direction,
|
|
@@ -6218,6 +6532,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6218
6532
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6219
6533
|
),
|
|
6220
6534
|
...softObstacles,
|
|
6535
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6221
6536
|
...routeTextObstacles
|
|
6222
6537
|
],
|
|
6223
6538
|
hardObstacles,
|
|
@@ -6236,15 +6551,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6236
6551
|
}
|
|
6237
6552
|
return coordinated;
|
|
6238
6553
|
}
|
|
6239
|
-
function
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6554
|
+
function isEdgeConnectedTextAnnotation(edge, annotation) {
|
|
6555
|
+
switch (annotation.surfaceKind) {
|
|
6556
|
+
case "edge-label":
|
|
6557
|
+
return annotation.ownerId === edge.id;
|
|
6558
|
+
case "node-label":
|
|
6559
|
+
case "compartment-row":
|
|
6560
|
+
return annotation.ownerId === edge.source.nodeId || annotation.ownerId === edge.target.nodeId;
|
|
6561
|
+
case "port-label":
|
|
6562
|
+
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}`;
|
|
6563
|
+
case "group-label":
|
|
6564
|
+
case "swimlane-label":
|
|
6565
|
+
case "frame-title":
|
|
6566
|
+
return false;
|
|
6243
6567
|
}
|
|
6244
|
-
|
|
6245
|
-
|
|
6568
|
+
}
|
|
6569
|
+
function ancestorGroupIds(groups, nodeId) {
|
|
6570
|
+
const direct = /* @__PURE__ */ new Set();
|
|
6571
|
+
for (const group of groups) {
|
|
6572
|
+
if (group.nodeIds.includes(nodeId)) {
|
|
6573
|
+
direct.add(group.id);
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
let previousSize = -1;
|
|
6577
|
+
const ancestors = new Set(direct);
|
|
6578
|
+
while (ancestors.size !== previousSize) {
|
|
6579
|
+
previousSize = ancestors.size;
|
|
6580
|
+
for (const group of groups) {
|
|
6581
|
+
for (const candidate of ancestors) {
|
|
6582
|
+
if (group.groupIds.includes(candidate)) {
|
|
6583
|
+
ancestors.add(group.id);
|
|
6584
|
+
break;
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6246
6588
|
}
|
|
6247
|
-
return
|
|
6589
|
+
return ancestors;
|
|
6590
|
+
}
|
|
6591
|
+
function groupObstaclesForEdge(edge, groups, margin) {
|
|
6592
|
+
const sourceAncestors = ancestorGroupIds(groups, edge.source.nodeId);
|
|
6593
|
+
const targetAncestors = ancestorGroupIds(groups, edge.target.nodeId);
|
|
6594
|
+
return groups.filter((group) => {
|
|
6595
|
+
if (sourceAncestors.has(group.id) || targetAncestors.has(group.id)) {
|
|
6596
|
+
return false;
|
|
6597
|
+
}
|
|
6598
|
+
return true;
|
|
6599
|
+
}).map((group) => margin === 0 ? group.box : expandBox(group.box, margin));
|
|
6248
6600
|
}
|
|
6249
6601
|
function coordinateBaseTextAnnotations(input) {
|
|
6250
6602
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -6432,6 +6784,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
6432
6784
|
}
|
|
6433
6785
|
return annotations;
|
|
6434
6786
|
}
|
|
6787
|
+
function estimateEdgeLabelAnnotations(edges, nodes, textMeasurer) {
|
|
6788
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
6789
|
+
const annotations = [];
|
|
6790
|
+
for (const edge of edges) {
|
|
6791
|
+
if (edge.label?.text === void 0) {
|
|
6792
|
+
continue;
|
|
6793
|
+
}
|
|
6794
|
+
const sourceGeom = nodes.get(edge.source.nodeId);
|
|
6795
|
+
const targetGeom = nodes.get(edge.target.nodeId);
|
|
6796
|
+
if (sourceGeom === void 0 || targetGeom === void 0) {
|
|
6797
|
+
continue;
|
|
6798
|
+
}
|
|
6799
|
+
const layout2 = fitLabel(
|
|
6800
|
+
edge.label.text,
|
|
6801
|
+
{
|
|
6802
|
+
font: typographyTextStyle(edge.label, {
|
|
6803
|
+
fontFamily: "Arial",
|
|
6804
|
+
fontSize: 12,
|
|
6805
|
+
lineHeight: 14
|
|
6806
|
+
}),
|
|
6807
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
6808
|
+
minSize: { width: 0, height: 0 },
|
|
6809
|
+
maxWidth: 200
|
|
6810
|
+
},
|
|
6811
|
+
measurer
|
|
6812
|
+
);
|
|
6813
|
+
const cx = (sourceGeom.center.x + targetGeom.center.x) / 2;
|
|
6814
|
+
const cy = (sourceGeom.center.y + targetGeom.center.y) / 2;
|
|
6815
|
+
const box = {
|
|
6816
|
+
x: cx - layout2.box.width / 2,
|
|
6817
|
+
y: cy - layout2.box.height / 2,
|
|
6818
|
+
width: layout2.box.width,
|
|
6819
|
+
height: layout2.box.height
|
|
6820
|
+
};
|
|
6821
|
+
annotations.push({
|
|
6822
|
+
text: layout2.text,
|
|
6823
|
+
ownerId: edge.id,
|
|
6824
|
+
surfaceKind: "edge-label",
|
|
6825
|
+
box,
|
|
6826
|
+
anchor: { x: cx, y: cy },
|
|
6827
|
+
paddings: layout2.padding,
|
|
6828
|
+
lines: layout2.lines,
|
|
6829
|
+
fontFamily: normalizeOutputFontFamily(layout2.font),
|
|
6830
|
+
fontSize: layout2.font.fontSize,
|
|
6831
|
+
textBackend: layout2.textBackend
|
|
6832
|
+
});
|
|
6833
|
+
}
|
|
6834
|
+
return annotations;
|
|
6835
|
+
}
|
|
6435
6836
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
6436
6837
|
const layout2 = fitLabel(
|
|
6437
6838
|
frame.titleTab,
|
|
@@ -6552,9 +6953,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6552
6953
|
const diagnostics = [];
|
|
6553
6954
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
6554
6955
|
for (const edge of edges) {
|
|
6555
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
6556
6956
|
for (const annotation of relevantAnnotations) {
|
|
6557
|
-
if (
|
|
6957
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
6558
6958
|
continue;
|
|
6559
6959
|
}
|
|
6560
6960
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -6578,9 +6978,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6578
6978
|
return diagnostics;
|
|
6579
6979
|
}
|
|
6580
6980
|
function isPreRouteTextObstacle(annotation) {
|
|
6581
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
6582
|
-
return false;
|
|
6583
|
-
}
|
|
6584
6981
|
return isRouteClearanceText(annotation);
|
|
6585
6982
|
}
|
|
6586
6983
|
function isRouteClearanceText(annotation) {
|
|
@@ -6591,8 +6988,9 @@ function isRouteClearanceText(annotation) {
|
|
|
6591
6988
|
case "frame-title":
|
|
6592
6989
|
return true;
|
|
6593
6990
|
case "node-label":
|
|
6594
|
-
case "group-label":
|
|
6595
6991
|
case "compartment-row":
|
|
6992
|
+
return true;
|
|
6993
|
+
case "group-label":
|
|
6596
6994
|
return textExtendsOutsideAnchor(annotation);
|
|
6597
6995
|
}
|
|
6598
6996
|
}
|
|
@@ -6625,17 +7023,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
6625
7023
|
return true;
|
|
6626
7024
|
}
|
|
6627
7025
|
if (start.x === end.x) {
|
|
6628
|
-
return start.x > left && start.x < right &&
|
|
7026
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
6629
7027
|
}
|
|
6630
7028
|
if (start.y === end.y) {
|
|
6631
|
-
return start.y > top && start.y < bottom &&
|
|
7029
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
6632
7030
|
}
|
|
6633
7031
|
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);
|
|
6634
7032
|
}
|
|
6635
7033
|
function pointInsideBox2(point2, box) {
|
|
6636
7034
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
6637
7035
|
}
|
|
6638
|
-
function
|
|
7036
|
+
function rangesOverlap3(a, b, min, max) {
|
|
6639
7037
|
const low = Math.min(a, b);
|
|
6640
7038
|
const high = Math.max(a, b);
|
|
6641
7039
|
return high > min && low < max;
|