@crazyhappyone/auto-graph 0.1.1 → 0.1.2
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 +277 -10
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +277 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +277 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +277 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3351,6 +3351,51 @@ function routeEdge(input) {
|
|
|
3351
3351
|
)
|
|
3352
3352
|
);
|
|
3353
3353
|
if (hardClearCandidate !== void 0) {
|
|
3354
|
+
let bestPoints2 = hardClearCandidate.points;
|
|
3355
|
+
if (input.kind === "obstacle-avoiding") {
|
|
3356
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
3357
|
+
for (const candidate of candidateRoutes) {
|
|
3358
|
+
if (routeCrossesBoxes(candidate.points, hardObstacles) || routeIntersectsEndpointInteriors(
|
|
3359
|
+
candidate.points,
|
|
3360
|
+
candidate.endpointObstacles
|
|
3361
|
+
)) {
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
const rerouted2 = greedyRerouteAroundObstacles(
|
|
3365
|
+
candidate.points,
|
|
3366
|
+
allObstacles,
|
|
3367
|
+
3
|
|
3368
|
+
);
|
|
3369
|
+
if (!routeCrossesBoxes(rerouted2, allObstacles) && !routeIntersectsEndpointInteriors(
|
|
3370
|
+
rerouted2,
|
|
3371
|
+
candidate.endpointObstacles
|
|
3372
|
+
)) {
|
|
3373
|
+
return {
|
|
3374
|
+
points: finalizeRoute(
|
|
3375
|
+
rerouted2,
|
|
3376
|
+
softObstacles,
|
|
3377
|
+
hardObstacles,
|
|
3378
|
+
diagnostics
|
|
3379
|
+
),
|
|
3380
|
+
diagnostics
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
const rerouted = greedyRerouteAroundObstacles(
|
|
3385
|
+
bestPoints2,
|
|
3386
|
+
allObstacles,
|
|
3387
|
+
3
|
|
3388
|
+
);
|
|
3389
|
+
const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
|
|
3390
|
+
rerouted,
|
|
3391
|
+
hardClearCandidate.endpointObstacles
|
|
3392
|
+
);
|
|
3393
|
+
if (reroutedAvoidsEndpointInteriors) {
|
|
3394
|
+
if (routeCrossesBoxes(rerouted, hardObstacles) && !routeCrossesBoxes(bestPoints2, hardObstacles)) ; else {
|
|
3395
|
+
bestPoints2 = rerouted;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3354
3399
|
diagnostics.push({
|
|
3355
3400
|
severity: "warning",
|
|
3356
3401
|
code: "routing.obstacle.unavoidable",
|
|
@@ -3358,7 +3403,7 @@ function routeEdge(input) {
|
|
|
3358
3403
|
});
|
|
3359
3404
|
return {
|
|
3360
3405
|
points: finalizeRoute(
|
|
3361
|
-
|
|
3406
|
+
bestPoints2,
|
|
3362
3407
|
softObstacles,
|
|
3363
3408
|
hardObstacles,
|
|
3364
3409
|
diagnostics
|
|
@@ -3367,6 +3412,33 @@ function routeEdge(input) {
|
|
|
3367
3412
|
};
|
|
3368
3413
|
}
|
|
3369
3414
|
if (hardObstacles.length > 0) {
|
|
3415
|
+
let bestPoints2 = candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors);
|
|
3416
|
+
if (input.kind === "obstacle-avoiding") {
|
|
3417
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
3418
|
+
for (const candidate of candidateRoutes) {
|
|
3419
|
+
const rerouted = greedyRerouteAroundObstacles(
|
|
3420
|
+
candidate.points,
|
|
3421
|
+
allObstacles,
|
|
3422
|
+
5
|
|
3423
|
+
);
|
|
3424
|
+
if (!routeCrossesBoxes(rerouted, allObstacles)) {
|
|
3425
|
+
return {
|
|
3426
|
+
points: finalizeRoute(
|
|
3427
|
+
rerouted,
|
|
3428
|
+
softObstacles,
|
|
3429
|
+
hardObstacles,
|
|
3430
|
+
diagnostics
|
|
3431
|
+
),
|
|
3432
|
+
diagnostics
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
bestPoints2 = greedyRerouteAroundObstacles(
|
|
3437
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
|
|
3438
|
+
allObstacles,
|
|
3439
|
+
5
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3370
3442
|
diagnostics.push({
|
|
3371
3443
|
severity: "error",
|
|
3372
3444
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -3374,7 +3446,7 @@ function routeEdge(input) {
|
|
|
3374
3446
|
});
|
|
3375
3447
|
return {
|
|
3376
3448
|
points: finalizeRoute(
|
|
3377
|
-
|
|
3449
|
+
bestPoints2,
|
|
3378
3450
|
softObstacles,
|
|
3379
3451
|
hardObstacles,
|
|
3380
3452
|
diagnostics
|
|
@@ -3382,6 +3454,33 @@ function routeEdge(input) {
|
|
|
3382
3454
|
diagnostics
|
|
3383
3455
|
};
|
|
3384
3456
|
}
|
|
3457
|
+
let bestPoints = candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors);
|
|
3458
|
+
if (input.kind === "obstacle-avoiding") {
|
|
3459
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
3460
|
+
for (const candidate of candidateRoutes) {
|
|
3461
|
+
const rerouted = greedyRerouteAroundObstacles(
|
|
3462
|
+
candidate.points,
|
|
3463
|
+
allObstacles,
|
|
3464
|
+
5
|
|
3465
|
+
);
|
|
3466
|
+
if (!routeCrossesBoxes(rerouted, allObstacles)) {
|
|
3467
|
+
return {
|
|
3468
|
+
points: finalizeRoute(
|
|
3469
|
+
rerouted,
|
|
3470
|
+
softObstacles,
|
|
3471
|
+
hardObstacles,
|
|
3472
|
+
diagnostics
|
|
3473
|
+
),
|
|
3474
|
+
diagnostics
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
bestPoints = greedyRerouteAroundObstacles(
|
|
3479
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
|
|
3480
|
+
allObstacles,
|
|
3481
|
+
5
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3385
3484
|
diagnostics.push({
|
|
3386
3485
|
severity: "warning",
|
|
3387
3486
|
code: "routing.obstacle.unavoidable",
|
|
@@ -3389,7 +3488,7 @@ function routeEdge(input) {
|
|
|
3389
3488
|
});
|
|
3390
3489
|
return {
|
|
3391
3490
|
points: finalizeRoute(
|
|
3392
|
-
|
|
3491
|
+
bestPoints,
|
|
3393
3492
|
softObstacles,
|
|
3394
3493
|
hardObstacles,
|
|
3395
3494
|
diagnostics
|
|
@@ -3547,6 +3646,70 @@ function insetBox(box, margin) {
|
|
|
3547
3646
|
height: box.height - margin * 2
|
|
3548
3647
|
};
|
|
3549
3648
|
}
|
|
3649
|
+
function greedyRerouteAroundObstacles(points, obstacles, maxIterations) {
|
|
3650
|
+
let current = [...points];
|
|
3651
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
3652
|
+
const improved = pushRouteAwayFromObstacles(current, obstacles);
|
|
3653
|
+
if (improved === null) {
|
|
3654
|
+
break;
|
|
3655
|
+
}
|
|
3656
|
+
current = improved;
|
|
3657
|
+
if (!routeCrossesBoxes(current, obstacles)) {
|
|
3658
|
+
break;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
return current;
|
|
3662
|
+
}
|
|
3663
|
+
function pushRouteAwayFromObstacles(points, obstacles) {
|
|
3664
|
+
const result = [];
|
|
3665
|
+
let improved = false;
|
|
3666
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
3667
|
+
const a = points[i];
|
|
3668
|
+
const b = points[i + 1];
|
|
3669
|
+
if (a === void 0 || b === void 0) {
|
|
3670
|
+
result.push(a ?? b ?? { x: 0, y: 0 });
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
result.push(a);
|
|
3674
|
+
const intersectors = obstacles.filter(
|
|
3675
|
+
(obs) => segmentIntersectsBox(a, b, obs)
|
|
3676
|
+
);
|
|
3677
|
+
if (intersectors.length === 0) {
|
|
3678
|
+
continue;
|
|
3679
|
+
}
|
|
3680
|
+
const mx = (a.x + b.x) / 2;
|
|
3681
|
+
const my = (a.y + b.y) / 2;
|
|
3682
|
+
const isHorizontal = a.y === b.y;
|
|
3683
|
+
const margin = 12;
|
|
3684
|
+
let bestWaypoint = null;
|
|
3685
|
+
let bestDist = Infinity;
|
|
3686
|
+
for (const obs of intersectors) {
|
|
3687
|
+
const candidates = isHorizontal ? [
|
|
3688
|
+
{ x: mx, y: obs.y - margin },
|
|
3689
|
+
{ x: mx, y: obs.y + obs.height + margin }
|
|
3690
|
+
] : [
|
|
3691
|
+
{ x: obs.x - margin, y: my },
|
|
3692
|
+
{ x: obs.x + obs.width + margin, y: my }
|
|
3693
|
+
];
|
|
3694
|
+
for (const wp of candidates) {
|
|
3695
|
+
const dist = Math.hypot(wp.x - mx, wp.y - my);
|
|
3696
|
+
if (dist < bestDist) {
|
|
3697
|
+
bestDist = dist;
|
|
3698
|
+
bestWaypoint = wp;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
if (bestWaypoint !== null) {
|
|
3703
|
+
result.push(bestWaypoint);
|
|
3704
|
+
improved = true;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
const last = points[points.length - 1];
|
|
3708
|
+
if (last !== void 0) {
|
|
3709
|
+
result.push(last);
|
|
3710
|
+
}
|
|
3711
|
+
return improved ? result : null;
|
|
3712
|
+
}
|
|
3550
3713
|
function fallbackRoute(input, defaultAnchors) {
|
|
3551
3714
|
return [
|
|
3552
3715
|
getEdgePort(
|
|
@@ -4133,7 +4296,9 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4133
4296
|
styledEdges,
|
|
4134
4297
|
nodeGeometryById,
|
|
4135
4298
|
coordinatedNodes,
|
|
4136
|
-
[...nodeGeometryById.values()].map(
|
|
4299
|
+
[...nodeGeometryById.values()].map(
|
|
4300
|
+
(geometry) => options.routingGutter === void 0 ? geometry.obstacleBox : expandBox(geometry.obstacleBox, options.routingGutter)
|
|
4301
|
+
),
|
|
4137
4302
|
[...softObstacles, ...titleBarObstacles],
|
|
4138
4303
|
routingTextObstacles,
|
|
4139
4304
|
hardObstacles,
|
|
@@ -6453,9 +6618,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
6453
6618
|
{ x: placement.x, y: placement.y + offset }
|
|
6454
6619
|
);
|
|
6455
6620
|
}
|
|
6456
|
-
|
|
6457
|
-
}
|
|
6458
|
-
if (segment.start.x === segment.end.x) {
|
|
6621
|
+
} else if (segment.start.x === segment.end.x) {
|
|
6459
6622
|
const needed = layout2.box.width / 2 + EDGE_LABEL_CLEARANCE;
|
|
6460
6623
|
const maxSteps = Math.max(12, Math.ceil(needed / EDGE_LABEL_CLEARANCE));
|
|
6461
6624
|
for (let step = 1; step <= maxSteps; step += 1) {
|
|
@@ -6465,7 +6628,83 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
6465
6628
|
{ x: placement.x - offset, y: placement.y }
|
|
6466
6629
|
);
|
|
6467
6630
|
}
|
|
6468
|
-
|
|
6631
|
+
} else {
|
|
6632
|
+
const dx = segment.end.x - segment.start.x;
|
|
6633
|
+
const dy = segment.end.y - segment.start.y;
|
|
6634
|
+
const segLen = Math.hypot(dx, dy);
|
|
6635
|
+
if (segLen > 0) {
|
|
6636
|
+
const nx = -dy / segLen;
|
|
6637
|
+
const ny = dx / segLen;
|
|
6638
|
+
const needed = (Math.abs(nx) * layout2.box.width + Math.abs(ny) * layout2.box.height) / 2 + EDGE_LABEL_CLEARANCE;
|
|
6639
|
+
const maxSteps = Math.max(12, Math.ceil(needed / EDGE_LABEL_CLEARANCE));
|
|
6640
|
+
for (let step = 1; step <= maxSteps; step += 1) {
|
|
6641
|
+
const offset = EDGE_LABEL_CLEARANCE * step;
|
|
6642
|
+
candidates.push(
|
|
6643
|
+
{ x: placement.x + nx * offset, y: placement.y + ny * offset },
|
|
6644
|
+
{ x: placement.x - nx * offset, y: placement.y - ny * offset }
|
|
6645
|
+
);
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
const totalLen = points.reduce((sum, p, idx) => {
|
|
6650
|
+
if (idx === 0) return 0;
|
|
6651
|
+
const prev = points[idx - 1];
|
|
6652
|
+
return sum + Math.hypot((p?.x ?? 0) - (prev?.x ?? 0), (p?.y ?? 0) - (prev?.y ?? 0));
|
|
6653
|
+
}, 0);
|
|
6654
|
+
if (totalLen > 200) {
|
|
6655
|
+
for (const ratio of [0.25, 0.75]) {
|
|
6656
|
+
const qp = labelPlacementAtRatio(points, ratio, totalLen);
|
|
6657
|
+
if (qp !== void 0) {
|
|
6658
|
+
candidates.push(qp);
|
|
6659
|
+
const qTargetDist = totalLen * ratio;
|
|
6660
|
+
let qTravelled = 0;
|
|
6661
|
+
let seg;
|
|
6662
|
+
for (let si = 1; si < points.length; si++) {
|
|
6663
|
+
const sp = points[si - 1];
|
|
6664
|
+
const sc = points[si];
|
|
6665
|
+
if (sp === void 0 || sc === void 0) continue;
|
|
6666
|
+
const sl = Math.hypot(sc.x - sp.x, sc.y - sp.y);
|
|
6667
|
+
if (sl <= 0) continue;
|
|
6668
|
+
if (qTravelled + sl >= qTargetDist) {
|
|
6669
|
+
seg = { start: sp, end: sc, length: sl };
|
|
6670
|
+
break;
|
|
6671
|
+
}
|
|
6672
|
+
qTravelled += sl;
|
|
6673
|
+
}
|
|
6674
|
+
if (seg !== void 0) {
|
|
6675
|
+
const segLen = Math.hypot(
|
|
6676
|
+
seg.end.x - seg.start.x,
|
|
6677
|
+
seg.end.y - seg.start.y
|
|
6678
|
+
);
|
|
6679
|
+
const qpNeeded = seg.start.y === seg.end.y ? layout2.box.height / 2 + EDGE_LABEL_CLEARANCE : seg.start.x === seg.end.x ? layout2.box.width / 2 + EDGE_LABEL_CLEARANCE : (Math.abs(seg.start.y - seg.end.y) * layout2.box.width + Math.abs(seg.end.x - seg.start.x) * layout2.box.height) / (2 * segLen) + EDGE_LABEL_CLEARANCE;
|
|
6680
|
+
const qpMaxSteps = Math.max(
|
|
6681
|
+
12,
|
|
6682
|
+
Math.ceil(qpNeeded / EDGE_LABEL_CLEARANCE)
|
|
6683
|
+
);
|
|
6684
|
+
for (let step = 1; step <= qpMaxSteps; step += 1) {
|
|
6685
|
+
const offset = EDGE_LABEL_CLEARANCE * step;
|
|
6686
|
+
if (seg.start.y === seg.end.y) {
|
|
6687
|
+
candidates.push(
|
|
6688
|
+
{ x: qp.x, y: qp.y - offset },
|
|
6689
|
+
{ x: qp.x, y: qp.y + offset }
|
|
6690
|
+
);
|
|
6691
|
+
} else if (seg.start.x === seg.end.x) {
|
|
6692
|
+
candidates.push(
|
|
6693
|
+
{ x: qp.x - offset, y: qp.y },
|
|
6694
|
+
{ x: qp.x + offset, y: qp.y }
|
|
6695
|
+
);
|
|
6696
|
+
} else {
|
|
6697
|
+
const nx = -(seg.end.y - seg.start.y) / segLen;
|
|
6698
|
+
const ny = (seg.end.x - seg.start.x) / segLen;
|
|
6699
|
+
candidates.push(
|
|
6700
|
+
{ x: qp.x + nx * offset, y: qp.y + ny * offset },
|
|
6701
|
+
{ x: qp.x - nx * offset, y: qp.y - ny * offset }
|
|
6702
|
+
);
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6469
6708
|
}
|
|
6470
6709
|
return candidates;
|
|
6471
6710
|
}
|
|
@@ -6522,6 +6761,34 @@ function nonZeroSegments2(points) {
|
|
|
6522
6761
|
}
|
|
6523
6762
|
return segments;
|
|
6524
6763
|
}
|
|
6764
|
+
function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
6765
|
+
if (points.length < 2 || ratio < 0 || ratio > 1) {
|
|
6766
|
+
return void 0;
|
|
6767
|
+
}
|
|
6768
|
+
const targetDist = totalLength * ratio;
|
|
6769
|
+
let travelled = 0;
|
|
6770
|
+
for (let idx = 1; idx < points.length; idx++) {
|
|
6771
|
+
const prev = points[idx - 1];
|
|
6772
|
+
const curr = points[idx];
|
|
6773
|
+
if (prev === void 0 || curr === void 0) {
|
|
6774
|
+
continue;
|
|
6775
|
+
}
|
|
6776
|
+
const segLen = Math.hypot(curr.x - prev.x, curr.y - prev.y);
|
|
6777
|
+
if (segLen <= 0) {
|
|
6778
|
+
continue;
|
|
6779
|
+
}
|
|
6780
|
+
if (travelled + segLen >= targetDist) {
|
|
6781
|
+
const t = (targetDist - travelled) / segLen;
|
|
6782
|
+
const offset = labelOffset2({ start: prev, end: curr, length: segLen });
|
|
6783
|
+
return {
|
|
6784
|
+
x: prev.x + (curr.x - prev.x) * t + offset.x,
|
|
6785
|
+
y: prev.y + (curr.y - prev.y) * t + offset.y
|
|
6786
|
+
};
|
|
6787
|
+
}
|
|
6788
|
+
travelled += segLen;
|
|
6789
|
+
}
|
|
6790
|
+
return void 0;
|
|
6791
|
+
}
|
|
6525
6792
|
function labelOffset2(segment) {
|
|
6526
6793
|
const offset = 10;
|
|
6527
6794
|
const dx = segment.end.x - segment.start.x;
|
|
@@ -6635,7 +6902,7 @@ function isValidEdgeId(value) {
|
|
|
6635
6902
|
return value.length > 0 && EDGE_ID_PATTERN.test(value);
|
|
6636
6903
|
}
|
|
6637
6904
|
var directionSchema = z.enum(["TB", "LR", "BT", "RL"]);
|
|
6638
|
-
var routeKindSchema = z.enum(["orthogonal", "straight"]);
|
|
6905
|
+
var routeKindSchema = z.enum(["orthogonal", "straight", "obstacle-avoiding"]);
|
|
6639
6906
|
var outputFormatSchema = z.enum(["svg", "excalidraw"]);
|
|
6640
6907
|
var edgeStrokeStyleSchema = z.enum(["solid", "dashed"]);
|
|
6641
6908
|
var edgeArrowheadSchema = z.enum(["triangle", "hollowTriangle"]);
|
|
@@ -7234,7 +7501,7 @@ function renderDiagramDsl(source, options = {}) {
|
|
|
7234
7501
|
return { diagnostics };
|
|
7235
7502
|
}
|
|
7236
7503
|
const solved = solveDiagram(normalized.diagram, {
|
|
7237
|
-
routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
|
|
7504
|
+
routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : normalized.diagram.metadata?.routeKind === "obstacle-avoiding" ? "obstacle-avoiding" : "orthogonal",
|
|
7238
7505
|
...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
|
|
7239
7506
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
7240
7507
|
});
|