@crazyhappyone/auto-graph 0.1.3 → 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 +588 -60
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +588 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +589 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +589 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -1292,6 +1292,9 @@ function applyLayoutConstraints(input) {
|
|
|
1292
1292
|
const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
|
|
1293
1293
|
applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
|
|
1294
1294
|
applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
|
|
1295
|
+
if (input.distributeContainedChildren) {
|
|
1296
|
+
yieldFixedPositionLocks(input, boxes, locks);
|
|
1297
|
+
}
|
|
1295
1298
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
1296
1299
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
1297
1300
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -1305,6 +1308,13 @@ function applyLayoutConstraints(input) {
|
|
|
1305
1308
|
);
|
|
1306
1309
|
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
1307
1310
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1311
|
+
if (input.distributeContainedChildren) {
|
|
1312
|
+
const diagBefore = diagnostics.length;
|
|
1313
|
+
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
1314
|
+
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1315
|
+
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
1316
|
+
}
|
|
1317
|
+
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
1308
1318
|
reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
|
|
1309
1319
|
reportIntraContainerOverflow(input, boxes, diagnostics);
|
|
1310
1320
|
return { boxes, locks, diagnostics };
|
|
@@ -1350,6 +1360,62 @@ function applyFixedPositionLocks(nodes, boxes, locks, diagnostics) {
|
|
|
1350
1360
|
locks.set(node.id, { nodeId: node.id, source: "fixed-position" });
|
|
1351
1361
|
}
|
|
1352
1362
|
}
|
|
1363
|
+
function dedupReplayDiagnostics(diagnostics, keepUpTo) {
|
|
1364
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1365
|
+
for (let i = 0; i < keepUpTo && i < diagnostics.length; i += 1) {
|
|
1366
|
+
const d = diagnostics[i];
|
|
1367
|
+
if (d === void 0) continue;
|
|
1368
|
+
seen.add(diagnosticFingerprint(d));
|
|
1369
|
+
}
|
|
1370
|
+
for (let i = diagnostics.length - 1; i >= keepUpTo; i -= 1) {
|
|
1371
|
+
const d = diagnostics[i];
|
|
1372
|
+
if (d === void 0) continue;
|
|
1373
|
+
const fp = diagnosticFingerprint(d);
|
|
1374
|
+
if (seen.has(fp)) {
|
|
1375
|
+
diagnostics.splice(i, 1);
|
|
1376
|
+
} else {
|
|
1377
|
+
seen.add(fp);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function diagnosticFingerprint(d) {
|
|
1382
|
+
const nodeId = typeof d.detail?.nodeId === "string" ? d.detail.nodeId : "";
|
|
1383
|
+
const containerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : "";
|
|
1384
|
+
return `${d.code}|${nodeId}|${containerId}`;
|
|
1385
|
+
}
|
|
1386
|
+
function yieldFixedPositionLocks(input, boxes, locks) {
|
|
1387
|
+
for (const c of input.constraints) {
|
|
1388
|
+
if (c.kind !== "containment") continue;
|
|
1389
|
+
const container = boxes.get(c.containerId);
|
|
1390
|
+
if (container === void 0) continue;
|
|
1391
|
+
const content = contentBox(container, c.padding);
|
|
1392
|
+
const mainAxis = input.direction === "LR" || input.direction === "RL" ? "width" : "height";
|
|
1393
|
+
const crossAxis = mainAxis === "width" ? "height" : "width";
|
|
1394
|
+
let eligible = 0;
|
|
1395
|
+
for (const childId of c.childIds) {
|
|
1396
|
+
const box = boxes.get(childId);
|
|
1397
|
+
if (box === void 0) continue;
|
|
1398
|
+
const lock = locks.get(childId);
|
|
1399
|
+
if (lock?.source === "exact-position") continue;
|
|
1400
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
1401
|
+
if (fits) {
|
|
1402
|
+
eligible += 1;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (eligible < 2) continue;
|
|
1406
|
+
for (const childId of c.childIds) {
|
|
1407
|
+
const lock = locks.get(childId);
|
|
1408
|
+
if (lock?.source === "fixed-position") {
|
|
1409
|
+
const box = boxes.get(childId);
|
|
1410
|
+
if (box === void 0) continue;
|
|
1411
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
1412
|
+
if (fits) {
|
|
1413
|
+
locks.delete(childId);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1353
1419
|
function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
|
|
1354
1420
|
for (const constraint of constraints) {
|
|
1355
1421
|
if (constraint.kind !== "exact-position") {
|
|
@@ -1422,7 +1488,7 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
|
|
|
1422
1488
|
code: "constraints.locked-target-not-moved",
|
|
1423
1489
|
message: `Locked child ${childId} was not moved into containment.`,
|
|
1424
1490
|
path: ["constraints", constraint.id ?? constraint.containerId],
|
|
1425
|
-
detail: { nodeId: childId }
|
|
1491
|
+
detail: { nodeId: childId, containerId: constraint.containerId }
|
|
1426
1492
|
});
|
|
1427
1493
|
if (!isInside(child, content)) {
|
|
1428
1494
|
diagnostics.push({
|
|
@@ -1574,6 +1640,60 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
|
|
|
1574
1640
|
}
|
|
1575
1641
|
reportOverlaps(boxes, diagnostics, ignoredPairs);
|
|
1576
1642
|
}
|
|
1643
|
+
function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
|
|
1644
|
+
for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
|
|
1645
|
+
const d = diagnostics[i];
|
|
1646
|
+
if (d === void 0) continue;
|
|
1647
|
+
if (d.code === "constraints.overlap.unresolved") {
|
|
1648
|
+
const aId = d.detail?.firstId;
|
|
1649
|
+
const bId = d.detail?.secondId;
|
|
1650
|
+
if (typeof aId !== "string" || typeof bId !== "string") continue;
|
|
1651
|
+
const a = boxes.get(aId);
|
|
1652
|
+
const b = boxes.get(bId);
|
|
1653
|
+
if (a !== void 0 && b !== void 0 && !intersectsAabb(a, b)) {
|
|
1654
|
+
diagnostics.splice(i, 1);
|
|
1655
|
+
}
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
if (d.code === "constraints.containment.impossible" || d.code === "constraints.locked-target-not-moved" && typeof d.message === "string" && d.message.includes("not moved into containment")) {
|
|
1659
|
+
const nodeId = d.detail?.nodeId;
|
|
1660
|
+
if (typeof nodeId !== "string") continue;
|
|
1661
|
+
const child = boxes.get(nodeId);
|
|
1662
|
+
if (child === void 0) continue;
|
|
1663
|
+
const diagContainerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : void 0;
|
|
1664
|
+
let resolved = false;
|
|
1665
|
+
for (const c of constraints) {
|
|
1666
|
+
if (c.kind !== "containment") continue;
|
|
1667
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
1668
|
+
if (diagContainerId !== void 0 && c.containerId !== diagContainerId) {
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
const container = boxes.get(c.containerId);
|
|
1672
|
+
if (container === void 0) continue;
|
|
1673
|
+
const content = contentBox(container, c.padding);
|
|
1674
|
+
if (isInside(child, content)) {
|
|
1675
|
+
diagnostics.splice(i, 1);
|
|
1676
|
+
resolved = true;
|
|
1677
|
+
}
|
|
1678
|
+
break;
|
|
1679
|
+
}
|
|
1680
|
+
if (!resolved && diagContainerId !== void 0) {
|
|
1681
|
+
for (const c of constraints) {
|
|
1682
|
+
if (c.kind !== "containment") continue;
|
|
1683
|
+
if (c.containerId !== diagContainerId) continue;
|
|
1684
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
1685
|
+
const container = boxes.get(c.containerId);
|
|
1686
|
+
if (container === void 0) continue;
|
|
1687
|
+
const content = contentBox(container, c.padding);
|
|
1688
|
+
if (isInside(child, content)) {
|
|
1689
|
+
diagnostics.splice(i, 1);
|
|
1690
|
+
}
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1577
1697
|
function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
|
|
1578
1698
|
const ids = [...boxes.keys()].sort();
|
|
1579
1699
|
const reported = new Set(
|
|
@@ -1881,6 +2001,11 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1881
2001
|
continue;
|
|
1882
2002
|
}
|
|
1883
2003
|
if (locks.has(childId)) {
|
|
2004
|
+
const lock = locks.get(childId);
|
|
2005
|
+
if (lock?.source === "fixed-position") {
|
|
2006
|
+
unlocked.push({ id: childId, box });
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
1884
2009
|
diagnostics.push({
|
|
1885
2010
|
severity: "warning",
|
|
1886
2011
|
code: "constraints.locked-target-not-moved",
|
|
@@ -1939,6 +2064,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1939
2064
|
});
|
|
1940
2065
|
}
|
|
1941
2066
|
boxes.set(child.id, clamped);
|
|
2067
|
+
locks.delete(child.id);
|
|
1942
2068
|
pos = clamped[axis] + clamped[mainSize] + minGap;
|
|
1943
2069
|
}
|
|
1944
2070
|
diagnostics.push({
|
|
@@ -3254,6 +3380,213 @@ function isValidDimension(value) {
|
|
|
3254
3380
|
return Number.isFinite(value) && value >= 0;
|
|
3255
3381
|
}
|
|
3256
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
|
+
|
|
3257
3590
|
// src/routing/routes.ts
|
|
3258
3591
|
function routeEdge(input) {
|
|
3259
3592
|
const diagnostics = [];
|
|
@@ -3299,6 +3632,43 @@ function routeEdge(input) {
|
|
|
3299
3632
|
}
|
|
3300
3633
|
return { points, diagnostics };
|
|
3301
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
|
+
}
|
|
3302
3672
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3303
3673
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3304
3674
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -3388,7 +3758,7 @@ function routeEdge(input) {
|
|
|
3388
3758
|
const rerouted = greedyRerouteAroundObstacles(
|
|
3389
3759
|
bestPoints2,
|
|
3390
3760
|
allObstacles,
|
|
3391
|
-
|
|
3761
|
+
maxAttempts
|
|
3392
3762
|
);
|
|
3393
3763
|
const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
|
|
3394
3764
|
rerouted,
|
|
@@ -3501,7 +3871,7 @@ function routeEdge(input) {
|
|
|
3501
3871
|
};
|
|
3502
3872
|
}
|
|
3503
3873
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
3504
|
-
const simplified =
|
|
3874
|
+
const simplified = simplifyRoute2(points);
|
|
3505
3875
|
if (simplified.length >= 3) {
|
|
3506
3876
|
return simplified;
|
|
3507
3877
|
}
|
|
@@ -3789,7 +4159,7 @@ function squaredDistance2(a, b) {
|
|
|
3789
4159
|
const dy = a.y - b.y;
|
|
3790
4160
|
return dx * dx + dy * dy;
|
|
3791
4161
|
}
|
|
3792
|
-
function
|
|
4162
|
+
function simplifyRoute2(points) {
|
|
3793
4163
|
const withoutDuplicates = [];
|
|
3794
4164
|
for (const point2 of points) {
|
|
3795
4165
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -3801,7 +4171,7 @@ function simplifyRoute(points) {
|
|
|
3801
4171
|
for (const point2 of withoutDuplicates) {
|
|
3802
4172
|
const previous = simplified.at(-1);
|
|
3803
4173
|
const beforePrevious = simplified.at(-2);
|
|
3804
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4174
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
3805
4175
|
simplified[simplified.length - 1] = { ...point2 };
|
|
3806
4176
|
} else {
|
|
3807
4177
|
simplified.push({ ...point2 });
|
|
@@ -4016,17 +4386,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4016
4386
|
return true;
|
|
4017
4387
|
}
|
|
4018
4388
|
if (start.x === end.x) {
|
|
4019
|
-
return start.x > left && start.x < right &&
|
|
4389
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4020
4390
|
}
|
|
4021
4391
|
if (start.y === end.y) {
|
|
4022
|
-
return start.y > top && start.y < bottom &&
|
|
4392
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4023
4393
|
}
|
|
4024
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);
|
|
4025
4395
|
}
|
|
4026
4396
|
function pointInsideBox(point2, box) {
|
|
4027
4397
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4028
4398
|
}
|
|
4029
|
-
function
|
|
4399
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4030
4400
|
const low = Math.min(a, b);
|
|
4031
4401
|
const high = Math.max(a, b);
|
|
4032
4402
|
return high > min && low < max;
|
|
@@ -4050,7 +4420,7 @@ function segmentBox(a, b) {
|
|
|
4050
4420
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4051
4421
|
};
|
|
4052
4422
|
}
|
|
4053
|
-
function
|
|
4423
|
+
function areCollinear2(a, b, c) {
|
|
4054
4424
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4055
4425
|
}
|
|
4056
4426
|
|
|
@@ -4130,6 +4500,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4130
4500
|
options,
|
|
4131
4501
|
diagnostics
|
|
4132
4502
|
);
|
|
4503
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4133
4504
|
const constrained = applyLayoutConstraints({
|
|
4134
4505
|
direction: diagram.direction,
|
|
4135
4506
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4150,9 +4521,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4150
4521
|
options?.overlapSpacing ?? 40,
|
|
4151
4522
|
Math.max(0, options?.minLaneGutter ?? 0)
|
|
4152
4523
|
);
|
|
4153
|
-
|
|
4154
|
-
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4155
|
-
}
|
|
4524
|
+
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4156
4525
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
4157
4526
|
const coordinatedNodes = coordinateNodes(
|
|
4158
4527
|
styledNodes,
|
|
@@ -4195,6 +4564,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4195
4564
|
swimlanes: coordinatedSwimlanes,
|
|
4196
4565
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4197
4566
|
});
|
|
4567
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
4568
|
+
styledEdges,
|
|
4569
|
+
nodeGeometryById,
|
|
4570
|
+
options.textMeasurer
|
|
4571
|
+
);
|
|
4198
4572
|
const layoutBoxes = [
|
|
4199
4573
|
...coordinatedNodes.map((node) => node.box),
|
|
4200
4574
|
...coordinatedNodes.flatMap(
|
|
@@ -4275,7 +4649,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4275
4649
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4276
4650
|
const routingTextObstacles = [
|
|
4277
4651
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4278
|
-
...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
|
|
4279
4656
|
];
|
|
4280
4657
|
const margin = options.obstacleMargin ?? 0;
|
|
4281
4658
|
const softObstacles = [
|
|
@@ -4308,7 +4685,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4308
4685
|
hardObstacles,
|
|
4309
4686
|
diagram.direction,
|
|
4310
4687
|
options,
|
|
4311
|
-
diagnostics
|
|
4688
|
+
diagnostics,
|
|
4689
|
+
coordinatedGroups
|
|
4312
4690
|
);
|
|
4313
4691
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4314
4692
|
coordinatedEdges,
|
|
@@ -4421,22 +4799,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
4421
4799
|
y: layout2.box.y + offsetY,
|
|
4422
4800
|
width: layout2.box.width,
|
|
4423
4801
|
height: layout2.box.height
|
|
4424
|
-
}
|
|
4425
|
-
contentBox: {
|
|
4426
|
-
x: layout2.contentBox.x + offsetX,
|
|
4427
|
-
y: layout2.contentBox.y + offsetY,
|
|
4428
|
-
width: layout2.contentBox.width,
|
|
4429
|
-
height: layout2.contentBox.height
|
|
4430
|
-
},
|
|
4431
|
-
lines: layout2.lines.map((line) => ({
|
|
4432
|
-
...line,
|
|
4433
|
-
box: {
|
|
4434
|
-
x: line.box.x + offsetX,
|
|
4435
|
-
y: line.box.y + offsetY,
|
|
4436
|
-
width: line.box.width,
|
|
4437
|
-
height: line.box.height
|
|
4438
|
-
}
|
|
4439
|
-
}))
|
|
4802
|
+
}
|
|
4440
4803
|
};
|
|
4441
4804
|
}
|
|
4442
4805
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -5383,6 +5746,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5383
5746
|
});
|
|
5384
5747
|
continue;
|
|
5385
5748
|
}
|
|
5749
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
5386
5750
|
const geometry = computeShapeGeometry({
|
|
5387
5751
|
shape: node.shape,
|
|
5388
5752
|
box,
|
|
@@ -5392,7 +5756,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5392
5756
|
id: node.id,
|
|
5393
5757
|
...node.label === void 0 ? {} : { label: node.label },
|
|
5394
5758
|
...node.style === void 0 ? {} : { style: node.style },
|
|
5395
|
-
...
|
|
5759
|
+
...ports === void 0 ? {} : { ports },
|
|
5396
5760
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
5397
5761
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
5398
5762
|
shape: node.shape,
|
|
@@ -5404,6 +5768,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5404
5768
|
}
|
|
5405
5769
|
return coordinated;
|
|
5406
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
|
+
}
|
|
5407
5843
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
5408
5844
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
5409
5845
|
for (const port of node.ports ?? []) {
|
|
@@ -5440,7 +5876,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5440
5876
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
5441
5877
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
5442
5878
|
const availableSpan = 2 * maxOffset;
|
|
5443
|
-
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;
|
|
5444
5884
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
5445
5885
|
switch (side) {
|
|
5446
5886
|
case "left":
|
|
@@ -5466,7 +5906,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5466
5906
|
}
|
|
5467
5907
|
}
|
|
5468
5908
|
function portBox(anchor) {
|
|
5469
|
-
const size =
|
|
5909
|
+
const size = PORT_BOX_SIZE;
|
|
5470
5910
|
return {
|
|
5471
5911
|
x: anchor.x - size / 2,
|
|
5472
5912
|
y: anchor.y - size / 2,
|
|
@@ -6055,7 +6495,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6055
6495
|
}
|
|
6056
6496
|
};
|
|
6057
6497
|
}
|
|
6058
|
-
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) {
|
|
6059
6499
|
const coordinated = [];
|
|
6060
6500
|
const coordinatedNodeById = new Map(
|
|
6061
6501
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6079,8 +6519,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6079
6519
|
}
|
|
6080
6520
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6081
6521
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6082
|
-
const
|
|
6083
|
-
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);
|
|
6084
6523
|
const route = routeEdge({
|
|
6085
6524
|
kind: options.routeKind ?? "orthogonal",
|
|
6086
6525
|
direction,
|
|
@@ -6093,9 +6532,11 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6093
6532
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6094
6533
|
),
|
|
6095
6534
|
...softObstacles,
|
|
6535
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6096
6536
|
...routeTextObstacles
|
|
6097
6537
|
],
|
|
6098
|
-
hardObstacles
|
|
6538
|
+
hardObstacles,
|
|
6539
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
|
|
6099
6540
|
});
|
|
6100
6541
|
diagnostics.push(
|
|
6101
6542
|
...route.diagnostics.map((diagnostic) => ({
|
|
@@ -6110,15 +6551,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6110
6551
|
}
|
|
6111
6552
|
return coordinated;
|
|
6112
6553
|
}
|
|
6113
|
-
function
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
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;
|
|
6117
6567
|
}
|
|
6118
|
-
|
|
6119
|
-
|
|
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
|
+
}
|
|
6120
6588
|
}
|
|
6121
|
-
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));
|
|
6122
6600
|
}
|
|
6123
6601
|
function coordinateBaseTextAnnotations(input) {
|
|
6124
6602
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -6306,6 +6784,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
6306
6784
|
}
|
|
6307
6785
|
return annotations;
|
|
6308
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
|
+
}
|
|
6309
6836
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
6310
6837
|
const layout2 = fitLabel(
|
|
6311
6838
|
frame.titleTab,
|
|
@@ -6426,9 +6953,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6426
6953
|
const diagnostics = [];
|
|
6427
6954
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
6428
6955
|
for (const edge of edges) {
|
|
6429
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
6430
6956
|
for (const annotation of relevantAnnotations) {
|
|
6431
|
-
if (
|
|
6957
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
6432
6958
|
continue;
|
|
6433
6959
|
}
|
|
6434
6960
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -6452,9 +6978,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6452
6978
|
return diagnostics;
|
|
6453
6979
|
}
|
|
6454
6980
|
function isPreRouteTextObstacle(annotation) {
|
|
6455
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
6456
|
-
return false;
|
|
6457
|
-
}
|
|
6458
6981
|
return isRouteClearanceText(annotation);
|
|
6459
6982
|
}
|
|
6460
6983
|
function isRouteClearanceText(annotation) {
|
|
@@ -6465,8 +6988,9 @@ function isRouteClearanceText(annotation) {
|
|
|
6465
6988
|
case "frame-title":
|
|
6466
6989
|
return true;
|
|
6467
6990
|
case "node-label":
|
|
6468
|
-
case "group-label":
|
|
6469
6991
|
case "compartment-row":
|
|
6992
|
+
return true;
|
|
6993
|
+
case "group-label":
|
|
6470
6994
|
return textExtendsOutsideAnchor(annotation);
|
|
6471
6995
|
}
|
|
6472
6996
|
}
|
|
@@ -6499,17 +7023,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
6499
7023
|
return true;
|
|
6500
7024
|
}
|
|
6501
7025
|
if (start.x === end.x) {
|
|
6502
|
-
return start.x > left && start.x < right &&
|
|
7026
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
6503
7027
|
}
|
|
6504
7028
|
if (start.y === end.y) {
|
|
6505
|
-
return start.y > top && start.y < bottom &&
|
|
7029
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
6506
7030
|
}
|
|
6507
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);
|
|
6508
7032
|
}
|
|
6509
7033
|
function pointInsideBox2(point2, box) {
|
|
6510
7034
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
6511
7035
|
}
|
|
6512
|
-
function
|
|
7036
|
+
function rangesOverlap3(a, b, min, max) {
|
|
6513
7037
|
const low = Math.min(a, b);
|
|
6514
7038
|
const high = Math.max(a, b);
|
|
6515
7039
|
return high > min && low < max;
|
|
@@ -6578,7 +7102,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
6578
7102
|
for (const candidate of edgeLabelAnchorCandidates(
|
|
6579
7103
|
edge.points,
|
|
6580
7104
|
placement,
|
|
6581
|
-
layout2
|
|
7105
|
+
layout2,
|
|
7106
|
+
baseOffset
|
|
6582
7107
|
)) {
|
|
6583
7108
|
const labelBox = {
|
|
6584
7109
|
x: candidate.x - layout2.box.width / 2,
|
|
@@ -6610,8 +7135,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
6610
7135
|
}
|
|
6611
7136
|
return placement;
|
|
6612
7137
|
}
|
|
6613
|
-
function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
6614
|
-
const segment = labelSegmentOnPolyline(points);
|
|
7138
|
+
function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
|
|
7139
|
+
const segment = labelSegmentOnPolyline(points, baseOffset);
|
|
6615
7140
|
if (segment === void 0) {
|
|
6616
7141
|
return [placement];
|
|
6617
7142
|
}
|
|
@@ -6661,7 +7186,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
6661
7186
|
}, 0);
|
|
6662
7187
|
if (totalLen > 200) {
|
|
6663
7188
|
for (const ratio of [0.25, 0.75]) {
|
|
6664
|
-
const qp = labelPlacementAtRatio(points, ratio, totalLen);
|
|
7189
|
+
const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
|
|
6665
7190
|
if (qp !== void 0) {
|
|
6666
7191
|
candidates.push(qp);
|
|
6667
7192
|
const qTargetDist = totalLen * ratio;
|
|
@@ -6747,7 +7272,7 @@ function labelSegmentOnPolyline(points, baseOffset = 10) {
|
|
|
6747
7272
|
if (last === void 0) {
|
|
6748
7273
|
return void 0;
|
|
6749
7274
|
}
|
|
6750
|
-
const offset = labelOffset2(last);
|
|
7275
|
+
const offset = labelOffset2(last, baseOffset);
|
|
6751
7276
|
return {
|
|
6752
7277
|
start: last.start,
|
|
6753
7278
|
end: last.end,
|
|
@@ -6769,7 +7294,7 @@ function nonZeroSegments2(points) {
|
|
|
6769
7294
|
}
|
|
6770
7295
|
return segments;
|
|
6771
7296
|
}
|
|
6772
|
-
function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
7297
|
+
function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
|
|
6773
7298
|
if (points.length < 2 || ratio < 0 || ratio > 1) {
|
|
6774
7299
|
return void 0;
|
|
6775
7300
|
}
|
|
@@ -6787,7 +7312,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
|
6787
7312
|
}
|
|
6788
7313
|
if (travelled + segLen >= targetDist) {
|
|
6789
7314
|
const t = (targetDist - travelled) / segLen;
|
|
6790
|
-
const offset = labelOffset2(
|
|
7315
|
+
const offset = labelOffset2(
|
|
7316
|
+
{ start: prev, end: curr, length: segLen },
|
|
7317
|
+
baseOffset
|
|
7318
|
+
);
|
|
6791
7319
|
return {
|
|
6792
7320
|
x: prev.x + (curr.x - prev.x) * t + offset.x,
|
|
6793
7321
|
y: prev.y + (curr.y - prev.y) * t + offset.y
|