@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/index.cjs
CHANGED
|
@@ -2071,7 +2071,7 @@ function point(value) {
|
|
|
2071
2071
|
return { x: value.x, y: value.y };
|
|
2072
2072
|
}
|
|
2073
2073
|
var directionSchema = zod.z.enum(["TB", "LR", "BT", "RL"]);
|
|
2074
|
-
var routeKindSchema = zod.z.enum(["orthogonal", "straight"]);
|
|
2074
|
+
var routeKindSchema = zod.z.enum(["orthogonal", "straight", "obstacle-avoiding"]);
|
|
2075
2075
|
var outputFormatSchema = zod.z.enum(["svg", "excalidraw"]);
|
|
2076
2076
|
var edgeStrokeStyleSchema = zod.z.enum(["solid", "dashed"]);
|
|
2077
2077
|
var edgeArrowheadSchema = zod.z.enum(["triangle", "hollowTriangle"]);
|
|
@@ -3957,6 +3957,51 @@ function routeEdge(input) {
|
|
|
3957
3957
|
)
|
|
3958
3958
|
);
|
|
3959
3959
|
if (hardClearCandidate !== void 0) {
|
|
3960
|
+
let bestPoints2 = hardClearCandidate.points;
|
|
3961
|
+
if (input.kind === "obstacle-avoiding") {
|
|
3962
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
3963
|
+
for (const candidate of candidateRoutes) {
|
|
3964
|
+
if (routeCrossesBoxes(candidate.points, hardObstacles) || routeIntersectsEndpointInteriors(
|
|
3965
|
+
candidate.points,
|
|
3966
|
+
candidate.endpointObstacles
|
|
3967
|
+
)) {
|
|
3968
|
+
continue;
|
|
3969
|
+
}
|
|
3970
|
+
const rerouted2 = greedyRerouteAroundObstacles(
|
|
3971
|
+
candidate.points,
|
|
3972
|
+
allObstacles,
|
|
3973
|
+
3
|
|
3974
|
+
);
|
|
3975
|
+
if (!routeCrossesBoxes(rerouted2, allObstacles) && !routeIntersectsEndpointInteriors(
|
|
3976
|
+
rerouted2,
|
|
3977
|
+
candidate.endpointObstacles
|
|
3978
|
+
)) {
|
|
3979
|
+
return {
|
|
3980
|
+
points: finalizeRoute(
|
|
3981
|
+
rerouted2,
|
|
3982
|
+
softObstacles,
|
|
3983
|
+
hardObstacles,
|
|
3984
|
+
diagnostics
|
|
3985
|
+
),
|
|
3986
|
+
diagnostics
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
const rerouted = greedyRerouteAroundObstacles(
|
|
3991
|
+
bestPoints2,
|
|
3992
|
+
allObstacles,
|
|
3993
|
+
3
|
|
3994
|
+
);
|
|
3995
|
+
const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
|
|
3996
|
+
rerouted,
|
|
3997
|
+
hardClearCandidate.endpointObstacles
|
|
3998
|
+
);
|
|
3999
|
+
if (reroutedAvoidsEndpointInteriors) {
|
|
4000
|
+
if (routeCrossesBoxes(rerouted, hardObstacles) && !routeCrossesBoxes(bestPoints2, hardObstacles)) ; else {
|
|
4001
|
+
bestPoints2 = rerouted;
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
3960
4005
|
diagnostics.push({
|
|
3961
4006
|
severity: "warning",
|
|
3962
4007
|
code: "routing.obstacle.unavoidable",
|
|
@@ -3964,7 +4009,7 @@ function routeEdge(input) {
|
|
|
3964
4009
|
});
|
|
3965
4010
|
return {
|
|
3966
4011
|
points: finalizeRoute(
|
|
3967
|
-
|
|
4012
|
+
bestPoints2,
|
|
3968
4013
|
softObstacles,
|
|
3969
4014
|
hardObstacles,
|
|
3970
4015
|
diagnostics
|
|
@@ -3973,6 +4018,33 @@ function routeEdge(input) {
|
|
|
3973
4018
|
};
|
|
3974
4019
|
}
|
|
3975
4020
|
if (hardObstacles.length > 0) {
|
|
4021
|
+
let bestPoints2 = candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors);
|
|
4022
|
+
if (input.kind === "obstacle-avoiding") {
|
|
4023
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4024
|
+
for (const candidate of candidateRoutes) {
|
|
4025
|
+
const rerouted = greedyRerouteAroundObstacles(
|
|
4026
|
+
candidate.points,
|
|
4027
|
+
allObstacles,
|
|
4028
|
+
5
|
|
4029
|
+
);
|
|
4030
|
+
if (!routeCrossesBoxes(rerouted, allObstacles)) {
|
|
4031
|
+
return {
|
|
4032
|
+
points: finalizeRoute(
|
|
4033
|
+
rerouted,
|
|
4034
|
+
softObstacles,
|
|
4035
|
+
hardObstacles,
|
|
4036
|
+
diagnostics
|
|
4037
|
+
),
|
|
4038
|
+
diagnostics
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
bestPoints2 = greedyRerouteAroundObstacles(
|
|
4043
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
|
|
4044
|
+
allObstacles,
|
|
4045
|
+
5
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
3976
4048
|
diagnostics.push({
|
|
3977
4049
|
severity: "error",
|
|
3978
4050
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -3980,7 +4052,7 @@ function routeEdge(input) {
|
|
|
3980
4052
|
});
|
|
3981
4053
|
return {
|
|
3982
4054
|
points: finalizeRoute(
|
|
3983
|
-
|
|
4055
|
+
bestPoints2,
|
|
3984
4056
|
softObstacles,
|
|
3985
4057
|
hardObstacles,
|
|
3986
4058
|
diagnostics
|
|
@@ -3988,6 +4060,33 @@ function routeEdge(input) {
|
|
|
3988
4060
|
diagnostics
|
|
3989
4061
|
};
|
|
3990
4062
|
}
|
|
4063
|
+
let bestPoints = candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors);
|
|
4064
|
+
if (input.kind === "obstacle-avoiding") {
|
|
4065
|
+
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4066
|
+
for (const candidate of candidateRoutes) {
|
|
4067
|
+
const rerouted = greedyRerouteAroundObstacles(
|
|
4068
|
+
candidate.points,
|
|
4069
|
+
allObstacles,
|
|
4070
|
+
5
|
|
4071
|
+
);
|
|
4072
|
+
if (!routeCrossesBoxes(rerouted, allObstacles)) {
|
|
4073
|
+
return {
|
|
4074
|
+
points: finalizeRoute(
|
|
4075
|
+
rerouted,
|
|
4076
|
+
softObstacles,
|
|
4077
|
+
hardObstacles,
|
|
4078
|
+
diagnostics
|
|
4079
|
+
),
|
|
4080
|
+
diagnostics
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
bestPoints = greedyRerouteAroundObstacles(
|
|
4085
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
|
|
4086
|
+
allObstacles,
|
|
4087
|
+
5
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
3991
4090
|
diagnostics.push({
|
|
3992
4091
|
severity: "warning",
|
|
3993
4092
|
code: "routing.obstacle.unavoidable",
|
|
@@ -3995,7 +4094,7 @@ function routeEdge(input) {
|
|
|
3995
4094
|
});
|
|
3996
4095
|
return {
|
|
3997
4096
|
points: finalizeRoute(
|
|
3998
|
-
|
|
4097
|
+
bestPoints,
|
|
3999
4098
|
softObstacles,
|
|
4000
4099
|
hardObstacles,
|
|
4001
4100
|
diagnostics
|
|
@@ -4153,6 +4252,70 @@ function insetBox(box, margin) {
|
|
|
4153
4252
|
height: box.height - margin * 2
|
|
4154
4253
|
};
|
|
4155
4254
|
}
|
|
4255
|
+
function greedyRerouteAroundObstacles(points, obstacles, maxIterations) {
|
|
4256
|
+
let current = [...points];
|
|
4257
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
4258
|
+
const improved = pushRouteAwayFromObstacles(current, obstacles);
|
|
4259
|
+
if (improved === null) {
|
|
4260
|
+
break;
|
|
4261
|
+
}
|
|
4262
|
+
current = improved;
|
|
4263
|
+
if (!routeCrossesBoxes(current, obstacles)) {
|
|
4264
|
+
break;
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
return current;
|
|
4268
|
+
}
|
|
4269
|
+
function pushRouteAwayFromObstacles(points, obstacles) {
|
|
4270
|
+
const result = [];
|
|
4271
|
+
let improved = false;
|
|
4272
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
4273
|
+
const a = points[i];
|
|
4274
|
+
const b = points[i + 1];
|
|
4275
|
+
if (a === void 0 || b === void 0) {
|
|
4276
|
+
result.push(a ?? b ?? { x: 0, y: 0 });
|
|
4277
|
+
continue;
|
|
4278
|
+
}
|
|
4279
|
+
result.push(a);
|
|
4280
|
+
const intersectors = obstacles.filter(
|
|
4281
|
+
(obs) => segmentIntersectsBox(a, b, obs)
|
|
4282
|
+
);
|
|
4283
|
+
if (intersectors.length === 0) {
|
|
4284
|
+
continue;
|
|
4285
|
+
}
|
|
4286
|
+
const mx = (a.x + b.x) / 2;
|
|
4287
|
+
const my = (a.y + b.y) / 2;
|
|
4288
|
+
const isHorizontal = a.y === b.y;
|
|
4289
|
+
const margin = 12;
|
|
4290
|
+
let bestWaypoint = null;
|
|
4291
|
+
let bestDist = Infinity;
|
|
4292
|
+
for (const obs of intersectors) {
|
|
4293
|
+
const candidates = isHorizontal ? [
|
|
4294
|
+
{ x: mx, y: obs.y - margin },
|
|
4295
|
+
{ x: mx, y: obs.y + obs.height + margin }
|
|
4296
|
+
] : [
|
|
4297
|
+
{ x: obs.x - margin, y: my },
|
|
4298
|
+
{ x: obs.x + obs.width + margin, y: my }
|
|
4299
|
+
];
|
|
4300
|
+
for (const wp of candidates) {
|
|
4301
|
+
const dist = Math.hypot(wp.x - mx, wp.y - my);
|
|
4302
|
+
if (dist < bestDist) {
|
|
4303
|
+
bestDist = dist;
|
|
4304
|
+
bestWaypoint = wp;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
if (bestWaypoint !== null) {
|
|
4309
|
+
result.push(bestWaypoint);
|
|
4310
|
+
improved = true;
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
const last = points[points.length - 1];
|
|
4314
|
+
if (last !== void 0) {
|
|
4315
|
+
result.push(last);
|
|
4316
|
+
}
|
|
4317
|
+
return improved ? result : null;
|
|
4318
|
+
}
|
|
4156
4319
|
function fallbackRoute(input, defaultAnchors) {
|
|
4157
4320
|
return [
|
|
4158
4321
|
getEdgePort(
|
|
@@ -4739,7 +4902,9 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4739
4902
|
styledEdges,
|
|
4740
4903
|
nodeGeometryById,
|
|
4741
4904
|
coordinatedNodes,
|
|
4742
|
-
[...nodeGeometryById.values()].map(
|
|
4905
|
+
[...nodeGeometryById.values()].map(
|
|
4906
|
+
(geometry) => options.routingGutter === void 0 ? geometry.obstacleBox : expandBox(geometry.obstacleBox, options.routingGutter)
|
|
4907
|
+
),
|
|
4743
4908
|
[...softObstacles, ...titleBarObstacles],
|
|
4744
4909
|
routingTextObstacles,
|
|
4745
4910
|
hardObstacles,
|
|
@@ -7062,9 +7227,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
7062
7227
|
{ x: placement.x, y: placement.y + offset }
|
|
7063
7228
|
);
|
|
7064
7229
|
}
|
|
7065
|
-
|
|
7066
|
-
}
|
|
7067
|
-
if (segment.start.x === segment.end.x) {
|
|
7230
|
+
} else if (segment.start.x === segment.end.x) {
|
|
7068
7231
|
const needed = layout2.box.width / 2 + EDGE_LABEL_CLEARANCE;
|
|
7069
7232
|
const maxSteps = Math.max(12, Math.ceil(needed / EDGE_LABEL_CLEARANCE));
|
|
7070
7233
|
for (let step = 1; step <= maxSteps; step += 1) {
|
|
@@ -7074,7 +7237,83 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
7074
7237
|
{ x: placement.x - offset, y: placement.y }
|
|
7075
7238
|
);
|
|
7076
7239
|
}
|
|
7077
|
-
|
|
7240
|
+
} else {
|
|
7241
|
+
const dx = segment.end.x - segment.start.x;
|
|
7242
|
+
const dy = segment.end.y - segment.start.y;
|
|
7243
|
+
const segLen = Math.hypot(dx, dy);
|
|
7244
|
+
if (segLen > 0) {
|
|
7245
|
+
const nx = -dy / segLen;
|
|
7246
|
+
const ny = dx / segLen;
|
|
7247
|
+
const needed = (Math.abs(nx) * layout2.box.width + Math.abs(ny) * layout2.box.height) / 2 + EDGE_LABEL_CLEARANCE;
|
|
7248
|
+
const maxSteps = Math.max(12, Math.ceil(needed / EDGE_LABEL_CLEARANCE));
|
|
7249
|
+
for (let step = 1; step <= maxSteps; step += 1) {
|
|
7250
|
+
const offset = EDGE_LABEL_CLEARANCE * step;
|
|
7251
|
+
candidates.push(
|
|
7252
|
+
{ x: placement.x + nx * offset, y: placement.y + ny * offset },
|
|
7253
|
+
{ x: placement.x - nx * offset, y: placement.y - ny * offset }
|
|
7254
|
+
);
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
const totalLen = points.reduce((sum, p, idx) => {
|
|
7259
|
+
if (idx === 0) return 0;
|
|
7260
|
+
const prev = points[idx - 1];
|
|
7261
|
+
return sum + Math.hypot((p?.x ?? 0) - (prev?.x ?? 0), (p?.y ?? 0) - (prev?.y ?? 0));
|
|
7262
|
+
}, 0);
|
|
7263
|
+
if (totalLen > 200) {
|
|
7264
|
+
for (const ratio of [0.25, 0.75]) {
|
|
7265
|
+
const qp = labelPlacementAtRatio(points, ratio, totalLen);
|
|
7266
|
+
if (qp !== void 0) {
|
|
7267
|
+
candidates.push(qp);
|
|
7268
|
+
const qTargetDist = totalLen * ratio;
|
|
7269
|
+
let qTravelled = 0;
|
|
7270
|
+
let seg;
|
|
7271
|
+
for (let si = 1; si < points.length; si++) {
|
|
7272
|
+
const sp = points[si - 1];
|
|
7273
|
+
const sc = points[si];
|
|
7274
|
+
if (sp === void 0 || sc === void 0) continue;
|
|
7275
|
+
const sl = Math.hypot(sc.x - sp.x, sc.y - sp.y);
|
|
7276
|
+
if (sl <= 0) continue;
|
|
7277
|
+
if (qTravelled + sl >= qTargetDist) {
|
|
7278
|
+
seg = { start: sp, end: sc, length: sl };
|
|
7279
|
+
break;
|
|
7280
|
+
}
|
|
7281
|
+
qTravelled += sl;
|
|
7282
|
+
}
|
|
7283
|
+
if (seg !== void 0) {
|
|
7284
|
+
const segLen = Math.hypot(
|
|
7285
|
+
seg.end.x - seg.start.x,
|
|
7286
|
+
seg.end.y - seg.start.y
|
|
7287
|
+
);
|
|
7288
|
+
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;
|
|
7289
|
+
const qpMaxSteps = Math.max(
|
|
7290
|
+
12,
|
|
7291
|
+
Math.ceil(qpNeeded / EDGE_LABEL_CLEARANCE)
|
|
7292
|
+
);
|
|
7293
|
+
for (let step = 1; step <= qpMaxSteps; step += 1) {
|
|
7294
|
+
const offset = EDGE_LABEL_CLEARANCE * step;
|
|
7295
|
+
if (seg.start.y === seg.end.y) {
|
|
7296
|
+
candidates.push(
|
|
7297
|
+
{ x: qp.x, y: qp.y - offset },
|
|
7298
|
+
{ x: qp.x, y: qp.y + offset }
|
|
7299
|
+
);
|
|
7300
|
+
} else if (seg.start.x === seg.end.x) {
|
|
7301
|
+
candidates.push(
|
|
7302
|
+
{ x: qp.x - offset, y: qp.y },
|
|
7303
|
+
{ x: qp.x + offset, y: qp.y }
|
|
7304
|
+
);
|
|
7305
|
+
} else {
|
|
7306
|
+
const nx = -(seg.end.y - seg.start.y) / segLen;
|
|
7307
|
+
const ny = (seg.end.x - seg.start.x) / segLen;
|
|
7308
|
+
candidates.push(
|
|
7309
|
+
{ x: qp.x + nx * offset, y: qp.y + ny * offset },
|
|
7310
|
+
{ x: qp.x - nx * offset, y: qp.y - ny * offset }
|
|
7311
|
+
);
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7078
7317
|
}
|
|
7079
7318
|
return candidates;
|
|
7080
7319
|
}
|
|
@@ -7131,6 +7370,34 @@ function nonZeroSegments2(points) {
|
|
|
7131
7370
|
}
|
|
7132
7371
|
return segments;
|
|
7133
7372
|
}
|
|
7373
|
+
function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
7374
|
+
if (points.length < 2 || ratio < 0 || ratio > 1) {
|
|
7375
|
+
return void 0;
|
|
7376
|
+
}
|
|
7377
|
+
const targetDist = totalLength * ratio;
|
|
7378
|
+
let travelled = 0;
|
|
7379
|
+
for (let idx = 1; idx < points.length; idx++) {
|
|
7380
|
+
const prev = points[idx - 1];
|
|
7381
|
+
const curr = points[idx];
|
|
7382
|
+
if (prev === void 0 || curr === void 0) {
|
|
7383
|
+
continue;
|
|
7384
|
+
}
|
|
7385
|
+
const segLen = Math.hypot(curr.x - prev.x, curr.y - prev.y);
|
|
7386
|
+
if (segLen <= 0) {
|
|
7387
|
+
continue;
|
|
7388
|
+
}
|
|
7389
|
+
if (travelled + segLen >= targetDist) {
|
|
7390
|
+
const t = (targetDist - travelled) / segLen;
|
|
7391
|
+
const offset = labelOffset2({ start: prev, end: curr, length: segLen });
|
|
7392
|
+
return {
|
|
7393
|
+
x: prev.x + (curr.x - prev.x) * t + offset.x,
|
|
7394
|
+
y: prev.y + (curr.y - prev.y) * t + offset.y
|
|
7395
|
+
};
|
|
7396
|
+
}
|
|
7397
|
+
travelled += segLen;
|
|
7398
|
+
}
|
|
7399
|
+
return void 0;
|
|
7400
|
+
}
|
|
7134
7401
|
function labelOffset2(segment) {
|
|
7135
7402
|
const offset = 10;
|
|
7136
7403
|
const dx = segment.end.x - segment.start.x;
|
|
@@ -7245,7 +7512,7 @@ function renderDiagramDsl(source, options = {}) {
|
|
|
7245
7512
|
return { diagnostics };
|
|
7246
7513
|
}
|
|
7247
7514
|
const solved = solveDiagram(normalized.diagram, {
|
|
7248
|
-
routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
|
|
7515
|
+
routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : normalized.diagram.metadata?.routeKind === "obstacle-avoiding" ? "obstacle-avoiding" : "orthogonal",
|
|
7249
7516
|
...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
|
|
7250
7517
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
7251
7518
|
});
|