@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.js
CHANGED
|
@@ -1289,6 +1289,9 @@ function applyLayoutConstraints(input) {
|
|
|
1289
1289
|
const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
|
|
1290
1290
|
applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
|
|
1291
1291
|
applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
|
|
1292
|
+
if (input.distributeContainedChildren) {
|
|
1293
|
+
yieldFixedPositionLocks(input, boxes, locks);
|
|
1294
|
+
}
|
|
1292
1295
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
1293
1296
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
1294
1297
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -1302,6 +1305,13 @@ function applyLayoutConstraints(input) {
|
|
|
1302
1305
|
);
|
|
1303
1306
|
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
1304
1307
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1308
|
+
if (input.distributeContainedChildren) {
|
|
1309
|
+
const diagBefore = diagnostics.length;
|
|
1310
|
+
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
1311
|
+
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
1312
|
+
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
1313
|
+
}
|
|
1314
|
+
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
1305
1315
|
reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
|
|
1306
1316
|
reportIntraContainerOverflow(input, boxes, diagnostics);
|
|
1307
1317
|
return { boxes, locks, diagnostics };
|
|
@@ -1347,6 +1357,62 @@ function applyFixedPositionLocks(nodes, boxes, locks, diagnostics) {
|
|
|
1347
1357
|
locks.set(node.id, { nodeId: node.id, source: "fixed-position" });
|
|
1348
1358
|
}
|
|
1349
1359
|
}
|
|
1360
|
+
function dedupReplayDiagnostics(diagnostics, keepUpTo) {
|
|
1361
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1362
|
+
for (let i = 0; i < keepUpTo && i < diagnostics.length; i += 1) {
|
|
1363
|
+
const d = diagnostics[i];
|
|
1364
|
+
if (d === void 0) continue;
|
|
1365
|
+
seen.add(diagnosticFingerprint(d));
|
|
1366
|
+
}
|
|
1367
|
+
for (let i = diagnostics.length - 1; i >= keepUpTo; i -= 1) {
|
|
1368
|
+
const d = diagnostics[i];
|
|
1369
|
+
if (d === void 0) continue;
|
|
1370
|
+
const fp = diagnosticFingerprint(d);
|
|
1371
|
+
if (seen.has(fp)) {
|
|
1372
|
+
diagnostics.splice(i, 1);
|
|
1373
|
+
} else {
|
|
1374
|
+
seen.add(fp);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
function diagnosticFingerprint(d) {
|
|
1379
|
+
const nodeId = typeof d.detail?.nodeId === "string" ? d.detail.nodeId : "";
|
|
1380
|
+
const containerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : "";
|
|
1381
|
+
return `${d.code}|${nodeId}|${containerId}`;
|
|
1382
|
+
}
|
|
1383
|
+
function yieldFixedPositionLocks(input, boxes, locks) {
|
|
1384
|
+
for (const c of input.constraints) {
|
|
1385
|
+
if (c.kind !== "containment") continue;
|
|
1386
|
+
const container = boxes.get(c.containerId);
|
|
1387
|
+
if (container === void 0) continue;
|
|
1388
|
+
const content = contentBox(container, c.padding);
|
|
1389
|
+
const mainAxis = input.direction === "LR" || input.direction === "RL" ? "width" : "height";
|
|
1390
|
+
const crossAxis = mainAxis === "width" ? "height" : "width";
|
|
1391
|
+
let eligible = 0;
|
|
1392
|
+
for (const childId of c.childIds) {
|
|
1393
|
+
const box = boxes.get(childId);
|
|
1394
|
+
if (box === void 0) continue;
|
|
1395
|
+
const lock = locks.get(childId);
|
|
1396
|
+
if (lock?.source === "exact-position") continue;
|
|
1397
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
1398
|
+
if (fits) {
|
|
1399
|
+
eligible += 1;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (eligible < 2) continue;
|
|
1403
|
+
for (const childId of c.childIds) {
|
|
1404
|
+
const lock = locks.get(childId);
|
|
1405
|
+
if (lock?.source === "fixed-position") {
|
|
1406
|
+
const box = boxes.get(childId);
|
|
1407
|
+
if (box === void 0) continue;
|
|
1408
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
1409
|
+
if (fits) {
|
|
1410
|
+
locks.delete(childId);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1350
1416
|
function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
|
|
1351
1417
|
for (const constraint of constraints) {
|
|
1352
1418
|
if (constraint.kind !== "exact-position") {
|
|
@@ -1419,7 +1485,7 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
|
|
|
1419
1485
|
code: "constraints.locked-target-not-moved",
|
|
1420
1486
|
message: `Locked child ${childId} was not moved into containment.`,
|
|
1421
1487
|
path: ["constraints", constraint.id ?? constraint.containerId],
|
|
1422
|
-
detail: { nodeId: childId }
|
|
1488
|
+
detail: { nodeId: childId, containerId: constraint.containerId }
|
|
1423
1489
|
});
|
|
1424
1490
|
if (!isInside(child, content)) {
|
|
1425
1491
|
diagnostics.push({
|
|
@@ -1571,6 +1637,60 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
|
|
|
1571
1637
|
}
|
|
1572
1638
|
reportOverlaps(boxes, diagnostics, ignoredPairs);
|
|
1573
1639
|
}
|
|
1640
|
+
function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
|
|
1641
|
+
for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
|
|
1642
|
+
const d = diagnostics[i];
|
|
1643
|
+
if (d === void 0) continue;
|
|
1644
|
+
if (d.code === "constraints.overlap.unresolved") {
|
|
1645
|
+
const aId = d.detail?.firstId;
|
|
1646
|
+
const bId = d.detail?.secondId;
|
|
1647
|
+
if (typeof aId !== "string" || typeof bId !== "string") continue;
|
|
1648
|
+
const a = boxes.get(aId);
|
|
1649
|
+
const b = boxes.get(bId);
|
|
1650
|
+
if (a !== void 0 && b !== void 0 && !intersectsAabb(a, b)) {
|
|
1651
|
+
diagnostics.splice(i, 1);
|
|
1652
|
+
}
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (d.code === "constraints.containment.impossible" || d.code === "constraints.locked-target-not-moved" && typeof d.message === "string" && d.message.includes("not moved into containment")) {
|
|
1656
|
+
const nodeId = d.detail?.nodeId;
|
|
1657
|
+
if (typeof nodeId !== "string") continue;
|
|
1658
|
+
const child = boxes.get(nodeId);
|
|
1659
|
+
if (child === void 0) continue;
|
|
1660
|
+
const diagContainerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : void 0;
|
|
1661
|
+
let resolved = false;
|
|
1662
|
+
for (const c of constraints) {
|
|
1663
|
+
if (c.kind !== "containment") continue;
|
|
1664
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
1665
|
+
if (diagContainerId !== void 0 && c.containerId !== diagContainerId) {
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
const container = boxes.get(c.containerId);
|
|
1669
|
+
if (container === void 0) continue;
|
|
1670
|
+
const content = contentBox(container, c.padding);
|
|
1671
|
+
if (isInside(child, content)) {
|
|
1672
|
+
diagnostics.splice(i, 1);
|
|
1673
|
+
resolved = true;
|
|
1674
|
+
}
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
if (!resolved && diagContainerId !== void 0) {
|
|
1678
|
+
for (const c of constraints) {
|
|
1679
|
+
if (c.kind !== "containment") continue;
|
|
1680
|
+
if (c.containerId !== diagContainerId) continue;
|
|
1681
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
1682
|
+
const container = boxes.get(c.containerId);
|
|
1683
|
+
if (container === void 0) continue;
|
|
1684
|
+
const content = contentBox(container, c.padding);
|
|
1685
|
+
if (isInside(child, content)) {
|
|
1686
|
+
diagnostics.splice(i, 1);
|
|
1687
|
+
}
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1574
1694
|
function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
|
|
1575
1695
|
const ids = [...boxes.keys()].sort();
|
|
1576
1696
|
const reported = new Set(
|
|
@@ -1878,6 +1998,11 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1878
1998
|
continue;
|
|
1879
1999
|
}
|
|
1880
2000
|
if (locks.has(childId)) {
|
|
2001
|
+
const lock = locks.get(childId);
|
|
2002
|
+
if (lock?.source === "fixed-position") {
|
|
2003
|
+
unlocked.push({ id: childId, box });
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
1881
2006
|
diagnostics.push({
|
|
1882
2007
|
severity: "warning",
|
|
1883
2008
|
code: "constraints.locked-target-not-moved",
|
|
@@ -1936,6 +2061,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1936
2061
|
});
|
|
1937
2062
|
}
|
|
1938
2063
|
boxes.set(child.id, clamped);
|
|
2064
|
+
locks.delete(child.id);
|
|
1939
2065
|
pos = clamped[axis] + clamped[mainSize] + minGap;
|
|
1940
2066
|
}
|
|
1941
2067
|
diagnostics.push({
|
|
@@ -3251,6 +3377,213 @@ function isValidDimension(value) {
|
|
|
3251
3377
|
return Number.isFinite(value) && value >= 0;
|
|
3252
3378
|
}
|
|
3253
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
|
+
|
|
3254
3587
|
// src/routing/routes.ts
|
|
3255
3588
|
function routeEdge(input) {
|
|
3256
3589
|
const diagnostics = [];
|
|
@@ -3296,6 +3629,43 @@ function routeEdge(input) {
|
|
|
3296
3629
|
}
|
|
3297
3630
|
return { points, diagnostics };
|
|
3298
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
|
+
}
|
|
3299
3669
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3300
3670
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3301
3671
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -3385,7 +3755,7 @@ function routeEdge(input) {
|
|
|
3385
3755
|
const rerouted = greedyRerouteAroundObstacles(
|
|
3386
3756
|
bestPoints2,
|
|
3387
3757
|
allObstacles,
|
|
3388
|
-
|
|
3758
|
+
maxAttempts
|
|
3389
3759
|
);
|
|
3390
3760
|
const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
|
|
3391
3761
|
rerouted,
|
|
@@ -3498,7 +3868,7 @@ function routeEdge(input) {
|
|
|
3498
3868
|
};
|
|
3499
3869
|
}
|
|
3500
3870
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
3501
|
-
const simplified =
|
|
3871
|
+
const simplified = simplifyRoute2(points);
|
|
3502
3872
|
if (simplified.length >= 3) {
|
|
3503
3873
|
return simplified;
|
|
3504
3874
|
}
|
|
@@ -3786,7 +4156,7 @@ function squaredDistance2(a, b) {
|
|
|
3786
4156
|
const dy = a.y - b.y;
|
|
3787
4157
|
return dx * dx + dy * dy;
|
|
3788
4158
|
}
|
|
3789
|
-
function
|
|
4159
|
+
function simplifyRoute2(points) {
|
|
3790
4160
|
const withoutDuplicates = [];
|
|
3791
4161
|
for (const point2 of points) {
|
|
3792
4162
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -3798,7 +4168,7 @@ function simplifyRoute(points) {
|
|
|
3798
4168
|
for (const point2 of withoutDuplicates) {
|
|
3799
4169
|
const previous = simplified.at(-1);
|
|
3800
4170
|
const beforePrevious = simplified.at(-2);
|
|
3801
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4171
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
3802
4172
|
simplified[simplified.length - 1] = { ...point2 };
|
|
3803
4173
|
} else {
|
|
3804
4174
|
simplified.push({ ...point2 });
|
|
@@ -4013,17 +4383,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4013
4383
|
return true;
|
|
4014
4384
|
}
|
|
4015
4385
|
if (start.x === end.x) {
|
|
4016
|
-
return start.x > left && start.x < right &&
|
|
4386
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4017
4387
|
}
|
|
4018
4388
|
if (start.y === end.y) {
|
|
4019
|
-
return start.y > top && start.y < bottom &&
|
|
4389
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4020
4390
|
}
|
|
4021
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);
|
|
4022
4392
|
}
|
|
4023
4393
|
function pointInsideBox(point2, box) {
|
|
4024
4394
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4025
4395
|
}
|
|
4026
|
-
function
|
|
4396
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4027
4397
|
const low = Math.min(a, b);
|
|
4028
4398
|
const high = Math.max(a, b);
|
|
4029
4399
|
return high > min && low < max;
|
|
@@ -4047,7 +4417,7 @@ function segmentBox(a, b) {
|
|
|
4047
4417
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4048
4418
|
};
|
|
4049
4419
|
}
|
|
4050
|
-
function
|
|
4420
|
+
function areCollinear2(a, b, c) {
|
|
4051
4421
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4052
4422
|
}
|
|
4053
4423
|
|
|
@@ -4127,6 +4497,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4127
4497
|
options,
|
|
4128
4498
|
diagnostics
|
|
4129
4499
|
);
|
|
4500
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4130
4501
|
const constrained = applyLayoutConstraints({
|
|
4131
4502
|
direction: diagram.direction,
|
|
4132
4503
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4147,9 +4518,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4147
4518
|
options?.overlapSpacing ?? 40,
|
|
4148
4519
|
Math.max(0, options?.minLaneGutter ?? 0)
|
|
4149
4520
|
);
|
|
4150
|
-
|
|
4151
|
-
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4152
|
-
}
|
|
4521
|
+
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4153
4522
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
4154
4523
|
const coordinatedNodes = coordinateNodes(
|
|
4155
4524
|
styledNodes,
|
|
@@ -4192,6 +4561,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4192
4561
|
swimlanes: coordinatedSwimlanes,
|
|
4193
4562
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4194
4563
|
});
|
|
4564
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
4565
|
+
styledEdges,
|
|
4566
|
+
nodeGeometryById,
|
|
4567
|
+
options.textMeasurer
|
|
4568
|
+
);
|
|
4195
4569
|
const layoutBoxes = [
|
|
4196
4570
|
...coordinatedNodes.map((node) => node.box),
|
|
4197
4571
|
...coordinatedNodes.flatMap(
|
|
@@ -4272,7 +4646,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4272
4646
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4273
4647
|
const routingTextObstacles = [
|
|
4274
4648
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4275
|
-
...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
|
|
4276
4653
|
];
|
|
4277
4654
|
const margin = options.obstacleMargin ?? 0;
|
|
4278
4655
|
const softObstacles = [
|
|
@@ -4305,7 +4682,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4305
4682
|
hardObstacles,
|
|
4306
4683
|
diagram.direction,
|
|
4307
4684
|
options,
|
|
4308
|
-
diagnostics
|
|
4685
|
+
diagnostics,
|
|
4686
|
+
coordinatedGroups
|
|
4309
4687
|
);
|
|
4310
4688
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4311
4689
|
coordinatedEdges,
|
|
@@ -4418,22 +4796,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
4418
4796
|
y: layout2.box.y + offsetY,
|
|
4419
4797
|
width: layout2.box.width,
|
|
4420
4798
|
height: layout2.box.height
|
|
4421
|
-
}
|
|
4422
|
-
contentBox: {
|
|
4423
|
-
x: layout2.contentBox.x + offsetX,
|
|
4424
|
-
y: layout2.contentBox.y + offsetY,
|
|
4425
|
-
width: layout2.contentBox.width,
|
|
4426
|
-
height: layout2.contentBox.height
|
|
4427
|
-
},
|
|
4428
|
-
lines: layout2.lines.map((line) => ({
|
|
4429
|
-
...line,
|
|
4430
|
-
box: {
|
|
4431
|
-
x: line.box.x + offsetX,
|
|
4432
|
-
y: line.box.y + offsetY,
|
|
4433
|
-
width: line.box.width,
|
|
4434
|
-
height: line.box.height
|
|
4435
|
-
}
|
|
4436
|
-
}))
|
|
4799
|
+
}
|
|
4437
4800
|
};
|
|
4438
4801
|
}
|
|
4439
4802
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -5380,6 +5743,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5380
5743
|
});
|
|
5381
5744
|
continue;
|
|
5382
5745
|
}
|
|
5746
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
5383
5747
|
const geometry = computeShapeGeometry({
|
|
5384
5748
|
shape: node.shape,
|
|
5385
5749
|
box,
|
|
@@ -5389,7 +5753,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5389
5753
|
id: node.id,
|
|
5390
5754
|
...node.label === void 0 ? {} : { label: node.label },
|
|
5391
5755
|
...node.style === void 0 ? {} : { style: node.style },
|
|
5392
|
-
...
|
|
5756
|
+
...ports === void 0 ? {} : { ports },
|
|
5393
5757
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
5394
5758
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
5395
5759
|
shape: node.shape,
|
|
@@ -5401,6 +5765,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5401
5765
|
}
|
|
5402
5766
|
return coordinated;
|
|
5403
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
|
+
}
|
|
5404
5840
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
5405
5841
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
5406
5842
|
for (const port of node.ports ?? []) {
|
|
@@ -5437,7 +5873,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5437
5873
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
5438
5874
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
5439
5875
|
const availableSpan = 2 * maxOffset;
|
|
5440
|
-
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;
|
|
5441
5881
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
5442
5882
|
switch (side) {
|
|
5443
5883
|
case "left":
|
|
@@ -5463,7 +5903,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
5463
5903
|
}
|
|
5464
5904
|
}
|
|
5465
5905
|
function portBox(anchor) {
|
|
5466
|
-
const size =
|
|
5906
|
+
const size = PORT_BOX_SIZE;
|
|
5467
5907
|
return {
|
|
5468
5908
|
x: anchor.x - size / 2,
|
|
5469
5909
|
y: anchor.y - size / 2,
|
|
@@ -6052,7 +6492,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6052
6492
|
}
|
|
6053
6493
|
};
|
|
6054
6494
|
}
|
|
6055
|
-
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) {
|
|
6056
6496
|
const coordinated = [];
|
|
6057
6497
|
const coordinatedNodeById = new Map(
|
|
6058
6498
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6076,8 +6516,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6076
6516
|
}
|
|
6077
6517
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6078
6518
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6079
|
-
const
|
|
6080
|
-
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);
|
|
6081
6520
|
const route = routeEdge({
|
|
6082
6521
|
kind: options.routeKind ?? "orthogonal",
|
|
6083
6522
|
direction,
|
|
@@ -6090,9 +6529,11 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6090
6529
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6091
6530
|
),
|
|
6092
6531
|
...softObstacles,
|
|
6532
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6093
6533
|
...routeTextObstacles
|
|
6094
6534
|
],
|
|
6095
|
-
hardObstacles
|
|
6535
|
+
hardObstacles,
|
|
6536
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
|
|
6096
6537
|
});
|
|
6097
6538
|
diagnostics.push(
|
|
6098
6539
|
...route.diagnostics.map((diagnostic) => ({
|
|
@@ -6107,15 +6548,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6107
6548
|
}
|
|
6108
6549
|
return coordinated;
|
|
6109
6550
|
}
|
|
6110
|
-
function
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
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;
|
|
6114
6564
|
}
|
|
6115
|
-
|
|
6116
|
-
|
|
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
|
+
}
|
|
6117
6585
|
}
|
|
6118
|
-
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));
|
|
6119
6597
|
}
|
|
6120
6598
|
function coordinateBaseTextAnnotations(input) {
|
|
6121
6599
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -6303,6 +6781,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
6303
6781
|
}
|
|
6304
6782
|
return annotations;
|
|
6305
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
|
+
}
|
|
6306
6833
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
6307
6834
|
const layout2 = fitLabel(
|
|
6308
6835
|
frame.titleTab,
|
|
@@ -6423,9 +6950,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6423
6950
|
const diagnostics = [];
|
|
6424
6951
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
6425
6952
|
for (const edge of edges) {
|
|
6426
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
6427
6953
|
for (const annotation of relevantAnnotations) {
|
|
6428
|
-
if (
|
|
6954
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
6429
6955
|
continue;
|
|
6430
6956
|
}
|
|
6431
6957
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -6449,9 +6975,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
6449
6975
|
return diagnostics;
|
|
6450
6976
|
}
|
|
6451
6977
|
function isPreRouteTextObstacle(annotation) {
|
|
6452
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
6453
|
-
return false;
|
|
6454
|
-
}
|
|
6455
6978
|
return isRouteClearanceText(annotation);
|
|
6456
6979
|
}
|
|
6457
6980
|
function isRouteClearanceText(annotation) {
|
|
@@ -6462,8 +6985,9 @@ function isRouteClearanceText(annotation) {
|
|
|
6462
6985
|
case "frame-title":
|
|
6463
6986
|
return true;
|
|
6464
6987
|
case "node-label":
|
|
6465
|
-
case "group-label":
|
|
6466
6988
|
case "compartment-row":
|
|
6989
|
+
return true;
|
|
6990
|
+
case "group-label":
|
|
6467
6991
|
return textExtendsOutsideAnchor(annotation);
|
|
6468
6992
|
}
|
|
6469
6993
|
}
|
|
@@ -6496,17 +7020,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
6496
7020
|
return true;
|
|
6497
7021
|
}
|
|
6498
7022
|
if (start.x === end.x) {
|
|
6499
|
-
return start.x > left && start.x < right &&
|
|
7023
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
6500
7024
|
}
|
|
6501
7025
|
if (start.y === end.y) {
|
|
6502
|
-
return start.y > top && start.y < bottom &&
|
|
7026
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
6503
7027
|
}
|
|
6504
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);
|
|
6505
7029
|
}
|
|
6506
7030
|
function pointInsideBox2(point2, box) {
|
|
6507
7031
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
6508
7032
|
}
|
|
6509
|
-
function
|
|
7033
|
+
function rangesOverlap3(a, b, min, max) {
|
|
6510
7034
|
const low = Math.min(a, b);
|
|
6511
7035
|
const high = Math.max(a, b);
|
|
6512
7036
|
return high > min && low < max;
|
|
@@ -6575,7 +7099,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
6575
7099
|
for (const candidate of edgeLabelAnchorCandidates(
|
|
6576
7100
|
edge.points,
|
|
6577
7101
|
placement,
|
|
6578
|
-
layout2
|
|
7102
|
+
layout2,
|
|
7103
|
+
baseOffset
|
|
6579
7104
|
)) {
|
|
6580
7105
|
const labelBox = {
|
|
6581
7106
|
x: candidate.x - layout2.box.width / 2,
|
|
@@ -6607,8 +7132,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
6607
7132
|
}
|
|
6608
7133
|
return placement;
|
|
6609
7134
|
}
|
|
6610
|
-
function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
6611
|
-
const segment = labelSegmentOnPolyline(points);
|
|
7135
|
+
function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
|
|
7136
|
+
const segment = labelSegmentOnPolyline(points, baseOffset);
|
|
6612
7137
|
if (segment === void 0) {
|
|
6613
7138
|
return [placement];
|
|
6614
7139
|
}
|
|
@@ -6658,7 +7183,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
6658
7183
|
}, 0);
|
|
6659
7184
|
if (totalLen > 200) {
|
|
6660
7185
|
for (const ratio of [0.25, 0.75]) {
|
|
6661
|
-
const qp = labelPlacementAtRatio(points, ratio, totalLen);
|
|
7186
|
+
const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
|
|
6662
7187
|
if (qp !== void 0) {
|
|
6663
7188
|
candidates.push(qp);
|
|
6664
7189
|
const qTargetDist = totalLen * ratio;
|
|
@@ -6744,7 +7269,7 @@ function labelSegmentOnPolyline(points, baseOffset = 10) {
|
|
|
6744
7269
|
if (last === void 0) {
|
|
6745
7270
|
return void 0;
|
|
6746
7271
|
}
|
|
6747
|
-
const offset = labelOffset2(last);
|
|
7272
|
+
const offset = labelOffset2(last, baseOffset);
|
|
6748
7273
|
return {
|
|
6749
7274
|
start: last.start,
|
|
6750
7275
|
end: last.end,
|
|
@@ -6766,7 +7291,7 @@ function nonZeroSegments2(points) {
|
|
|
6766
7291
|
}
|
|
6767
7292
|
return segments;
|
|
6768
7293
|
}
|
|
6769
|
-
function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
7294
|
+
function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
|
|
6770
7295
|
if (points.length < 2 || ratio < 0 || ratio > 1) {
|
|
6771
7296
|
return void 0;
|
|
6772
7297
|
}
|
|
@@ -6784,7 +7309,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
|
6784
7309
|
}
|
|
6785
7310
|
if (travelled + segLen >= targetDist) {
|
|
6786
7311
|
const t = (targetDist - travelled) / segLen;
|
|
6787
|
-
const offset = labelOffset2(
|
|
7312
|
+
const offset = labelOffset2(
|
|
7313
|
+
{ start: prev, end: curr, length: segLen },
|
|
7314
|
+
baseOffset
|
|
7315
|
+
);
|
|
6788
7316
|
return {
|
|
6789
7317
|
x: prev.x + (curr.x - prev.x) * t + offset.x,
|
|
6790
7318
|
y: prev.y + (curr.y - prev.y) * t + offset.y
|