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