@crazyhappyone/auto-graph 0.1.3 → 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 +588 -60
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +588 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +589 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +589 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -99,6 +99,9 @@ function applyLayoutConstraints(input) {
|
|
|
99
99
|
const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
|
|
100
100
|
applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
|
|
101
101
|
applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
|
|
102
|
+
if (input.distributeContainedChildren) {
|
|
103
|
+
yieldFixedPositionLocks(input, boxes, locks);
|
|
104
|
+
}
|
|
102
105
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
103
106
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
104
107
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -112,6 +115,13 @@ function applyLayoutConstraints(input) {
|
|
|
112
115
|
);
|
|
113
116
|
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
114
117
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
118
|
+
if (input.distributeContainedChildren) {
|
|
119
|
+
const diagBefore = diagnostics.length;
|
|
120
|
+
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
121
|
+
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
122
|
+
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
123
|
+
}
|
|
124
|
+
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
115
125
|
reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
|
|
116
126
|
reportIntraContainerOverflow(input, boxes, diagnostics);
|
|
117
127
|
return { boxes, locks, diagnostics };
|
|
@@ -157,6 +167,62 @@ function applyFixedPositionLocks(nodes, boxes, locks, diagnostics) {
|
|
|
157
167
|
locks.set(node.id, { nodeId: node.id, source: "fixed-position" });
|
|
158
168
|
}
|
|
159
169
|
}
|
|
170
|
+
function dedupReplayDiagnostics(diagnostics, keepUpTo) {
|
|
171
|
+
const seen = /* @__PURE__ */ new Set();
|
|
172
|
+
for (let i = 0; i < keepUpTo && i < diagnostics.length; i += 1) {
|
|
173
|
+
const d = diagnostics[i];
|
|
174
|
+
if (d === void 0) continue;
|
|
175
|
+
seen.add(diagnosticFingerprint(d));
|
|
176
|
+
}
|
|
177
|
+
for (let i = diagnostics.length - 1; i >= keepUpTo; i -= 1) {
|
|
178
|
+
const d = diagnostics[i];
|
|
179
|
+
if (d === void 0) continue;
|
|
180
|
+
const fp = diagnosticFingerprint(d);
|
|
181
|
+
if (seen.has(fp)) {
|
|
182
|
+
diagnostics.splice(i, 1);
|
|
183
|
+
} else {
|
|
184
|
+
seen.add(fp);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function diagnosticFingerprint(d) {
|
|
189
|
+
const nodeId = typeof d.detail?.nodeId === "string" ? d.detail.nodeId : "";
|
|
190
|
+
const containerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : "";
|
|
191
|
+
return `${d.code}|${nodeId}|${containerId}`;
|
|
192
|
+
}
|
|
193
|
+
function yieldFixedPositionLocks(input, boxes, locks) {
|
|
194
|
+
for (const c of input.constraints) {
|
|
195
|
+
if (c.kind !== "containment") continue;
|
|
196
|
+
const container = boxes.get(c.containerId);
|
|
197
|
+
if (container === void 0) continue;
|
|
198
|
+
const content = contentBox(container, c.padding);
|
|
199
|
+
const mainAxis = input.direction === "LR" || input.direction === "RL" ? "width" : "height";
|
|
200
|
+
const crossAxis = mainAxis === "width" ? "height" : "width";
|
|
201
|
+
let eligible = 0;
|
|
202
|
+
for (const childId of c.childIds) {
|
|
203
|
+
const box = boxes.get(childId);
|
|
204
|
+
if (box === void 0) continue;
|
|
205
|
+
const lock = locks.get(childId);
|
|
206
|
+
if (lock?.source === "exact-position") continue;
|
|
207
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
208
|
+
if (fits) {
|
|
209
|
+
eligible += 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (eligible < 2) continue;
|
|
213
|
+
for (const childId of c.childIds) {
|
|
214
|
+
const lock = locks.get(childId);
|
|
215
|
+
if (lock?.source === "fixed-position") {
|
|
216
|
+
const box = boxes.get(childId);
|
|
217
|
+
if (box === void 0) continue;
|
|
218
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
219
|
+
if (fits) {
|
|
220
|
+
locks.delete(childId);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
160
226
|
function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
|
|
161
227
|
for (const constraint of constraints) {
|
|
162
228
|
if (constraint.kind !== "exact-position") {
|
|
@@ -229,7 +295,7 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
|
|
|
229
295
|
code: "constraints.locked-target-not-moved",
|
|
230
296
|
message: `Locked child ${childId} was not moved into containment.`,
|
|
231
297
|
path: ["constraints", constraint.id ?? constraint.containerId],
|
|
232
|
-
detail: { nodeId: childId }
|
|
298
|
+
detail: { nodeId: childId, containerId: constraint.containerId }
|
|
233
299
|
});
|
|
234
300
|
if (!isInside(child, content)) {
|
|
235
301
|
diagnostics.push({
|
|
@@ -381,6 +447,60 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
|
|
|
381
447
|
}
|
|
382
448
|
reportOverlaps(boxes, diagnostics, ignoredPairs);
|
|
383
449
|
}
|
|
450
|
+
function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
|
|
451
|
+
for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
|
|
452
|
+
const d = diagnostics[i];
|
|
453
|
+
if (d === void 0) continue;
|
|
454
|
+
if (d.code === "constraints.overlap.unresolved") {
|
|
455
|
+
const aId = d.detail?.firstId;
|
|
456
|
+
const bId = d.detail?.secondId;
|
|
457
|
+
if (typeof aId !== "string" || typeof bId !== "string") continue;
|
|
458
|
+
const a = boxes.get(aId);
|
|
459
|
+
const b = boxes.get(bId);
|
|
460
|
+
if (a !== void 0 && b !== void 0 && !intersectsAabb(a, b)) {
|
|
461
|
+
diagnostics.splice(i, 1);
|
|
462
|
+
}
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (d.code === "constraints.containment.impossible" || d.code === "constraints.locked-target-not-moved" && typeof d.message === "string" && d.message.includes("not moved into containment")) {
|
|
466
|
+
const nodeId = d.detail?.nodeId;
|
|
467
|
+
if (typeof nodeId !== "string") continue;
|
|
468
|
+
const child = boxes.get(nodeId);
|
|
469
|
+
if (child === void 0) continue;
|
|
470
|
+
const diagContainerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : void 0;
|
|
471
|
+
let resolved = false;
|
|
472
|
+
for (const c of constraints) {
|
|
473
|
+
if (c.kind !== "containment") continue;
|
|
474
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
475
|
+
if (diagContainerId !== void 0 && c.containerId !== diagContainerId) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const container = boxes.get(c.containerId);
|
|
479
|
+
if (container === void 0) continue;
|
|
480
|
+
const content = contentBox(container, c.padding);
|
|
481
|
+
if (isInside(child, content)) {
|
|
482
|
+
diagnostics.splice(i, 1);
|
|
483
|
+
resolved = true;
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
if (!resolved && diagContainerId !== void 0) {
|
|
488
|
+
for (const c of constraints) {
|
|
489
|
+
if (c.kind !== "containment") continue;
|
|
490
|
+
if (c.containerId !== diagContainerId) continue;
|
|
491
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
492
|
+
const container = boxes.get(c.containerId);
|
|
493
|
+
if (container === void 0) continue;
|
|
494
|
+
const content = contentBox(container, c.padding);
|
|
495
|
+
if (isInside(child, content)) {
|
|
496
|
+
diagnostics.splice(i, 1);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
384
504
|
function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
|
|
385
505
|
const ids = [...boxes.keys()].sort();
|
|
386
506
|
const reported = new Set(
|
|
@@ -688,6 +808,11 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
688
808
|
continue;
|
|
689
809
|
}
|
|
690
810
|
if (locks.has(childId)) {
|
|
811
|
+
const lock = locks.get(childId);
|
|
812
|
+
if (lock?.source === "fixed-position") {
|
|
813
|
+
unlocked.push({ id: childId, box });
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
691
816
|
diagnostics.push({
|
|
692
817
|
severity: "warning",
|
|
693
818
|
code: "constraints.locked-target-not-moved",
|
|
@@ -746,6 +871,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
746
871
|
});
|
|
747
872
|
}
|
|
748
873
|
boxes.set(child.id, clamped);
|
|
874
|
+
locks.delete(child.id);
|
|
749
875
|
pos = clamped[axis] + clamped[mainSize] + minGap;
|
|
750
876
|
}
|
|
751
877
|
diagnostics.push({
|
|
@@ -3857,6 +3983,213 @@ function isValidDimension(value) {
|
|
|
3857
3983
|
return Number.isFinite(value) && value >= 0;
|
|
3858
3984
|
}
|
|
3859
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
|
+
|
|
3860
4193
|
// src/routing/routes.ts
|
|
3861
4194
|
function routeEdge(input) {
|
|
3862
4195
|
const diagnostics = [];
|
|
@@ -3902,6 +4235,43 @@ function routeEdge(input) {
|
|
|
3902
4235
|
}
|
|
3903
4236
|
return { points, diagnostics };
|
|
3904
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
|
+
}
|
|
3905
4275
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3906
4276
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3907
4277
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -3991,7 +4361,7 @@ function routeEdge(input) {
|
|
|
3991
4361
|
const rerouted = greedyRerouteAroundObstacles(
|
|
3992
4362
|
bestPoints2,
|
|
3993
4363
|
allObstacles,
|
|
3994
|
-
|
|
4364
|
+
maxAttempts
|
|
3995
4365
|
);
|
|
3996
4366
|
const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
|
|
3997
4367
|
rerouted,
|
|
@@ -4104,7 +4474,7 @@ function routeEdge(input) {
|
|
|
4104
4474
|
};
|
|
4105
4475
|
}
|
|
4106
4476
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
4107
|
-
const simplified =
|
|
4477
|
+
const simplified = simplifyRoute2(points);
|
|
4108
4478
|
if (simplified.length >= 3) {
|
|
4109
4479
|
return simplified;
|
|
4110
4480
|
}
|
|
@@ -4392,7 +4762,7 @@ function squaredDistance2(a, b) {
|
|
|
4392
4762
|
const dy = a.y - b.y;
|
|
4393
4763
|
return dx * dx + dy * dy;
|
|
4394
4764
|
}
|
|
4395
|
-
function
|
|
4765
|
+
function simplifyRoute2(points) {
|
|
4396
4766
|
const withoutDuplicates = [];
|
|
4397
4767
|
for (const point2 of points) {
|
|
4398
4768
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -4404,7 +4774,7 @@ function simplifyRoute(points) {
|
|
|
4404
4774
|
for (const point2 of withoutDuplicates) {
|
|
4405
4775
|
const previous = simplified.at(-1);
|
|
4406
4776
|
const beforePrevious = simplified.at(-2);
|
|
4407
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4777
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
4408
4778
|
simplified[simplified.length - 1] = { ...point2 };
|
|
4409
4779
|
} else {
|
|
4410
4780
|
simplified.push({ ...point2 });
|
|
@@ -4619,17 +4989,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4619
4989
|
return true;
|
|
4620
4990
|
}
|
|
4621
4991
|
if (start.x === end.x) {
|
|
4622
|
-
return start.x > left && start.x < right &&
|
|
4992
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4623
4993
|
}
|
|
4624
4994
|
if (start.y === end.y) {
|
|
4625
|
-
return start.y > top && start.y < bottom &&
|
|
4995
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4626
4996
|
}
|
|
4627
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);
|
|
4628
4998
|
}
|
|
4629
4999
|
function pointInsideBox(point2, box) {
|
|
4630
5000
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4631
5001
|
}
|
|
4632
|
-
function
|
|
5002
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4633
5003
|
const low = Math.min(a, b);
|
|
4634
5004
|
const high = Math.max(a, b);
|
|
4635
5005
|
return high > min && low < max;
|
|
@@ -4653,7 +5023,7 @@ function segmentBox(a, b) {
|
|
|
4653
5023
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4654
5024
|
};
|
|
4655
5025
|
}
|
|
4656
|
-
function
|
|
5026
|
+
function areCollinear2(a, b, c) {
|
|
4657
5027
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4658
5028
|
}
|
|
4659
5029
|
|
|
@@ -4733,6 +5103,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4733
5103
|
options,
|
|
4734
5104
|
diagnostics
|
|
4735
5105
|
);
|
|
5106
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4736
5107
|
const constrained = applyLayoutConstraints({
|
|
4737
5108
|
direction: diagram.direction,
|
|
4738
5109
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4753,9 +5124,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4753
5124
|
options?.overlapSpacing ?? 40,
|
|
4754
5125
|
Math.max(0, options?.minLaneGutter ?? 0)
|
|
4755
5126
|
);
|
|
4756
|
-
|
|
4757
|
-
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4758
|
-
}
|
|
5127
|
+
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4759
5128
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
4760
5129
|
const coordinatedNodes = coordinateNodes(
|
|
4761
5130
|
styledNodes,
|
|
@@ -4798,6 +5167,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4798
5167
|
swimlanes: coordinatedSwimlanes,
|
|
4799
5168
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4800
5169
|
});
|
|
5170
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
5171
|
+
styledEdges,
|
|
5172
|
+
nodeGeometryById,
|
|
5173
|
+
options.textMeasurer
|
|
5174
|
+
);
|
|
4801
5175
|
const layoutBoxes = [
|
|
4802
5176
|
...coordinatedNodes.map((node) => node.box),
|
|
4803
5177
|
...coordinatedNodes.flatMap(
|
|
@@ -4878,7 +5252,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4878
5252
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4879
5253
|
const routingTextObstacles = [
|
|
4880
5254
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4881
|
-
...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
|
|
4882
5259
|
];
|
|
4883
5260
|
const margin = options.obstacleMargin ?? 0;
|
|
4884
5261
|
const softObstacles = [
|
|
@@ -4911,7 +5288,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4911
5288
|
hardObstacles,
|
|
4912
5289
|
diagram.direction,
|
|
4913
5290
|
options,
|
|
4914
|
-
diagnostics
|
|
5291
|
+
diagnostics,
|
|
5292
|
+
coordinatedGroups
|
|
4915
5293
|
);
|
|
4916
5294
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4917
5295
|
coordinatedEdges,
|
|
@@ -5027,22 +5405,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
5027
5405
|
y: layout2.box.y + offsetY,
|
|
5028
5406
|
width: layout2.box.width,
|
|
5029
5407
|
height: layout2.box.height
|
|
5030
|
-
}
|
|
5031
|
-
contentBox: {
|
|
5032
|
-
x: layout2.contentBox.x + offsetX,
|
|
5033
|
-
y: layout2.contentBox.y + offsetY,
|
|
5034
|
-
width: layout2.contentBox.width,
|
|
5035
|
-
height: layout2.contentBox.height
|
|
5036
|
-
},
|
|
5037
|
-
lines: layout2.lines.map((line) => ({
|
|
5038
|
-
...line,
|
|
5039
|
-
box: {
|
|
5040
|
-
x: line.box.x + offsetX,
|
|
5041
|
-
y: line.box.y + offsetY,
|
|
5042
|
-
width: line.box.width,
|
|
5043
|
-
height: line.box.height
|
|
5044
|
-
}
|
|
5045
|
-
}))
|
|
5408
|
+
}
|
|
5046
5409
|
};
|
|
5047
5410
|
}
|
|
5048
5411
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -5989,6 +6352,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5989
6352
|
});
|
|
5990
6353
|
continue;
|
|
5991
6354
|
}
|
|
6355
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
5992
6356
|
const geometry = computeShapeGeometry({
|
|
5993
6357
|
shape: node.shape,
|
|
5994
6358
|
box,
|
|
@@ -5998,7 +6362,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5998
6362
|
id: node.id,
|
|
5999
6363
|
...node.label === void 0 ? {} : { label: node.label },
|
|
6000
6364
|
...node.style === void 0 ? {} : { style: node.style },
|
|
6001
|
-
...
|
|
6365
|
+
...ports === void 0 ? {} : { ports },
|
|
6002
6366
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
6003
6367
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
6004
6368
|
shape: node.shape,
|
|
@@ -6010,6 +6374,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6010
6374
|
}
|
|
6011
6375
|
return coordinated;
|
|
6012
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
|
+
}
|
|
6013
6449
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
6014
6450
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
6015
6451
|
for (const port of node.ports ?? []) {
|
|
@@ -6046,7 +6482,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6046
6482
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
6047
6483
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
6048
6484
|
const availableSpan = 2 * maxOffset;
|
|
6049
|
-
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;
|
|
6050
6490
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
6051
6491
|
switch (side) {
|
|
6052
6492
|
case "left":
|
|
@@ -6072,7 +6512,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6072
6512
|
}
|
|
6073
6513
|
}
|
|
6074
6514
|
function portBox(anchor) {
|
|
6075
|
-
const size =
|
|
6515
|
+
const size = PORT_BOX_SIZE;
|
|
6076
6516
|
return {
|
|
6077
6517
|
x: anchor.x - size / 2,
|
|
6078
6518
|
y: anchor.y - size / 2,
|
|
@@ -6661,7 +7101,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6661
7101
|
}
|
|
6662
7102
|
};
|
|
6663
7103
|
}
|
|
6664
|
-
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) {
|
|
6665
7105
|
const coordinated = [];
|
|
6666
7106
|
const coordinatedNodeById = new Map(
|
|
6667
7107
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6685,8 +7125,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6685
7125
|
}
|
|
6686
7126
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6687
7127
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6688
|
-
const
|
|
6689
|
-
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);
|
|
6690
7129
|
const route = routeEdge({
|
|
6691
7130
|
kind: options.routeKind ?? "orthogonal",
|
|
6692
7131
|
direction,
|
|
@@ -6699,9 +7138,11 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6699
7138
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6700
7139
|
),
|
|
6701
7140
|
...softObstacles,
|
|
7141
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6702
7142
|
...routeTextObstacles
|
|
6703
7143
|
],
|
|
6704
|
-
hardObstacles
|
|
7144
|
+
hardObstacles,
|
|
7145
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
|
|
6705
7146
|
});
|
|
6706
7147
|
diagnostics.push(
|
|
6707
7148
|
...route.diagnostics.map((diagnostic) => ({
|
|
@@ -6716,15 +7157,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6716
7157
|
}
|
|
6717
7158
|
return coordinated;
|
|
6718
7159
|
}
|
|
6719
|
-
function
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
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;
|
|
6723
7173
|
}
|
|
6724
|
-
|
|
6725
|
-
|
|
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
|
+
}
|
|
6726
7194
|
}
|
|
6727
|
-
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));
|
|
6728
7206
|
}
|
|
6729
7207
|
function coordinateBaseTextAnnotations(input) {
|
|
6730
7208
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -6912,6 +7390,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
6912
7390
|
}
|
|
6913
7391
|
return annotations;
|
|
6914
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
|
+
}
|
|
6915
7442
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
6916
7443
|
const layout2 = fitLabel(
|
|
6917
7444
|
frame.titleTab,
|
|
@@ -7032,9 +7559,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7032
7559
|
const diagnostics = [];
|
|
7033
7560
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
7034
7561
|
for (const edge of edges) {
|
|
7035
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
7036
7562
|
for (const annotation of relevantAnnotations) {
|
|
7037
|
-
if (
|
|
7563
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
7038
7564
|
continue;
|
|
7039
7565
|
}
|
|
7040
7566
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -7058,9 +7584,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7058
7584
|
return diagnostics;
|
|
7059
7585
|
}
|
|
7060
7586
|
function isPreRouteTextObstacle(annotation) {
|
|
7061
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
7062
|
-
return false;
|
|
7063
|
-
}
|
|
7064
7587
|
return isRouteClearanceText(annotation);
|
|
7065
7588
|
}
|
|
7066
7589
|
function isRouteClearanceText(annotation) {
|
|
@@ -7071,8 +7594,9 @@ function isRouteClearanceText(annotation) {
|
|
|
7071
7594
|
case "frame-title":
|
|
7072
7595
|
return true;
|
|
7073
7596
|
case "node-label":
|
|
7074
|
-
case "group-label":
|
|
7075
7597
|
case "compartment-row":
|
|
7598
|
+
return true;
|
|
7599
|
+
case "group-label":
|
|
7076
7600
|
return textExtendsOutsideAnchor(annotation);
|
|
7077
7601
|
}
|
|
7078
7602
|
}
|
|
@@ -7105,17 +7629,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
7105
7629
|
return true;
|
|
7106
7630
|
}
|
|
7107
7631
|
if (start.x === end.x) {
|
|
7108
|
-
return start.x > left && start.x < right &&
|
|
7632
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
7109
7633
|
}
|
|
7110
7634
|
if (start.y === end.y) {
|
|
7111
|
-
return start.y > top && start.y < bottom &&
|
|
7635
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
7112
7636
|
}
|
|
7113
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);
|
|
7114
7638
|
}
|
|
7115
7639
|
function pointInsideBox2(point2, box) {
|
|
7116
7640
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
7117
7641
|
}
|
|
7118
|
-
function
|
|
7642
|
+
function rangesOverlap3(a, b, min, max) {
|
|
7119
7643
|
const low = Math.min(a, b);
|
|
7120
7644
|
const high = Math.max(a, b);
|
|
7121
7645
|
return high > min && low < max;
|
|
@@ -7184,7 +7708,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
7184
7708
|
for (const candidate of edgeLabelAnchorCandidates(
|
|
7185
7709
|
edge.points,
|
|
7186
7710
|
placement,
|
|
7187
|
-
layout2
|
|
7711
|
+
layout2,
|
|
7712
|
+
baseOffset
|
|
7188
7713
|
)) {
|
|
7189
7714
|
const labelBox = {
|
|
7190
7715
|
x: candidate.x - layout2.box.width / 2,
|
|
@@ -7216,8 +7741,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
7216
7741
|
}
|
|
7217
7742
|
return placement;
|
|
7218
7743
|
}
|
|
7219
|
-
function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
7220
|
-
const segment = labelSegmentOnPolyline(points);
|
|
7744
|
+
function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
|
|
7745
|
+
const segment = labelSegmentOnPolyline(points, baseOffset);
|
|
7221
7746
|
if (segment === void 0) {
|
|
7222
7747
|
return [placement];
|
|
7223
7748
|
}
|
|
@@ -7267,7 +7792,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
7267
7792
|
}, 0);
|
|
7268
7793
|
if (totalLen > 200) {
|
|
7269
7794
|
for (const ratio of [0.25, 0.75]) {
|
|
7270
|
-
const qp = labelPlacementAtRatio(points, ratio, totalLen);
|
|
7795
|
+
const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
|
|
7271
7796
|
if (qp !== void 0) {
|
|
7272
7797
|
candidates.push(qp);
|
|
7273
7798
|
const qTargetDist = totalLen * ratio;
|
|
@@ -7353,7 +7878,7 @@ function labelSegmentOnPolyline(points, baseOffset = 10) {
|
|
|
7353
7878
|
if (last === void 0) {
|
|
7354
7879
|
return void 0;
|
|
7355
7880
|
}
|
|
7356
|
-
const offset = labelOffset2(last);
|
|
7881
|
+
const offset = labelOffset2(last, baseOffset);
|
|
7357
7882
|
return {
|
|
7358
7883
|
start: last.start,
|
|
7359
7884
|
end: last.end,
|
|
@@ -7375,7 +7900,7 @@ function nonZeroSegments2(points) {
|
|
|
7375
7900
|
}
|
|
7376
7901
|
return segments;
|
|
7377
7902
|
}
|
|
7378
|
-
function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
7903
|
+
function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
|
|
7379
7904
|
if (points.length < 2 || ratio < 0 || ratio > 1) {
|
|
7380
7905
|
return void 0;
|
|
7381
7906
|
}
|
|
@@ -7393,7 +7918,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
|
7393
7918
|
}
|
|
7394
7919
|
if (travelled + segLen >= targetDist) {
|
|
7395
7920
|
const t = (targetDist - travelled) / segLen;
|
|
7396
|
-
const offset = labelOffset2(
|
|
7921
|
+
const offset = labelOffset2(
|
|
7922
|
+
{ start: prev, end: curr, length: segLen },
|
|
7923
|
+
baseOffset
|
|
7924
|
+
);
|
|
7397
7925
|
return {
|
|
7398
7926
|
x: prev.x + (curr.x - prev.x) * t + offset.x,
|
|
7399
7927
|
y: prev.y + (curr.y - prev.y) * t + offset.y
|
|
@@ -7739,7 +8267,7 @@ exports.resolveLineHeight = resolveLineHeight;
|
|
|
7739
8267
|
exports.resolveOutputFormat = resolveOutputFormat;
|
|
7740
8268
|
exports.routeEdge = routeEdge;
|
|
7741
8269
|
exports.runDagreInitialLayout = runDagreInitialLayout;
|
|
7742
|
-
exports.simplifyRoute =
|
|
8270
|
+
exports.simplifyRoute = simplifyRoute2;
|
|
7743
8271
|
exports.solveDiagram = solveDiagram;
|
|
7744
8272
|
exports.solveDiagramSafe = solveDiagramSafe;
|
|
7745
8273
|
exports.sortDslDiagnostics = sortDslDiagnostics;
|