@crazyhappyone/auto-graph 0.1.4 → 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 +446 -48
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +446 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +447 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +447 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -808,7 +808,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
808
808
|
const lock = locks.get(childId);
|
|
809
809
|
if (lock?.source === "fixed-position") {
|
|
810
810
|
unlocked.push({ id: childId, box });
|
|
811
|
-
locks.delete(childId);
|
|
812
811
|
continue;
|
|
813
812
|
}
|
|
814
813
|
diagnostics.push({
|
|
@@ -3981,6 +3980,213 @@ function isValidDimension(value) {
|
|
|
3981
3980
|
return Number.isFinite(value) && value >= 0;
|
|
3982
3981
|
}
|
|
3983
3982
|
|
|
3983
|
+
// src/routing/astar.ts
|
|
3984
|
+
function findObstacleFreePath(source, target, obstacles, options = {}) {
|
|
3985
|
+
const margin = options.margin ?? 0;
|
|
3986
|
+
const turnPenalty = options.turnPenalty ?? 50;
|
|
3987
|
+
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
3988
|
+
const endpointObstacles = options.endpointObstacles ?? [];
|
|
3989
|
+
const maxNodes = options.maxNodes ?? 4e3;
|
|
3990
|
+
const xs = collectXs(source, target, obstacles, margin);
|
|
3991
|
+
const ys = collectYs(source, target, obstacles, margin);
|
|
3992
|
+
if (xs.length * ys.length > maxNodes) {
|
|
3993
|
+
return null;
|
|
3994
|
+
}
|
|
3995
|
+
const { nodes, nodeIndex } = buildGraph(xs, ys);
|
|
3996
|
+
connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin);
|
|
3997
|
+
connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin);
|
|
3998
|
+
const path = aStarSearch(
|
|
3999
|
+
nodes,
|
|
4000
|
+
nodeIndex,
|
|
4001
|
+
source,
|
|
4002
|
+
target,
|
|
4003
|
+
turnPenalty,
|
|
4004
|
+
segmentPenalty
|
|
4005
|
+
);
|
|
4006
|
+
if (path === null) return null;
|
|
4007
|
+
return simplifyRoute(path);
|
|
4008
|
+
}
|
|
4009
|
+
function collectXs(source, target, obstacles, margin) {
|
|
4010
|
+
const set = /* @__PURE__ */ new Set();
|
|
4011
|
+
set.add(source.x);
|
|
4012
|
+
set.add(target.x);
|
|
4013
|
+
for (const obs of obstacles) {
|
|
4014
|
+
set.add(obs.x - margin - 2);
|
|
4015
|
+
set.add(obs.x + obs.width + margin + 2);
|
|
4016
|
+
}
|
|
4017
|
+
return [...set].sort((a, b) => a - b);
|
|
4018
|
+
}
|
|
4019
|
+
function collectYs(source, target, obstacles, margin) {
|
|
4020
|
+
const set = /* @__PURE__ */ new Set();
|
|
4021
|
+
set.add(source.y);
|
|
4022
|
+
set.add(target.y);
|
|
4023
|
+
for (const obs of obstacles) {
|
|
4024
|
+
set.add(obs.y - margin - 2);
|
|
4025
|
+
set.add(obs.y + obs.height + margin + 2);
|
|
4026
|
+
}
|
|
4027
|
+
return [...set].sort((a, b) => a - b);
|
|
4028
|
+
}
|
|
4029
|
+
function buildGraph(xs, ys) {
|
|
4030
|
+
const nodes = [];
|
|
4031
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
4032
|
+
for (let xi = 0; xi < xs.length; xi++) {
|
|
4033
|
+
for (let yi = 0; yi < ys.length; yi++) {
|
|
4034
|
+
const x = xs[xi];
|
|
4035
|
+
const y = ys[yi];
|
|
4036
|
+
const id = nodes.length;
|
|
4037
|
+
nodes.push({ x, y, id, neighbors: /* @__PURE__ */ new Map() });
|
|
4038
|
+
nodeIndex.set(`${x},${y}`, id);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
return { nodes, nodeIndex };
|
|
4042
|
+
}
|
|
4043
|
+
function connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin) {
|
|
4044
|
+
for (const y of ys) {
|
|
4045
|
+
const row = nodes.filter((n) => n.y === y).sort((a, b) => a.x - b.x);
|
|
4046
|
+
for (let i = 0; i < row.length - 1; i++) {
|
|
4047
|
+
const a = row[i];
|
|
4048
|
+
const b = row[i + 1];
|
|
4049
|
+
const dx = b.x - a.x;
|
|
4050
|
+
if (dx <= 0) continue;
|
|
4051
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
4052
|
+
continue;
|
|
4053
|
+
}
|
|
4054
|
+
a.neighbors.set(b.id, dx);
|
|
4055
|
+
b.neighbors.set(a.id, dx);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
function connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin) {
|
|
4060
|
+
for (const x of xs) {
|
|
4061
|
+
const col = nodes.filter((n) => n.x === x).sort((a, b) => a.y - b.y);
|
|
4062
|
+
for (let i = 0; i < col.length - 1; i++) {
|
|
4063
|
+
const a = col[i];
|
|
4064
|
+
const b = col[i + 1];
|
|
4065
|
+
const dy = b.y - a.y;
|
|
4066
|
+
if (dy <= 0) continue;
|
|
4067
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
4068
|
+
continue;
|
|
4069
|
+
}
|
|
4070
|
+
a.neighbors.set(b.id, dy);
|
|
4071
|
+
b.neighbors.set(a.id, dy);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
function segmentCrossesAny(a, b, obstacles, endpointObstacles, margin) {
|
|
4076
|
+
for (const obs of obstacles) {
|
|
4077
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) return true;
|
|
4078
|
+
}
|
|
4079
|
+
for (const ep of endpointObstacles) {
|
|
4080
|
+
if (segmentCrossesBoxStrict(a, b, ep, margin)) return true;
|
|
4081
|
+
}
|
|
4082
|
+
return false;
|
|
4083
|
+
}
|
|
4084
|
+
function segmentCrossesBoxStrict(start, end, box, margin) {
|
|
4085
|
+
const left = box.x - margin;
|
|
4086
|
+
const right = box.x + box.width + margin;
|
|
4087
|
+
const top = box.y - margin;
|
|
4088
|
+
const bottom = box.y + box.height + margin;
|
|
4089
|
+
if (pointInsideStrict(start, left, right, top, bottom)) return true;
|
|
4090
|
+
if (pointInsideStrict(end, left, right, top, bottom)) return true;
|
|
4091
|
+
if (start.x === end.x) {
|
|
4092
|
+
return start.x >= left && start.x <= right && rangesOverlap(start.y, end.y, top, bottom);
|
|
4093
|
+
}
|
|
4094
|
+
if (start.y === end.y) {
|
|
4095
|
+
return start.y >= top && start.y <= bottom && rangesOverlap(start.x, end.x, left, right);
|
|
4096
|
+
}
|
|
4097
|
+
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);
|
|
4098
|
+
}
|
|
4099
|
+
function pointInsideStrict(p, left, right, top, bottom) {
|
|
4100
|
+
return p.x > left && p.x < right && p.y > top && p.y < bottom;
|
|
4101
|
+
}
|
|
4102
|
+
function rangesOverlap(a, b, min, max) {
|
|
4103
|
+
const low = Math.min(a, b);
|
|
4104
|
+
const high = Math.max(a, b);
|
|
4105
|
+
return high > min && low < max;
|
|
4106
|
+
}
|
|
4107
|
+
function segmentEdgeIntersect(start, end, x1, y1, x2, y2) {
|
|
4108
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
4109
|
+
if (denominator === 0) return false;
|
|
4110
|
+
const t = ((start.x - x1) * (y2 - y1) - (start.y - y1) * (x2 - x1)) / denominator;
|
|
4111
|
+
const u = ((start.x - x1) * (end.y - start.y) - (start.y - y1) * (end.x - start.x)) / denominator;
|
|
4112
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
4113
|
+
}
|
|
4114
|
+
function aStarSearch(nodes, nodeIndex, source, target, turnPenalty, segmentPenalty) {
|
|
4115
|
+
const startId = nodeIndex.get(`${source.x},${source.y}`);
|
|
4116
|
+
const goalId = nodeIndex.get(`${target.x},${target.y}`);
|
|
4117
|
+
if (startId === void 0 || goalId === void 0) return null;
|
|
4118
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
4119
|
+
gScore.set(startId, 0);
|
|
4120
|
+
const cameFrom = /* @__PURE__ */ new Map();
|
|
4121
|
+
const cameFromDir = /* @__PURE__ */ new Map();
|
|
4122
|
+
const openSet = [];
|
|
4123
|
+
openSet.push({
|
|
4124
|
+
id: startId,
|
|
4125
|
+
f: manhattan(source, target)
|
|
4126
|
+
});
|
|
4127
|
+
while (openSet.length > 0) {
|
|
4128
|
+
let bestIdx = 0;
|
|
4129
|
+
for (let i = 1; i < openSet.length; i++) {
|
|
4130
|
+
if (openSet[i].f < openSet[bestIdx].f) {
|
|
4131
|
+
bestIdx = i;
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
const current = openSet.splice(bestIdx, 1)[0];
|
|
4135
|
+
if (current.id === goalId) {
|
|
4136
|
+
return reconstructPath(nodes, cameFrom, goalId);
|
|
4137
|
+
}
|
|
4138
|
+
const node = nodes[current.id];
|
|
4139
|
+
const currentG = gScore.get(current.id);
|
|
4140
|
+
const prevDir = cameFromDir.get(current.id);
|
|
4141
|
+
for (const [neighborId, edgeCost] of node.neighbors) {
|
|
4142
|
+
const neighbor = nodes[neighborId];
|
|
4143
|
+
const tentativeG = currentG + edgeCost * segmentPenalty;
|
|
4144
|
+
const newDir = neighbor.y === node.y ? "h" : "v";
|
|
4145
|
+
const turnCost = prevDir !== void 0 && prevDir !== newDir ? turnPenalty : 0;
|
|
4146
|
+
const totalG = tentativeG + turnCost;
|
|
4147
|
+
const existingG = gScore.get(neighborId);
|
|
4148
|
+
if (existingG === void 0 || totalG < existingG) {
|
|
4149
|
+
gScore.set(neighborId, totalG);
|
|
4150
|
+
cameFrom.set(neighborId, current.id);
|
|
4151
|
+
cameFromDir.set(neighborId, newDir);
|
|
4152
|
+
const f = totalG + manhattan(neighbor, target);
|
|
4153
|
+
openSet.push({ id: neighborId, f });
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
return null;
|
|
4158
|
+
}
|
|
4159
|
+
function manhattan(a, b) {
|
|
4160
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
4161
|
+
}
|
|
4162
|
+
function reconstructPath(nodes, cameFrom, goalId) {
|
|
4163
|
+
const path = [];
|
|
4164
|
+
let current = goalId;
|
|
4165
|
+
while (current !== void 0) {
|
|
4166
|
+
const node = nodes[current];
|
|
4167
|
+
path.unshift({ x: node.x, y: node.y });
|
|
4168
|
+
current = cameFrom.get(current);
|
|
4169
|
+
}
|
|
4170
|
+
return path;
|
|
4171
|
+
}
|
|
4172
|
+
function simplifyRoute(points) {
|
|
4173
|
+
if (points.length <= 2) return [...points];
|
|
4174
|
+
const result = [points[0]];
|
|
4175
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
4176
|
+
const prev = result[result.length - 1];
|
|
4177
|
+
const curr = points[i];
|
|
4178
|
+
const next = points[i + 1];
|
|
4179
|
+
if (!areCollinear(prev, curr, next)) {
|
|
4180
|
+
result.push(curr);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
result.push(points[points.length - 1]);
|
|
4184
|
+
return result;
|
|
4185
|
+
}
|
|
4186
|
+
function areCollinear(a, b, c) {
|
|
4187
|
+
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4188
|
+
}
|
|
4189
|
+
|
|
3984
4190
|
// src/routing/routes.ts
|
|
3985
4191
|
function routeEdge(input) {
|
|
3986
4192
|
const diagnostics = [];
|
|
@@ -4026,6 +4232,43 @@ function routeEdge(input) {
|
|
|
4026
4232
|
}
|
|
4027
4233
|
return { points, diagnostics };
|
|
4028
4234
|
}
|
|
4235
|
+
if ((input.kind ?? "orthogonal") === "obstacle-avoiding") {
|
|
4236
|
+
const endpointObstacles = endpointObstaclesForAutoAnchors(input);
|
|
4237
|
+
for (const { sourceAnchor, targetAnchor } of routeAnchorPairs(
|
|
4238
|
+
input,
|
|
4239
|
+
defaultAnchors
|
|
4240
|
+
)) {
|
|
4241
|
+
const source = getEdgePort(
|
|
4242
|
+
input.source,
|
|
4243
|
+
input.target.center,
|
|
4244
|
+
sourceAnchor
|
|
4245
|
+
);
|
|
4246
|
+
const target = getEdgePort(
|
|
4247
|
+
input.target,
|
|
4248
|
+
input.source.center,
|
|
4249
|
+
targetAnchor
|
|
4250
|
+
);
|
|
4251
|
+
const path = findObstacleFreePath(
|
|
4252
|
+
source,
|
|
4253
|
+
target,
|
|
4254
|
+
[...softObstacles, ...hardObstacles],
|
|
4255
|
+
{
|
|
4256
|
+
endpointObstacles
|
|
4257
|
+
}
|
|
4258
|
+
);
|
|
4259
|
+
if (path !== null && path.length >= 2) {
|
|
4260
|
+
const finalized = finalizeRoute(
|
|
4261
|
+
path,
|
|
4262
|
+
softObstacles,
|
|
4263
|
+
hardObstacles,
|
|
4264
|
+
diagnostics
|
|
4265
|
+
);
|
|
4266
|
+
if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
|
|
4267
|
+
return { points: finalized, diagnostics };
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4029
4272
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
4030
4273
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
4031
4274
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -4228,7 +4471,7 @@ function routeEdge(input) {
|
|
|
4228
4471
|
};
|
|
4229
4472
|
}
|
|
4230
4473
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
4231
|
-
const simplified =
|
|
4474
|
+
const simplified = simplifyRoute2(points);
|
|
4232
4475
|
if (simplified.length >= 3) {
|
|
4233
4476
|
return simplified;
|
|
4234
4477
|
}
|
|
@@ -4516,7 +4759,7 @@ function squaredDistance2(a, b) {
|
|
|
4516
4759
|
const dy = a.y - b.y;
|
|
4517
4760
|
return dx * dx + dy * dy;
|
|
4518
4761
|
}
|
|
4519
|
-
function
|
|
4762
|
+
function simplifyRoute2(points) {
|
|
4520
4763
|
const withoutDuplicates = [];
|
|
4521
4764
|
for (const point2 of points) {
|
|
4522
4765
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -4528,7 +4771,7 @@ function simplifyRoute(points) {
|
|
|
4528
4771
|
for (const point2 of withoutDuplicates) {
|
|
4529
4772
|
const previous = simplified.at(-1);
|
|
4530
4773
|
const beforePrevious = simplified.at(-2);
|
|
4531
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4774
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
4532
4775
|
simplified[simplified.length - 1] = { ...point2 };
|
|
4533
4776
|
} else {
|
|
4534
4777
|
simplified.push({ ...point2 });
|
|
@@ -4743,17 +4986,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4743
4986
|
return true;
|
|
4744
4987
|
}
|
|
4745
4988
|
if (start.x === end.x) {
|
|
4746
|
-
return start.x > left && start.x < right &&
|
|
4989
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4747
4990
|
}
|
|
4748
4991
|
if (start.y === end.y) {
|
|
4749
|
-
return start.y > top && start.y < bottom &&
|
|
4992
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4750
4993
|
}
|
|
4751
4994
|
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);
|
|
4752
4995
|
}
|
|
4753
4996
|
function pointInsideBox(point2, box) {
|
|
4754
4997
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4755
4998
|
}
|
|
4756
|
-
function
|
|
4999
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4757
5000
|
const low = Math.min(a, b);
|
|
4758
5001
|
const high = Math.max(a, b);
|
|
4759
5002
|
return high > min && low < max;
|
|
@@ -4777,7 +5020,7 @@ function segmentBox(a, b) {
|
|
|
4777
5020
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4778
5021
|
};
|
|
4779
5022
|
}
|
|
4780
|
-
function
|
|
5023
|
+
function areCollinear2(a, b, c) {
|
|
4781
5024
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4782
5025
|
}
|
|
4783
5026
|
|
|
@@ -4857,6 +5100,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4857
5100
|
options,
|
|
4858
5101
|
diagnostics
|
|
4859
5102
|
);
|
|
5103
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4860
5104
|
const constrained = applyLayoutConstraints({
|
|
4861
5105
|
direction: diagram.direction,
|
|
4862
5106
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4920,6 +5164,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4920
5164
|
swimlanes: coordinatedSwimlanes,
|
|
4921
5165
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4922
5166
|
});
|
|
5167
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
5168
|
+
styledEdges,
|
|
5169
|
+
nodeGeometryById,
|
|
5170
|
+
options.textMeasurer
|
|
5171
|
+
);
|
|
4923
5172
|
const layoutBoxes = [
|
|
4924
5173
|
...coordinatedNodes.map((node) => node.box),
|
|
4925
5174
|
...coordinatedNodes.flatMap(
|
|
@@ -5000,7 +5249,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5000
5249
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
5001
5250
|
const routingTextObstacles = [
|
|
5002
5251
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
5003
|
-
...frameTextAnnotation.filter(isPreRouteTextObstacle)
|
|
5252
|
+
...frameTextAnnotation.filter(isPreRouteTextObstacle),
|
|
5253
|
+
// Dry-run edge-label estimates so edges route around
|
|
5254
|
+
// each other's label areas (Issue #41).
|
|
5255
|
+
...edgeLabelEstimates
|
|
5004
5256
|
];
|
|
5005
5257
|
const margin = options.obstacleMargin ?? 0;
|
|
5006
5258
|
const softObstacles = [
|
|
@@ -5033,7 +5285,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5033
5285
|
hardObstacles,
|
|
5034
5286
|
diagram.direction,
|
|
5035
5287
|
options,
|
|
5036
|
-
diagnostics
|
|
5288
|
+
diagnostics,
|
|
5289
|
+
coordinatedGroups
|
|
5037
5290
|
);
|
|
5038
5291
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
5039
5292
|
coordinatedEdges,
|
|
@@ -5149,22 +5402,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
5149
5402
|
y: layout2.box.y + offsetY,
|
|
5150
5403
|
width: layout2.box.width,
|
|
5151
5404
|
height: layout2.box.height
|
|
5152
|
-
}
|
|
5153
|
-
contentBox: {
|
|
5154
|
-
x: layout2.contentBox.x + offsetX,
|
|
5155
|
-
y: layout2.contentBox.y + offsetY,
|
|
5156
|
-
width: layout2.contentBox.width,
|
|
5157
|
-
height: layout2.contentBox.height
|
|
5158
|
-
},
|
|
5159
|
-
lines: layout2.lines.map((line) => ({
|
|
5160
|
-
...line,
|
|
5161
|
-
box: {
|
|
5162
|
-
x: line.box.x + offsetX,
|
|
5163
|
-
y: line.box.y + offsetY,
|
|
5164
|
-
width: line.box.width,
|
|
5165
|
-
height: line.box.height
|
|
5166
|
-
}
|
|
5167
|
-
}))
|
|
5405
|
+
}
|
|
5168
5406
|
};
|
|
5169
5407
|
}
|
|
5170
5408
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -6111,6 +6349,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6111
6349
|
});
|
|
6112
6350
|
continue;
|
|
6113
6351
|
}
|
|
6352
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
6114
6353
|
const geometry = computeShapeGeometry({
|
|
6115
6354
|
shape: node.shape,
|
|
6116
6355
|
box,
|
|
@@ -6120,7 +6359,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6120
6359
|
id: node.id,
|
|
6121
6360
|
...node.label === void 0 ? {} : { label: node.label },
|
|
6122
6361
|
...node.style === void 0 ? {} : { style: node.style },
|
|
6123
|
-
...
|
|
6362
|
+
...ports === void 0 ? {} : { ports },
|
|
6124
6363
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
6125
6364
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
6126
6365
|
shape: node.shape,
|
|
@@ -6132,6 +6371,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6132
6371
|
}
|
|
6133
6372
|
return coordinated;
|
|
6134
6373
|
}
|
|
6374
|
+
var PORT_BOX_SIZE = 10;
|
|
6375
|
+
var MIN_PORT_EDGE_GAP = 12;
|
|
6376
|
+
function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
6377
|
+
const shiftingEnabled = options.portShifting?.enabled ?? true;
|
|
6378
|
+
if (!shiftingEnabled) return;
|
|
6379
|
+
const requestedSpacing = options.portShifting?.spacing ?? 24;
|
|
6380
|
+
const minSpacing = Math.max(
|
|
6381
|
+
requestedSpacing,
|
|
6382
|
+
PORT_BOX_SIZE + MIN_PORT_EDGE_GAP
|
|
6383
|
+
);
|
|
6384
|
+
for (const node of nodes) {
|
|
6385
|
+
if (node.ports === void 0 || node.ports.length === 0) continue;
|
|
6386
|
+
const box = boxes.get(node.id);
|
|
6387
|
+
if (box === void 0) continue;
|
|
6388
|
+
let heightExpansion = 0;
|
|
6389
|
+
let widthExpansion = 0;
|
|
6390
|
+
const portsBySide = /* @__PURE__ */ new Map();
|
|
6391
|
+
for (const port of node.ports) {
|
|
6392
|
+
const list = portsBySide.get(port.side) ?? [];
|
|
6393
|
+
list.push(port);
|
|
6394
|
+
portsBySide.set(port.side, list);
|
|
6395
|
+
}
|
|
6396
|
+
for (const [side, ports] of portsBySide) {
|
|
6397
|
+
const count = (ports ?? []).length;
|
|
6398
|
+
if (count <= 1) continue;
|
|
6399
|
+
const isVertical = side === "left" || side === "right";
|
|
6400
|
+
const availableSpan = isVertical ? box.height : box.width;
|
|
6401
|
+
const requiredSpan = (count - 1) * minSpacing + PORT_BOX_SIZE;
|
|
6402
|
+
if (requiredSpan > availableSpan) {
|
|
6403
|
+
const expansion = requiredSpan - availableSpan;
|
|
6404
|
+
if (isVertical) {
|
|
6405
|
+
heightExpansion = Math.max(heightExpansion, expansion);
|
|
6406
|
+
} else {
|
|
6407
|
+
widthExpansion = Math.max(widthExpansion, expansion);
|
|
6408
|
+
}
|
|
6409
|
+
diagnostics.push({
|
|
6410
|
+
severity: "info",
|
|
6411
|
+
code: "port_capacity_overflow",
|
|
6412
|
+
message: `Expanded node ${node.id} ${isVertical ? "height" : "width"} by ${Math.ceil(expansion)} px to fit ${count} port(s) on ${side} side.`,
|
|
6413
|
+
path: ["nodes", node.id, "ports"],
|
|
6414
|
+
detail: {
|
|
6415
|
+
nodeId: node.id,
|
|
6416
|
+
side,
|
|
6417
|
+
portCount: count,
|
|
6418
|
+
expansion: Math.ceil(expansion)
|
|
6419
|
+
}
|
|
6420
|
+
});
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
if (heightExpansion > 0) {
|
|
6424
|
+
box.y -= heightExpansion / 2;
|
|
6425
|
+
box.height += heightExpansion;
|
|
6426
|
+
}
|
|
6427
|
+
if (widthExpansion > 0) {
|
|
6428
|
+
box.x -= widthExpansion / 2;
|
|
6429
|
+
box.width += widthExpansion;
|
|
6430
|
+
}
|
|
6431
|
+
if ((heightExpansion > 0 || widthExpansion > 0) && node.labelLayout !== void 0) {
|
|
6432
|
+
const layout2 = node.labelLayout;
|
|
6433
|
+
const newOffsetX = Math.max(0, (box.width - layout2.box.width) / 2);
|
|
6434
|
+
const newOffsetY = Math.max(0, (box.height - layout2.box.height) / 2);
|
|
6435
|
+
node.labelLayout = {
|
|
6436
|
+
...layout2,
|
|
6437
|
+
box: {
|
|
6438
|
+
...layout2.box,
|
|
6439
|
+
x: newOffsetX,
|
|
6440
|
+
y: newOffsetY
|
|
6441
|
+
}
|
|
6442
|
+
};
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6135
6446
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
6136
6447
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
6137
6448
|
for (const port of node.ports ?? []) {
|
|
@@ -6168,7 +6479,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6168
6479
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
6169
6480
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
6170
6481
|
const availableSpan = 2 * maxOffset;
|
|
6171
|
-
const
|
|
6482
|
+
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
6483
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
6484
|
+
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
6485
|
+
minSpacing
|
|
6486
|
+
) : requestedSpacing;
|
|
6172
6487
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
6173
6488
|
switch (side) {
|
|
6174
6489
|
case "left":
|
|
@@ -6194,7 +6509,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6194
6509
|
}
|
|
6195
6510
|
}
|
|
6196
6511
|
function portBox(anchor) {
|
|
6197
|
-
const size =
|
|
6512
|
+
const size = PORT_BOX_SIZE;
|
|
6198
6513
|
return {
|
|
6199
6514
|
x: anchor.x - size / 2,
|
|
6200
6515
|
y: anchor.y - size / 2,
|
|
@@ -6783,7 +7098,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6783
7098
|
}
|
|
6784
7099
|
};
|
|
6785
7100
|
}
|
|
6786
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
|
|
7101
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
6787
7102
|
const coordinated = [];
|
|
6788
7103
|
const coordinatedNodeById = new Map(
|
|
6789
7104
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6807,8 +7122,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6807
7122
|
}
|
|
6808
7123
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6809
7124
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6810
|
-
const
|
|
6811
|
-
const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
|
|
7125
|
+
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
6812
7126
|
const route = routeEdge({
|
|
6813
7127
|
kind: options.routeKind ?? "orthogonal",
|
|
6814
7128
|
direction,
|
|
@@ -6821,6 +7135,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6821
7135
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6822
7136
|
),
|
|
6823
7137
|
...softObstacles,
|
|
7138
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6824
7139
|
...routeTextObstacles
|
|
6825
7140
|
],
|
|
6826
7141
|
hardObstacles,
|
|
@@ -6839,15 +7154,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6839
7154
|
}
|
|
6840
7155
|
return coordinated;
|
|
6841
7156
|
}
|
|
6842
|
-
function
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
7157
|
+
function isEdgeConnectedTextAnnotation(edge, annotation) {
|
|
7158
|
+
switch (annotation.surfaceKind) {
|
|
7159
|
+
case "edge-label":
|
|
7160
|
+
return annotation.ownerId === edge.id;
|
|
7161
|
+
case "node-label":
|
|
7162
|
+
case "compartment-row":
|
|
7163
|
+
return annotation.ownerId === edge.source.nodeId || annotation.ownerId === edge.target.nodeId;
|
|
7164
|
+
case "port-label":
|
|
7165
|
+
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}`;
|
|
7166
|
+
case "group-label":
|
|
7167
|
+
case "swimlane-label":
|
|
7168
|
+
case "frame-title":
|
|
7169
|
+
return false;
|
|
6846
7170
|
}
|
|
6847
|
-
|
|
6848
|
-
|
|
7171
|
+
}
|
|
7172
|
+
function ancestorGroupIds(groups, nodeId) {
|
|
7173
|
+
const direct = /* @__PURE__ */ new Set();
|
|
7174
|
+
for (const group of groups) {
|
|
7175
|
+
if (group.nodeIds.includes(nodeId)) {
|
|
7176
|
+
direct.add(group.id);
|
|
7177
|
+
}
|
|
7178
|
+
}
|
|
7179
|
+
let previousSize = -1;
|
|
7180
|
+
const ancestors = new Set(direct);
|
|
7181
|
+
while (ancestors.size !== previousSize) {
|
|
7182
|
+
previousSize = ancestors.size;
|
|
7183
|
+
for (const group of groups) {
|
|
7184
|
+
for (const candidate of ancestors) {
|
|
7185
|
+
if (group.groupIds.includes(candidate)) {
|
|
7186
|
+
ancestors.add(group.id);
|
|
7187
|
+
break;
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
6849
7191
|
}
|
|
6850
|
-
return
|
|
7192
|
+
return ancestors;
|
|
7193
|
+
}
|
|
7194
|
+
function groupObstaclesForEdge(edge, groups, margin) {
|
|
7195
|
+
const sourceAncestors = ancestorGroupIds(groups, edge.source.nodeId);
|
|
7196
|
+
const targetAncestors = ancestorGroupIds(groups, edge.target.nodeId);
|
|
7197
|
+
return groups.filter((group) => {
|
|
7198
|
+
if (sourceAncestors.has(group.id) || targetAncestors.has(group.id)) {
|
|
7199
|
+
return false;
|
|
7200
|
+
}
|
|
7201
|
+
return true;
|
|
7202
|
+
}).map((group) => margin === 0 ? group.box : expandBox(group.box, margin));
|
|
6851
7203
|
}
|
|
6852
7204
|
function coordinateBaseTextAnnotations(input) {
|
|
6853
7205
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -7035,6 +7387,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
7035
7387
|
}
|
|
7036
7388
|
return annotations;
|
|
7037
7389
|
}
|
|
7390
|
+
function estimateEdgeLabelAnnotations(edges, nodes, textMeasurer) {
|
|
7391
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
7392
|
+
const annotations = [];
|
|
7393
|
+
for (const edge of edges) {
|
|
7394
|
+
if (edge.label?.text === void 0) {
|
|
7395
|
+
continue;
|
|
7396
|
+
}
|
|
7397
|
+
const sourceGeom = nodes.get(edge.source.nodeId);
|
|
7398
|
+
const targetGeom = nodes.get(edge.target.nodeId);
|
|
7399
|
+
if (sourceGeom === void 0 || targetGeom === void 0) {
|
|
7400
|
+
continue;
|
|
7401
|
+
}
|
|
7402
|
+
const layout2 = fitLabel(
|
|
7403
|
+
edge.label.text,
|
|
7404
|
+
{
|
|
7405
|
+
font: typographyTextStyle(edge.label, {
|
|
7406
|
+
fontFamily: "Arial",
|
|
7407
|
+
fontSize: 12,
|
|
7408
|
+
lineHeight: 14
|
|
7409
|
+
}),
|
|
7410
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
7411
|
+
minSize: { width: 0, height: 0 },
|
|
7412
|
+
maxWidth: 200
|
|
7413
|
+
},
|
|
7414
|
+
measurer
|
|
7415
|
+
);
|
|
7416
|
+
const cx = (sourceGeom.center.x + targetGeom.center.x) / 2;
|
|
7417
|
+
const cy = (sourceGeom.center.y + targetGeom.center.y) / 2;
|
|
7418
|
+
const box = {
|
|
7419
|
+
x: cx - layout2.box.width / 2,
|
|
7420
|
+
y: cy - layout2.box.height / 2,
|
|
7421
|
+
width: layout2.box.width,
|
|
7422
|
+
height: layout2.box.height
|
|
7423
|
+
};
|
|
7424
|
+
annotations.push({
|
|
7425
|
+
text: layout2.text,
|
|
7426
|
+
ownerId: edge.id,
|
|
7427
|
+
surfaceKind: "edge-label",
|
|
7428
|
+
box,
|
|
7429
|
+
anchor: { x: cx, y: cy },
|
|
7430
|
+
paddings: layout2.padding,
|
|
7431
|
+
lines: layout2.lines,
|
|
7432
|
+
fontFamily: normalizeOutputFontFamily(layout2.font),
|
|
7433
|
+
fontSize: layout2.font.fontSize,
|
|
7434
|
+
textBackend: layout2.textBackend
|
|
7435
|
+
});
|
|
7436
|
+
}
|
|
7437
|
+
return annotations;
|
|
7438
|
+
}
|
|
7038
7439
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
7039
7440
|
const layout2 = fitLabel(
|
|
7040
7441
|
frame.titleTab,
|
|
@@ -7155,9 +7556,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7155
7556
|
const diagnostics = [];
|
|
7156
7557
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
7157
7558
|
for (const edge of edges) {
|
|
7158
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
7159
7559
|
for (const annotation of relevantAnnotations) {
|
|
7160
|
-
if (
|
|
7560
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
7161
7561
|
continue;
|
|
7162
7562
|
}
|
|
7163
7563
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -7181,9 +7581,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7181
7581
|
return diagnostics;
|
|
7182
7582
|
}
|
|
7183
7583
|
function isPreRouteTextObstacle(annotation) {
|
|
7184
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
7185
|
-
return false;
|
|
7186
|
-
}
|
|
7187
7584
|
return isRouteClearanceText(annotation);
|
|
7188
7585
|
}
|
|
7189
7586
|
function isRouteClearanceText(annotation) {
|
|
@@ -7194,8 +7591,9 @@ function isRouteClearanceText(annotation) {
|
|
|
7194
7591
|
case "frame-title":
|
|
7195
7592
|
return true;
|
|
7196
7593
|
case "node-label":
|
|
7197
|
-
case "group-label":
|
|
7198
7594
|
case "compartment-row":
|
|
7595
|
+
return true;
|
|
7596
|
+
case "group-label":
|
|
7199
7597
|
return textExtendsOutsideAnchor(annotation);
|
|
7200
7598
|
}
|
|
7201
7599
|
}
|
|
@@ -7228,17 +7626,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
7228
7626
|
return true;
|
|
7229
7627
|
}
|
|
7230
7628
|
if (start.x === end.x) {
|
|
7231
|
-
return start.x > left && start.x < right &&
|
|
7629
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
7232
7630
|
}
|
|
7233
7631
|
if (start.y === end.y) {
|
|
7234
|
-
return start.y > top && start.y < bottom &&
|
|
7632
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
7235
7633
|
}
|
|
7236
7634
|
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);
|
|
7237
7635
|
}
|
|
7238
7636
|
function pointInsideBox2(point2, box) {
|
|
7239
7637
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
7240
7638
|
}
|
|
7241
|
-
function
|
|
7639
|
+
function rangesOverlap3(a, b, min, max) {
|
|
7242
7640
|
const low = Math.min(a, b);
|
|
7243
7641
|
const high = Math.max(a, b);
|
|
7244
7642
|
return high > min && low < max;
|
|
@@ -7834,6 +8232,6 @@ function isPointLikeRecord(value) {
|
|
|
7834
8232
|
return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
|
|
7835
8233
|
}
|
|
7836
8234
|
|
|
7837
|
-
export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createDefaultTextMeasurer, expandBox, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, parseDiagramDsl, parseEdgeShorthand, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runDagreInitialLayout, simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
|
|
8235
|
+
export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createDefaultTextMeasurer, expandBox, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, parseDiagramDsl, parseEdgeShorthand, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
|
|
7838
8236
|
//# sourceMappingURL=index.js.map
|
|
7839
8237
|
//# sourceMappingURL=index.js.map
|