@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.cjs
CHANGED
|
@@ -811,7 +811,6 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
811
811
|
const lock = locks.get(childId);
|
|
812
812
|
if (lock?.source === "fixed-position") {
|
|
813
813
|
unlocked.push({ id: childId, box });
|
|
814
|
-
locks.delete(childId);
|
|
815
814
|
continue;
|
|
816
815
|
}
|
|
817
816
|
diagnostics.push({
|
|
@@ -3984,6 +3983,213 @@ function isValidDimension(value) {
|
|
|
3984
3983
|
return Number.isFinite(value) && value >= 0;
|
|
3985
3984
|
}
|
|
3986
3985
|
|
|
3986
|
+
// src/routing/astar.ts
|
|
3987
|
+
function findObstacleFreePath(source, target, obstacles, options = {}) {
|
|
3988
|
+
const margin = options.margin ?? 0;
|
|
3989
|
+
const turnPenalty = options.turnPenalty ?? 50;
|
|
3990
|
+
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
3991
|
+
const endpointObstacles = options.endpointObstacles ?? [];
|
|
3992
|
+
const maxNodes = options.maxNodes ?? 4e3;
|
|
3993
|
+
const xs = collectXs(source, target, obstacles, margin);
|
|
3994
|
+
const ys = collectYs(source, target, obstacles, margin);
|
|
3995
|
+
if (xs.length * ys.length > maxNodes) {
|
|
3996
|
+
return null;
|
|
3997
|
+
}
|
|
3998
|
+
const { nodes, nodeIndex } = buildGraph(xs, ys);
|
|
3999
|
+
connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin);
|
|
4000
|
+
connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin);
|
|
4001
|
+
const path = aStarSearch(
|
|
4002
|
+
nodes,
|
|
4003
|
+
nodeIndex,
|
|
4004
|
+
source,
|
|
4005
|
+
target,
|
|
4006
|
+
turnPenalty,
|
|
4007
|
+
segmentPenalty
|
|
4008
|
+
);
|
|
4009
|
+
if (path === null) return null;
|
|
4010
|
+
return simplifyRoute(path);
|
|
4011
|
+
}
|
|
4012
|
+
function collectXs(source, target, obstacles, margin) {
|
|
4013
|
+
const set = /* @__PURE__ */ new Set();
|
|
4014
|
+
set.add(source.x);
|
|
4015
|
+
set.add(target.x);
|
|
4016
|
+
for (const obs of obstacles) {
|
|
4017
|
+
set.add(obs.x - margin - 2);
|
|
4018
|
+
set.add(obs.x + obs.width + margin + 2);
|
|
4019
|
+
}
|
|
4020
|
+
return [...set].sort((a, b) => a - b);
|
|
4021
|
+
}
|
|
4022
|
+
function collectYs(source, target, obstacles, margin) {
|
|
4023
|
+
const set = /* @__PURE__ */ new Set();
|
|
4024
|
+
set.add(source.y);
|
|
4025
|
+
set.add(target.y);
|
|
4026
|
+
for (const obs of obstacles) {
|
|
4027
|
+
set.add(obs.y - margin - 2);
|
|
4028
|
+
set.add(obs.y + obs.height + margin + 2);
|
|
4029
|
+
}
|
|
4030
|
+
return [...set].sort((a, b) => a - b);
|
|
4031
|
+
}
|
|
4032
|
+
function buildGraph(xs, ys) {
|
|
4033
|
+
const nodes = [];
|
|
4034
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
4035
|
+
for (let xi = 0; xi < xs.length; xi++) {
|
|
4036
|
+
for (let yi = 0; yi < ys.length; yi++) {
|
|
4037
|
+
const x = xs[xi];
|
|
4038
|
+
const y = ys[yi];
|
|
4039
|
+
const id = nodes.length;
|
|
4040
|
+
nodes.push({ x, y, id, neighbors: /* @__PURE__ */ new Map() });
|
|
4041
|
+
nodeIndex.set(`${x},${y}`, id);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
return { nodes, nodeIndex };
|
|
4045
|
+
}
|
|
4046
|
+
function connectHorizontalEdges(nodes, ys, obstacles, endpointObstacles, margin) {
|
|
4047
|
+
for (const y of ys) {
|
|
4048
|
+
const row = nodes.filter((n) => n.y === y).sort((a, b) => a.x - b.x);
|
|
4049
|
+
for (let i = 0; i < row.length - 1; i++) {
|
|
4050
|
+
const a = row[i];
|
|
4051
|
+
const b = row[i + 1];
|
|
4052
|
+
const dx = b.x - a.x;
|
|
4053
|
+
if (dx <= 0) continue;
|
|
4054
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4057
|
+
a.neighbors.set(b.id, dx);
|
|
4058
|
+
b.neighbors.set(a.id, dx);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
function connectVerticalEdges(nodes, xs, obstacles, endpointObstacles, margin) {
|
|
4063
|
+
for (const x of xs) {
|
|
4064
|
+
const col = nodes.filter((n) => n.x === x).sort((a, b) => a.y - b.y);
|
|
4065
|
+
for (let i = 0; i < col.length - 1; i++) {
|
|
4066
|
+
const a = col[i];
|
|
4067
|
+
const b = col[i + 1];
|
|
4068
|
+
const dy = b.y - a.y;
|
|
4069
|
+
if (dy <= 0) continue;
|
|
4070
|
+
if (segmentCrossesAny(a, b, obstacles, endpointObstacles, margin)) {
|
|
4071
|
+
continue;
|
|
4072
|
+
}
|
|
4073
|
+
a.neighbors.set(b.id, dy);
|
|
4074
|
+
b.neighbors.set(a.id, dy);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
function segmentCrossesAny(a, b, obstacles, endpointObstacles, margin) {
|
|
4079
|
+
for (const obs of obstacles) {
|
|
4080
|
+
if (segmentCrossesBoxStrict(a, b, obs, margin)) return true;
|
|
4081
|
+
}
|
|
4082
|
+
for (const ep of endpointObstacles) {
|
|
4083
|
+
if (segmentCrossesBoxStrict(a, b, ep, margin)) return true;
|
|
4084
|
+
}
|
|
4085
|
+
return false;
|
|
4086
|
+
}
|
|
4087
|
+
function segmentCrossesBoxStrict(start, end, box, margin) {
|
|
4088
|
+
const left = box.x - margin;
|
|
4089
|
+
const right = box.x + box.width + margin;
|
|
4090
|
+
const top = box.y - margin;
|
|
4091
|
+
const bottom = box.y + box.height + margin;
|
|
4092
|
+
if (pointInsideStrict(start, left, right, top, bottom)) return true;
|
|
4093
|
+
if (pointInsideStrict(end, left, right, top, bottom)) return true;
|
|
4094
|
+
if (start.x === end.x) {
|
|
4095
|
+
return start.x >= left && start.x <= right && rangesOverlap(start.y, end.y, top, bottom);
|
|
4096
|
+
}
|
|
4097
|
+
if (start.y === end.y) {
|
|
4098
|
+
return start.y >= top && start.y <= bottom && rangesOverlap(start.x, end.x, left, right);
|
|
4099
|
+
}
|
|
4100
|
+
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);
|
|
4101
|
+
}
|
|
4102
|
+
function pointInsideStrict(p, left, right, top, bottom) {
|
|
4103
|
+
return p.x > left && p.x < right && p.y > top && p.y < bottom;
|
|
4104
|
+
}
|
|
4105
|
+
function rangesOverlap(a, b, min, max) {
|
|
4106
|
+
const low = Math.min(a, b);
|
|
4107
|
+
const high = Math.max(a, b);
|
|
4108
|
+
return high > min && low < max;
|
|
4109
|
+
}
|
|
4110
|
+
function segmentEdgeIntersect(start, end, x1, y1, x2, y2) {
|
|
4111
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
4112
|
+
if (denominator === 0) return false;
|
|
4113
|
+
const t = ((start.x - x1) * (y2 - y1) - (start.y - y1) * (x2 - x1)) / denominator;
|
|
4114
|
+
const u = ((start.x - x1) * (end.y - start.y) - (start.y - y1) * (end.x - start.x)) / denominator;
|
|
4115
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
4116
|
+
}
|
|
4117
|
+
function aStarSearch(nodes, nodeIndex, source, target, turnPenalty, segmentPenalty) {
|
|
4118
|
+
const startId = nodeIndex.get(`${source.x},${source.y}`);
|
|
4119
|
+
const goalId = nodeIndex.get(`${target.x},${target.y}`);
|
|
4120
|
+
if (startId === void 0 || goalId === void 0) return null;
|
|
4121
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
4122
|
+
gScore.set(startId, 0);
|
|
4123
|
+
const cameFrom = /* @__PURE__ */ new Map();
|
|
4124
|
+
const cameFromDir = /* @__PURE__ */ new Map();
|
|
4125
|
+
const openSet = [];
|
|
4126
|
+
openSet.push({
|
|
4127
|
+
id: startId,
|
|
4128
|
+
f: manhattan(source, target)
|
|
4129
|
+
});
|
|
4130
|
+
while (openSet.length > 0) {
|
|
4131
|
+
let bestIdx = 0;
|
|
4132
|
+
for (let i = 1; i < openSet.length; i++) {
|
|
4133
|
+
if (openSet[i].f < openSet[bestIdx].f) {
|
|
4134
|
+
bestIdx = i;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
const current = openSet.splice(bestIdx, 1)[0];
|
|
4138
|
+
if (current.id === goalId) {
|
|
4139
|
+
return reconstructPath(nodes, cameFrom, goalId);
|
|
4140
|
+
}
|
|
4141
|
+
const node = nodes[current.id];
|
|
4142
|
+
const currentG = gScore.get(current.id);
|
|
4143
|
+
const prevDir = cameFromDir.get(current.id);
|
|
4144
|
+
for (const [neighborId, edgeCost] of node.neighbors) {
|
|
4145
|
+
const neighbor = nodes[neighborId];
|
|
4146
|
+
const tentativeG = currentG + edgeCost * segmentPenalty;
|
|
4147
|
+
const newDir = neighbor.y === node.y ? "h" : "v";
|
|
4148
|
+
const turnCost = prevDir !== void 0 && prevDir !== newDir ? turnPenalty : 0;
|
|
4149
|
+
const totalG = tentativeG + turnCost;
|
|
4150
|
+
const existingG = gScore.get(neighborId);
|
|
4151
|
+
if (existingG === void 0 || totalG < existingG) {
|
|
4152
|
+
gScore.set(neighborId, totalG);
|
|
4153
|
+
cameFrom.set(neighborId, current.id);
|
|
4154
|
+
cameFromDir.set(neighborId, newDir);
|
|
4155
|
+
const f = totalG + manhattan(neighbor, target);
|
|
4156
|
+
openSet.push({ id: neighborId, f });
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
return null;
|
|
4161
|
+
}
|
|
4162
|
+
function manhattan(a, b) {
|
|
4163
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
4164
|
+
}
|
|
4165
|
+
function reconstructPath(nodes, cameFrom, goalId) {
|
|
4166
|
+
const path = [];
|
|
4167
|
+
let current = goalId;
|
|
4168
|
+
while (current !== void 0) {
|
|
4169
|
+
const node = nodes[current];
|
|
4170
|
+
path.unshift({ x: node.x, y: node.y });
|
|
4171
|
+
current = cameFrom.get(current);
|
|
4172
|
+
}
|
|
4173
|
+
return path;
|
|
4174
|
+
}
|
|
4175
|
+
function simplifyRoute(points) {
|
|
4176
|
+
if (points.length <= 2) return [...points];
|
|
4177
|
+
const result = [points[0]];
|
|
4178
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
4179
|
+
const prev = result[result.length - 1];
|
|
4180
|
+
const curr = points[i];
|
|
4181
|
+
const next = points[i + 1];
|
|
4182
|
+
if (!areCollinear(prev, curr, next)) {
|
|
4183
|
+
result.push(curr);
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
result.push(points[points.length - 1]);
|
|
4187
|
+
return result;
|
|
4188
|
+
}
|
|
4189
|
+
function areCollinear(a, b, c) {
|
|
4190
|
+
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4191
|
+
}
|
|
4192
|
+
|
|
3987
4193
|
// src/routing/routes.ts
|
|
3988
4194
|
function routeEdge(input) {
|
|
3989
4195
|
const diagnostics = [];
|
|
@@ -4029,6 +4235,43 @@ function routeEdge(input) {
|
|
|
4029
4235
|
}
|
|
4030
4236
|
return { points, diagnostics };
|
|
4031
4237
|
}
|
|
4238
|
+
if ((input.kind ?? "orthogonal") === "obstacle-avoiding") {
|
|
4239
|
+
const endpointObstacles = endpointObstaclesForAutoAnchors(input);
|
|
4240
|
+
for (const { sourceAnchor, targetAnchor } of routeAnchorPairs(
|
|
4241
|
+
input,
|
|
4242
|
+
defaultAnchors
|
|
4243
|
+
)) {
|
|
4244
|
+
const source = getEdgePort(
|
|
4245
|
+
input.source,
|
|
4246
|
+
input.target.center,
|
|
4247
|
+
sourceAnchor
|
|
4248
|
+
);
|
|
4249
|
+
const target = getEdgePort(
|
|
4250
|
+
input.target,
|
|
4251
|
+
input.source.center,
|
|
4252
|
+
targetAnchor
|
|
4253
|
+
);
|
|
4254
|
+
const path = findObstacleFreePath(
|
|
4255
|
+
source,
|
|
4256
|
+
target,
|
|
4257
|
+
[...softObstacles, ...hardObstacles],
|
|
4258
|
+
{
|
|
4259
|
+
endpointObstacles
|
|
4260
|
+
}
|
|
4261
|
+
);
|
|
4262
|
+
if (path !== null && path.length >= 2) {
|
|
4263
|
+
const finalized = finalizeRoute(
|
|
4264
|
+
path,
|
|
4265
|
+
softObstacles,
|
|
4266
|
+
hardObstacles,
|
|
4267
|
+
diagnostics
|
|
4268
|
+
);
|
|
4269
|
+
if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
|
|
4270
|
+
return { points: finalized, diagnostics };
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4032
4275
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
4033
4276
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
4034
4277
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -4231,7 +4474,7 @@ function routeEdge(input) {
|
|
|
4231
4474
|
};
|
|
4232
4475
|
}
|
|
4233
4476
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
4234
|
-
const simplified =
|
|
4477
|
+
const simplified = simplifyRoute2(points);
|
|
4235
4478
|
if (simplified.length >= 3) {
|
|
4236
4479
|
return simplified;
|
|
4237
4480
|
}
|
|
@@ -4519,7 +4762,7 @@ function squaredDistance2(a, b) {
|
|
|
4519
4762
|
const dy = a.y - b.y;
|
|
4520
4763
|
return dx * dx + dy * dy;
|
|
4521
4764
|
}
|
|
4522
|
-
function
|
|
4765
|
+
function simplifyRoute2(points) {
|
|
4523
4766
|
const withoutDuplicates = [];
|
|
4524
4767
|
for (const point2 of points) {
|
|
4525
4768
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -4531,7 +4774,7 @@ function simplifyRoute(points) {
|
|
|
4531
4774
|
for (const point2 of withoutDuplicates) {
|
|
4532
4775
|
const previous = simplified.at(-1);
|
|
4533
4776
|
const beforePrevious = simplified.at(-2);
|
|
4534
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4777
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
4535
4778
|
simplified[simplified.length - 1] = { ...point2 };
|
|
4536
4779
|
} else {
|
|
4537
4780
|
simplified.push({ ...point2 });
|
|
@@ -4746,17 +4989,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4746
4989
|
return true;
|
|
4747
4990
|
}
|
|
4748
4991
|
if (start.x === end.x) {
|
|
4749
|
-
return start.x > left && start.x < right &&
|
|
4992
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4750
4993
|
}
|
|
4751
4994
|
if (start.y === end.y) {
|
|
4752
|
-
return start.y > top && start.y < bottom &&
|
|
4995
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4753
4996
|
}
|
|
4754
4997
|
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);
|
|
4755
4998
|
}
|
|
4756
4999
|
function pointInsideBox(point2, box) {
|
|
4757
5000
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4758
5001
|
}
|
|
4759
|
-
function
|
|
5002
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4760
5003
|
const low = Math.min(a, b);
|
|
4761
5004
|
const high = Math.max(a, b);
|
|
4762
5005
|
return high > min && low < max;
|
|
@@ -4780,7 +5023,7 @@ function segmentBox(a, b) {
|
|
|
4780
5023
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4781
5024
|
};
|
|
4782
5025
|
}
|
|
4783
|
-
function
|
|
5026
|
+
function areCollinear2(a, b, c) {
|
|
4784
5027
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4785
5028
|
}
|
|
4786
5029
|
|
|
@@ -4860,6 +5103,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4860
5103
|
options,
|
|
4861
5104
|
diagnostics
|
|
4862
5105
|
);
|
|
5106
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4863
5107
|
const constrained = applyLayoutConstraints({
|
|
4864
5108
|
direction: diagram.direction,
|
|
4865
5109
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4923,6 +5167,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4923
5167
|
swimlanes: coordinatedSwimlanes,
|
|
4924
5168
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4925
5169
|
});
|
|
5170
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
5171
|
+
styledEdges,
|
|
5172
|
+
nodeGeometryById,
|
|
5173
|
+
options.textMeasurer
|
|
5174
|
+
);
|
|
4926
5175
|
const layoutBoxes = [
|
|
4927
5176
|
...coordinatedNodes.map((node) => node.box),
|
|
4928
5177
|
...coordinatedNodes.flatMap(
|
|
@@ -5003,7 +5252,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5003
5252
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
5004
5253
|
const routingTextObstacles = [
|
|
5005
5254
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
5006
|
-
...frameTextAnnotation.filter(isPreRouteTextObstacle)
|
|
5255
|
+
...frameTextAnnotation.filter(isPreRouteTextObstacle),
|
|
5256
|
+
// Dry-run edge-label estimates so edges route around
|
|
5257
|
+
// each other's label areas (Issue #41).
|
|
5258
|
+
...edgeLabelEstimates
|
|
5007
5259
|
];
|
|
5008
5260
|
const margin = options.obstacleMargin ?? 0;
|
|
5009
5261
|
const softObstacles = [
|
|
@@ -5036,7 +5288,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5036
5288
|
hardObstacles,
|
|
5037
5289
|
diagram.direction,
|
|
5038
5290
|
options,
|
|
5039
|
-
diagnostics
|
|
5291
|
+
diagnostics,
|
|
5292
|
+
coordinatedGroups
|
|
5040
5293
|
);
|
|
5041
5294
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
5042
5295
|
coordinatedEdges,
|
|
@@ -5152,22 +5405,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
5152
5405
|
y: layout2.box.y + offsetY,
|
|
5153
5406
|
width: layout2.box.width,
|
|
5154
5407
|
height: layout2.box.height
|
|
5155
|
-
}
|
|
5156
|
-
contentBox: {
|
|
5157
|
-
x: layout2.contentBox.x + offsetX,
|
|
5158
|
-
y: layout2.contentBox.y + offsetY,
|
|
5159
|
-
width: layout2.contentBox.width,
|
|
5160
|
-
height: layout2.contentBox.height
|
|
5161
|
-
},
|
|
5162
|
-
lines: layout2.lines.map((line) => ({
|
|
5163
|
-
...line,
|
|
5164
|
-
box: {
|
|
5165
|
-
x: line.box.x + offsetX,
|
|
5166
|
-
y: line.box.y + offsetY,
|
|
5167
|
-
width: line.box.width,
|
|
5168
|
-
height: line.box.height
|
|
5169
|
-
}
|
|
5170
|
-
}))
|
|
5408
|
+
}
|
|
5171
5409
|
};
|
|
5172
5410
|
}
|
|
5173
5411
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -6114,6 +6352,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6114
6352
|
});
|
|
6115
6353
|
continue;
|
|
6116
6354
|
}
|
|
6355
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
6117
6356
|
const geometry = computeShapeGeometry({
|
|
6118
6357
|
shape: node.shape,
|
|
6119
6358
|
box,
|
|
@@ -6123,7 +6362,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6123
6362
|
id: node.id,
|
|
6124
6363
|
...node.label === void 0 ? {} : { label: node.label },
|
|
6125
6364
|
...node.style === void 0 ? {} : { style: node.style },
|
|
6126
|
-
...
|
|
6365
|
+
...ports === void 0 ? {} : { ports },
|
|
6127
6366
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
6128
6367
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
6129
6368
|
shape: node.shape,
|
|
@@ -6135,6 +6374,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6135
6374
|
}
|
|
6136
6375
|
return coordinated;
|
|
6137
6376
|
}
|
|
6377
|
+
var PORT_BOX_SIZE = 10;
|
|
6378
|
+
var MIN_PORT_EDGE_GAP = 12;
|
|
6379
|
+
function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
6380
|
+
const shiftingEnabled = options.portShifting?.enabled ?? true;
|
|
6381
|
+
if (!shiftingEnabled) return;
|
|
6382
|
+
const requestedSpacing = options.portShifting?.spacing ?? 24;
|
|
6383
|
+
const minSpacing = Math.max(
|
|
6384
|
+
requestedSpacing,
|
|
6385
|
+
PORT_BOX_SIZE + MIN_PORT_EDGE_GAP
|
|
6386
|
+
);
|
|
6387
|
+
for (const node of nodes) {
|
|
6388
|
+
if (node.ports === void 0 || node.ports.length === 0) continue;
|
|
6389
|
+
const box = boxes.get(node.id);
|
|
6390
|
+
if (box === void 0) continue;
|
|
6391
|
+
let heightExpansion = 0;
|
|
6392
|
+
let widthExpansion = 0;
|
|
6393
|
+
const portsBySide = /* @__PURE__ */ new Map();
|
|
6394
|
+
for (const port of node.ports) {
|
|
6395
|
+
const list = portsBySide.get(port.side) ?? [];
|
|
6396
|
+
list.push(port);
|
|
6397
|
+
portsBySide.set(port.side, list);
|
|
6398
|
+
}
|
|
6399
|
+
for (const [side, ports] of portsBySide) {
|
|
6400
|
+
const count = (ports ?? []).length;
|
|
6401
|
+
if (count <= 1) continue;
|
|
6402
|
+
const isVertical = side === "left" || side === "right";
|
|
6403
|
+
const availableSpan = isVertical ? box.height : box.width;
|
|
6404
|
+
const requiredSpan = (count - 1) * minSpacing + PORT_BOX_SIZE;
|
|
6405
|
+
if (requiredSpan > availableSpan) {
|
|
6406
|
+
const expansion = requiredSpan - availableSpan;
|
|
6407
|
+
if (isVertical) {
|
|
6408
|
+
heightExpansion = Math.max(heightExpansion, expansion);
|
|
6409
|
+
} else {
|
|
6410
|
+
widthExpansion = Math.max(widthExpansion, expansion);
|
|
6411
|
+
}
|
|
6412
|
+
diagnostics.push({
|
|
6413
|
+
severity: "info",
|
|
6414
|
+
code: "port_capacity_overflow",
|
|
6415
|
+
message: `Expanded node ${node.id} ${isVertical ? "height" : "width"} by ${Math.ceil(expansion)} px to fit ${count} port(s) on ${side} side.`,
|
|
6416
|
+
path: ["nodes", node.id, "ports"],
|
|
6417
|
+
detail: {
|
|
6418
|
+
nodeId: node.id,
|
|
6419
|
+
side,
|
|
6420
|
+
portCount: count,
|
|
6421
|
+
expansion: Math.ceil(expansion)
|
|
6422
|
+
}
|
|
6423
|
+
});
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
if (heightExpansion > 0) {
|
|
6427
|
+
box.y -= heightExpansion / 2;
|
|
6428
|
+
box.height += heightExpansion;
|
|
6429
|
+
}
|
|
6430
|
+
if (widthExpansion > 0) {
|
|
6431
|
+
box.x -= widthExpansion / 2;
|
|
6432
|
+
box.width += widthExpansion;
|
|
6433
|
+
}
|
|
6434
|
+
if ((heightExpansion > 0 || widthExpansion > 0) && node.labelLayout !== void 0) {
|
|
6435
|
+
const layout2 = node.labelLayout;
|
|
6436
|
+
const newOffsetX = Math.max(0, (box.width - layout2.box.width) / 2);
|
|
6437
|
+
const newOffsetY = Math.max(0, (box.height - layout2.box.height) / 2);
|
|
6438
|
+
node.labelLayout = {
|
|
6439
|
+
...layout2,
|
|
6440
|
+
box: {
|
|
6441
|
+
...layout2.box,
|
|
6442
|
+
x: newOffsetX,
|
|
6443
|
+
y: newOffsetY
|
|
6444
|
+
}
|
|
6445
|
+
};
|
|
6446
|
+
}
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6138
6449
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
6139
6450
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
6140
6451
|
for (const port of node.ports ?? []) {
|
|
@@ -6171,7 +6482,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6171
6482
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
6172
6483
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
6173
6484
|
const availableSpan = 2 * maxOffset;
|
|
6174
|
-
const
|
|
6485
|
+
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
6486
|
+
const spacing = shiftingEnabled && count > 1 ? Math.max(
|
|
6487
|
+
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
6488
|
+
minSpacing
|
|
6489
|
+
) : requestedSpacing;
|
|
6175
6490
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
6176
6491
|
switch (side) {
|
|
6177
6492
|
case "left":
|
|
@@ -6197,7 +6512,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6197
6512
|
}
|
|
6198
6513
|
}
|
|
6199
6514
|
function portBox(anchor) {
|
|
6200
|
-
const size =
|
|
6515
|
+
const size = PORT_BOX_SIZE;
|
|
6201
6516
|
return {
|
|
6202
6517
|
x: anchor.x - size / 2,
|
|
6203
6518
|
y: anchor.y - size / 2,
|
|
@@ -6786,7 +7101,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6786
7101
|
}
|
|
6787
7102
|
};
|
|
6788
7103
|
}
|
|
6789
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
|
|
7104
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
6790
7105
|
const coordinated = [];
|
|
6791
7106
|
const coordinatedNodeById = new Map(
|
|
6792
7107
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6810,8 +7125,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6810
7125
|
}
|
|
6811
7126
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6812
7127
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6813
|
-
const
|
|
6814
|
-
const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
|
|
7128
|
+
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
6815
7129
|
const route = routeEdge({
|
|
6816
7130
|
kind: options.routeKind ?? "orthogonal",
|
|
6817
7131
|
direction,
|
|
@@ -6824,6 +7138,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6824
7138
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6825
7139
|
),
|
|
6826
7140
|
...softObstacles,
|
|
7141
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6827
7142
|
...routeTextObstacles
|
|
6828
7143
|
],
|
|
6829
7144
|
hardObstacles,
|
|
@@ -6842,15 +7157,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6842
7157
|
}
|
|
6843
7158
|
return coordinated;
|
|
6844
7159
|
}
|
|
6845
|
-
function
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
7160
|
+
function isEdgeConnectedTextAnnotation(edge, annotation) {
|
|
7161
|
+
switch (annotation.surfaceKind) {
|
|
7162
|
+
case "edge-label":
|
|
7163
|
+
return annotation.ownerId === edge.id;
|
|
7164
|
+
case "node-label":
|
|
7165
|
+
case "compartment-row":
|
|
7166
|
+
return annotation.ownerId === edge.source.nodeId || annotation.ownerId === edge.target.nodeId;
|
|
7167
|
+
case "port-label":
|
|
7168
|
+
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}`;
|
|
7169
|
+
case "group-label":
|
|
7170
|
+
case "swimlane-label":
|
|
7171
|
+
case "frame-title":
|
|
7172
|
+
return false;
|
|
6849
7173
|
}
|
|
6850
|
-
|
|
6851
|
-
|
|
7174
|
+
}
|
|
7175
|
+
function ancestorGroupIds(groups, nodeId) {
|
|
7176
|
+
const direct = /* @__PURE__ */ new Set();
|
|
7177
|
+
for (const group of groups) {
|
|
7178
|
+
if (group.nodeIds.includes(nodeId)) {
|
|
7179
|
+
direct.add(group.id);
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
let previousSize = -1;
|
|
7183
|
+
const ancestors = new Set(direct);
|
|
7184
|
+
while (ancestors.size !== previousSize) {
|
|
7185
|
+
previousSize = ancestors.size;
|
|
7186
|
+
for (const group of groups) {
|
|
7187
|
+
for (const candidate of ancestors) {
|
|
7188
|
+
if (group.groupIds.includes(candidate)) {
|
|
7189
|
+
ancestors.add(group.id);
|
|
7190
|
+
break;
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
}
|
|
6852
7194
|
}
|
|
6853
|
-
return
|
|
7195
|
+
return ancestors;
|
|
7196
|
+
}
|
|
7197
|
+
function groupObstaclesForEdge(edge, groups, margin) {
|
|
7198
|
+
const sourceAncestors = ancestorGroupIds(groups, edge.source.nodeId);
|
|
7199
|
+
const targetAncestors = ancestorGroupIds(groups, edge.target.nodeId);
|
|
7200
|
+
return groups.filter((group) => {
|
|
7201
|
+
if (sourceAncestors.has(group.id) || targetAncestors.has(group.id)) {
|
|
7202
|
+
return false;
|
|
7203
|
+
}
|
|
7204
|
+
return true;
|
|
7205
|
+
}).map((group) => margin === 0 ? group.box : expandBox(group.box, margin));
|
|
6854
7206
|
}
|
|
6855
7207
|
function coordinateBaseTextAnnotations(input) {
|
|
6856
7208
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -7038,6 +7390,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
7038
7390
|
}
|
|
7039
7391
|
return annotations;
|
|
7040
7392
|
}
|
|
7393
|
+
function estimateEdgeLabelAnnotations(edges, nodes, textMeasurer) {
|
|
7394
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
7395
|
+
const annotations = [];
|
|
7396
|
+
for (const edge of edges) {
|
|
7397
|
+
if (edge.label?.text === void 0) {
|
|
7398
|
+
continue;
|
|
7399
|
+
}
|
|
7400
|
+
const sourceGeom = nodes.get(edge.source.nodeId);
|
|
7401
|
+
const targetGeom = nodes.get(edge.target.nodeId);
|
|
7402
|
+
if (sourceGeom === void 0 || targetGeom === void 0) {
|
|
7403
|
+
continue;
|
|
7404
|
+
}
|
|
7405
|
+
const layout2 = fitLabel(
|
|
7406
|
+
edge.label.text,
|
|
7407
|
+
{
|
|
7408
|
+
font: typographyTextStyle(edge.label, {
|
|
7409
|
+
fontFamily: "Arial",
|
|
7410
|
+
fontSize: 12,
|
|
7411
|
+
lineHeight: 14
|
|
7412
|
+
}),
|
|
7413
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
7414
|
+
minSize: { width: 0, height: 0 },
|
|
7415
|
+
maxWidth: 200
|
|
7416
|
+
},
|
|
7417
|
+
measurer
|
|
7418
|
+
);
|
|
7419
|
+
const cx = (sourceGeom.center.x + targetGeom.center.x) / 2;
|
|
7420
|
+
const cy = (sourceGeom.center.y + targetGeom.center.y) / 2;
|
|
7421
|
+
const box = {
|
|
7422
|
+
x: cx - layout2.box.width / 2,
|
|
7423
|
+
y: cy - layout2.box.height / 2,
|
|
7424
|
+
width: layout2.box.width,
|
|
7425
|
+
height: layout2.box.height
|
|
7426
|
+
};
|
|
7427
|
+
annotations.push({
|
|
7428
|
+
text: layout2.text,
|
|
7429
|
+
ownerId: edge.id,
|
|
7430
|
+
surfaceKind: "edge-label",
|
|
7431
|
+
box,
|
|
7432
|
+
anchor: { x: cx, y: cy },
|
|
7433
|
+
paddings: layout2.padding,
|
|
7434
|
+
lines: layout2.lines,
|
|
7435
|
+
fontFamily: normalizeOutputFontFamily(layout2.font),
|
|
7436
|
+
fontSize: layout2.font.fontSize,
|
|
7437
|
+
textBackend: layout2.textBackend
|
|
7438
|
+
});
|
|
7439
|
+
}
|
|
7440
|
+
return annotations;
|
|
7441
|
+
}
|
|
7041
7442
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
7042
7443
|
const layout2 = fitLabel(
|
|
7043
7444
|
frame.titleTab,
|
|
@@ -7158,9 +7559,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7158
7559
|
const diagnostics = [];
|
|
7159
7560
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
7160
7561
|
for (const edge of edges) {
|
|
7161
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
7162
7562
|
for (const annotation of relevantAnnotations) {
|
|
7163
|
-
if (
|
|
7563
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
7164
7564
|
continue;
|
|
7165
7565
|
}
|
|
7166
7566
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -7184,9 +7584,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7184
7584
|
return diagnostics;
|
|
7185
7585
|
}
|
|
7186
7586
|
function isPreRouteTextObstacle(annotation) {
|
|
7187
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
7188
|
-
return false;
|
|
7189
|
-
}
|
|
7190
7587
|
return isRouteClearanceText(annotation);
|
|
7191
7588
|
}
|
|
7192
7589
|
function isRouteClearanceText(annotation) {
|
|
@@ -7197,8 +7594,9 @@ function isRouteClearanceText(annotation) {
|
|
|
7197
7594
|
case "frame-title":
|
|
7198
7595
|
return true;
|
|
7199
7596
|
case "node-label":
|
|
7200
|
-
case "group-label":
|
|
7201
7597
|
case "compartment-row":
|
|
7598
|
+
return true;
|
|
7599
|
+
case "group-label":
|
|
7202
7600
|
return textExtendsOutsideAnchor(annotation);
|
|
7203
7601
|
}
|
|
7204
7602
|
}
|
|
@@ -7231,17 +7629,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
7231
7629
|
return true;
|
|
7232
7630
|
}
|
|
7233
7631
|
if (start.x === end.x) {
|
|
7234
|
-
return start.x > left && start.x < right &&
|
|
7632
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
7235
7633
|
}
|
|
7236
7634
|
if (start.y === end.y) {
|
|
7237
|
-
return start.y > top && start.y < bottom &&
|
|
7635
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
7238
7636
|
}
|
|
7239
7637
|
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);
|
|
7240
7638
|
}
|
|
7241
7639
|
function pointInsideBox2(point2, box) {
|
|
7242
7640
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
7243
7641
|
}
|
|
7244
|
-
function
|
|
7642
|
+
function rangesOverlap3(a, b, min, max) {
|
|
7245
7643
|
const low = Math.min(a, b);
|
|
7246
7644
|
const high = Math.max(a, b);
|
|
7247
7645
|
return high > min && low < max;
|
|
@@ -7869,7 +8267,7 @@ exports.resolveLineHeight = resolveLineHeight;
|
|
|
7869
8267
|
exports.resolveOutputFormat = resolveOutputFormat;
|
|
7870
8268
|
exports.routeEdge = routeEdge;
|
|
7871
8269
|
exports.runDagreInitialLayout = runDagreInitialLayout;
|
|
7872
|
-
exports.simplifyRoute =
|
|
8270
|
+
exports.simplifyRoute = simplifyRoute2;
|
|
7873
8271
|
exports.solveDiagram = solveDiagram;
|
|
7874
8272
|
exports.solveDiagramSafe = solveDiagramSafe;
|
|
7875
8273
|
exports.sortDslDiagnostics = sortDslDiagnostics;
|