@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.js
CHANGED
|
@@ -96,6 +96,9 @@ function applyLayoutConstraints(input) {
|
|
|
96
96
|
const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
|
|
97
97
|
applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
|
|
98
98
|
applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
|
|
99
|
+
if (input.distributeContainedChildren) {
|
|
100
|
+
yieldFixedPositionLocks(input, boxes, locks);
|
|
101
|
+
}
|
|
99
102
|
applyContainment(input.constraints, boxes, locks, diagnostics, false);
|
|
100
103
|
applyRelative(input.constraints, boxes, locks, diagnostics);
|
|
101
104
|
applyAlign(input.constraints, boxes, locks, diagnostics);
|
|
@@ -109,6 +112,13 @@ function applyLayoutConstraints(input) {
|
|
|
109
112
|
);
|
|
110
113
|
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
111
114
|
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
115
|
+
if (input.distributeContainedChildren) {
|
|
116
|
+
const diagBefore = diagnostics.length;
|
|
117
|
+
applyContainment(input.constraints, boxes, locks, diagnostics, true);
|
|
118
|
+
applyDistributeContained(input, boxes, locks, diagnostics);
|
|
119
|
+
dedupReplayDiagnostics(diagnostics, diagBefore);
|
|
120
|
+
}
|
|
121
|
+
removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
|
|
112
122
|
reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
|
|
113
123
|
reportIntraContainerOverflow(input, boxes, diagnostics);
|
|
114
124
|
return { boxes, locks, diagnostics };
|
|
@@ -154,6 +164,62 @@ function applyFixedPositionLocks(nodes, boxes, locks, diagnostics) {
|
|
|
154
164
|
locks.set(node.id, { nodeId: node.id, source: "fixed-position" });
|
|
155
165
|
}
|
|
156
166
|
}
|
|
167
|
+
function dedupReplayDiagnostics(diagnostics, keepUpTo) {
|
|
168
|
+
const seen = /* @__PURE__ */ new Set();
|
|
169
|
+
for (let i = 0; i < keepUpTo && i < diagnostics.length; i += 1) {
|
|
170
|
+
const d = diagnostics[i];
|
|
171
|
+
if (d === void 0) continue;
|
|
172
|
+
seen.add(diagnosticFingerprint(d));
|
|
173
|
+
}
|
|
174
|
+
for (let i = diagnostics.length - 1; i >= keepUpTo; i -= 1) {
|
|
175
|
+
const d = diagnostics[i];
|
|
176
|
+
if (d === void 0) continue;
|
|
177
|
+
const fp = diagnosticFingerprint(d);
|
|
178
|
+
if (seen.has(fp)) {
|
|
179
|
+
diagnostics.splice(i, 1);
|
|
180
|
+
} else {
|
|
181
|
+
seen.add(fp);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function diagnosticFingerprint(d) {
|
|
186
|
+
const nodeId = typeof d.detail?.nodeId === "string" ? d.detail.nodeId : "";
|
|
187
|
+
const containerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : "";
|
|
188
|
+
return `${d.code}|${nodeId}|${containerId}`;
|
|
189
|
+
}
|
|
190
|
+
function yieldFixedPositionLocks(input, boxes, locks) {
|
|
191
|
+
for (const c of input.constraints) {
|
|
192
|
+
if (c.kind !== "containment") continue;
|
|
193
|
+
const container = boxes.get(c.containerId);
|
|
194
|
+
if (container === void 0) continue;
|
|
195
|
+
const content = contentBox(container, c.padding);
|
|
196
|
+
const mainAxis = input.direction === "LR" || input.direction === "RL" ? "width" : "height";
|
|
197
|
+
const crossAxis = mainAxis === "width" ? "height" : "width";
|
|
198
|
+
let eligible = 0;
|
|
199
|
+
for (const childId of c.childIds) {
|
|
200
|
+
const box = boxes.get(childId);
|
|
201
|
+
if (box === void 0) continue;
|
|
202
|
+
const lock = locks.get(childId);
|
|
203
|
+
if (lock?.source === "exact-position") continue;
|
|
204
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
205
|
+
if (fits) {
|
|
206
|
+
eligible += 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (eligible < 2) continue;
|
|
210
|
+
for (const childId of c.childIds) {
|
|
211
|
+
const lock = locks.get(childId);
|
|
212
|
+
if (lock?.source === "fixed-position") {
|
|
213
|
+
const box = boxes.get(childId);
|
|
214
|
+
if (box === void 0) continue;
|
|
215
|
+
const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
|
|
216
|
+
if (fits) {
|
|
217
|
+
locks.delete(childId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
157
223
|
function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
|
|
158
224
|
for (const constraint of constraints) {
|
|
159
225
|
if (constraint.kind !== "exact-position") {
|
|
@@ -226,7 +292,7 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
|
|
|
226
292
|
code: "constraints.locked-target-not-moved",
|
|
227
293
|
message: `Locked child ${childId} was not moved into containment.`,
|
|
228
294
|
path: ["constraints", constraint.id ?? constraint.containerId],
|
|
229
|
-
detail: { nodeId: childId }
|
|
295
|
+
detail: { nodeId: childId, containerId: constraint.containerId }
|
|
230
296
|
});
|
|
231
297
|
if (!isInside(child, content)) {
|
|
232
298
|
diagnostics.push({
|
|
@@ -378,6 +444,60 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
|
|
|
378
444
|
}
|
|
379
445
|
reportOverlaps(boxes, diagnostics, ignoredPairs);
|
|
380
446
|
}
|
|
447
|
+
function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
|
|
448
|
+
for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
|
|
449
|
+
const d = diagnostics[i];
|
|
450
|
+
if (d === void 0) continue;
|
|
451
|
+
if (d.code === "constraints.overlap.unresolved") {
|
|
452
|
+
const aId = d.detail?.firstId;
|
|
453
|
+
const bId = d.detail?.secondId;
|
|
454
|
+
if (typeof aId !== "string" || typeof bId !== "string") continue;
|
|
455
|
+
const a = boxes.get(aId);
|
|
456
|
+
const b = boxes.get(bId);
|
|
457
|
+
if (a !== void 0 && b !== void 0 && !intersectsAabb(a, b)) {
|
|
458
|
+
diagnostics.splice(i, 1);
|
|
459
|
+
}
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (d.code === "constraints.containment.impossible" || d.code === "constraints.locked-target-not-moved" && typeof d.message === "string" && d.message.includes("not moved into containment")) {
|
|
463
|
+
const nodeId = d.detail?.nodeId;
|
|
464
|
+
if (typeof nodeId !== "string") continue;
|
|
465
|
+
const child = boxes.get(nodeId);
|
|
466
|
+
if (child === void 0) continue;
|
|
467
|
+
const diagContainerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : void 0;
|
|
468
|
+
let resolved = false;
|
|
469
|
+
for (const c of constraints) {
|
|
470
|
+
if (c.kind !== "containment") continue;
|
|
471
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
472
|
+
if (diagContainerId !== void 0 && c.containerId !== diagContainerId) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const container = boxes.get(c.containerId);
|
|
476
|
+
if (container === void 0) continue;
|
|
477
|
+
const content = contentBox(container, c.padding);
|
|
478
|
+
if (isInside(child, content)) {
|
|
479
|
+
diagnostics.splice(i, 1);
|
|
480
|
+
resolved = true;
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
if (!resolved && diagContainerId !== void 0) {
|
|
485
|
+
for (const c of constraints) {
|
|
486
|
+
if (c.kind !== "containment") continue;
|
|
487
|
+
if (c.containerId !== diagContainerId) continue;
|
|
488
|
+
if (!c.childIds.includes(nodeId)) continue;
|
|
489
|
+
const container = boxes.get(c.containerId);
|
|
490
|
+
if (container === void 0) continue;
|
|
491
|
+
const content = contentBox(container, c.padding);
|
|
492
|
+
if (isInside(child, content)) {
|
|
493
|
+
diagnostics.splice(i, 1);
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
381
501
|
function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
|
|
382
502
|
const ids = [...boxes.keys()].sort();
|
|
383
503
|
const reported = new Set(
|
|
@@ -685,6 +805,11 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
685
805
|
continue;
|
|
686
806
|
}
|
|
687
807
|
if (locks.has(childId)) {
|
|
808
|
+
const lock = locks.get(childId);
|
|
809
|
+
if (lock?.source === "fixed-position") {
|
|
810
|
+
unlocked.push({ id: childId, box });
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
688
813
|
diagnostics.push({
|
|
689
814
|
severity: "warning",
|
|
690
815
|
code: "constraints.locked-target-not-moved",
|
|
@@ -743,6 +868,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
743
868
|
});
|
|
744
869
|
}
|
|
745
870
|
boxes.set(child.id, clamped);
|
|
871
|
+
locks.delete(child.id);
|
|
746
872
|
pos = clamped[axis] + clamped[mainSize] + minGap;
|
|
747
873
|
}
|
|
748
874
|
diagnostics.push({
|
|
@@ -3854,6 +3980,213 @@ function isValidDimension(value) {
|
|
|
3854
3980
|
return Number.isFinite(value) && value >= 0;
|
|
3855
3981
|
}
|
|
3856
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
|
+
|
|
3857
4190
|
// src/routing/routes.ts
|
|
3858
4191
|
function routeEdge(input) {
|
|
3859
4192
|
const diagnostics = [];
|
|
@@ -3899,6 +4232,43 @@ function routeEdge(input) {
|
|
|
3899
4232
|
}
|
|
3900
4233
|
return { points, diagnostics };
|
|
3901
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
|
+
}
|
|
3902
4272
|
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3903
4273
|
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3904
4274
|
const candidateRoutes = anchorPairs.flatMap(
|
|
@@ -3988,7 +4358,7 @@ function routeEdge(input) {
|
|
|
3988
4358
|
const rerouted = greedyRerouteAroundObstacles(
|
|
3989
4359
|
bestPoints2,
|
|
3990
4360
|
allObstacles,
|
|
3991
|
-
|
|
4361
|
+
maxAttempts
|
|
3992
4362
|
);
|
|
3993
4363
|
const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
|
|
3994
4364
|
rerouted,
|
|
@@ -4101,7 +4471,7 @@ function routeEdge(input) {
|
|
|
4101
4471
|
};
|
|
4102
4472
|
}
|
|
4103
4473
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
|
|
4104
|
-
const simplified =
|
|
4474
|
+
const simplified = simplifyRoute2(points);
|
|
4105
4475
|
if (simplified.length >= 3) {
|
|
4106
4476
|
return simplified;
|
|
4107
4477
|
}
|
|
@@ -4389,7 +4759,7 @@ function squaredDistance2(a, b) {
|
|
|
4389
4759
|
const dy = a.y - b.y;
|
|
4390
4760
|
return dx * dx + dy * dy;
|
|
4391
4761
|
}
|
|
4392
|
-
function
|
|
4762
|
+
function simplifyRoute2(points) {
|
|
4393
4763
|
const withoutDuplicates = [];
|
|
4394
4764
|
for (const point2 of points) {
|
|
4395
4765
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -4401,7 +4771,7 @@ function simplifyRoute(points) {
|
|
|
4401
4771
|
for (const point2 of withoutDuplicates) {
|
|
4402
4772
|
const previous = simplified.at(-1);
|
|
4403
4773
|
const beforePrevious = simplified.at(-2);
|
|
4404
|
-
if (previous !== void 0 && beforePrevious !== void 0 &&
|
|
4774
|
+
if (previous !== void 0 && beforePrevious !== void 0 && areCollinear2(beforePrevious, previous, point2)) {
|
|
4405
4775
|
simplified[simplified.length - 1] = { ...point2 };
|
|
4406
4776
|
} else {
|
|
4407
4777
|
simplified.push({ ...point2 });
|
|
@@ -4616,17 +4986,17 @@ function segmentIntersectsBox(start, end, box) {
|
|
|
4616
4986
|
return true;
|
|
4617
4987
|
}
|
|
4618
4988
|
if (start.x === end.x) {
|
|
4619
|
-
return start.x > left && start.x < right &&
|
|
4989
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
4620
4990
|
}
|
|
4621
4991
|
if (start.y === end.y) {
|
|
4622
|
-
return start.y > top && start.y < bottom &&
|
|
4992
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4623
4993
|
}
|
|
4624
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);
|
|
4625
4995
|
}
|
|
4626
4996
|
function pointInsideBox(point2, box) {
|
|
4627
4997
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
4628
4998
|
}
|
|
4629
|
-
function
|
|
4999
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4630
5000
|
const low = Math.min(a, b);
|
|
4631
5001
|
const high = Math.max(a, b);
|
|
4632
5002
|
return high > min && low < max;
|
|
@@ -4650,7 +5020,7 @@ function segmentBox(a, b) {
|
|
|
4650
5020
|
height: Math.max(1, Math.abs(a.y - b.y))
|
|
4651
5021
|
};
|
|
4652
5022
|
}
|
|
4653
|
-
function
|
|
5023
|
+
function areCollinear2(a, b, c) {
|
|
4654
5024
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4655
5025
|
}
|
|
4656
5026
|
|
|
@@ -4730,6 +5100,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4730
5100
|
options,
|
|
4731
5101
|
diagnostics
|
|
4732
5102
|
);
|
|
5103
|
+
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
4733
5104
|
const constrained = applyLayoutConstraints({
|
|
4734
5105
|
direction: diagram.direction,
|
|
4735
5106
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
@@ -4750,9 +5121,7 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4750
5121
|
options?.overlapSpacing ?? 40,
|
|
4751
5122
|
Math.max(0, options?.minLaneGutter ?? 0)
|
|
4752
5123
|
);
|
|
4753
|
-
|
|
4754
|
-
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4755
|
-
}
|
|
5124
|
+
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
4756
5125
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
4757
5126
|
const coordinatedNodes = coordinateNodes(
|
|
4758
5127
|
styledNodes,
|
|
@@ -4795,6 +5164,11 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4795
5164
|
swimlanes: coordinatedSwimlanes,
|
|
4796
5165
|
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4797
5166
|
});
|
|
5167
|
+
const edgeLabelEstimates = estimateEdgeLabelAnnotations(
|
|
5168
|
+
styledEdges,
|
|
5169
|
+
nodeGeometryById,
|
|
5170
|
+
options.textMeasurer
|
|
5171
|
+
);
|
|
4798
5172
|
const layoutBoxes = [
|
|
4799
5173
|
...coordinatedNodes.map((node) => node.box),
|
|
4800
5174
|
...coordinatedNodes.flatMap(
|
|
@@ -4875,7 +5249,10 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4875
5249
|
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4876
5250
|
const routingTextObstacles = [
|
|
4877
5251
|
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4878
|
-
...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
|
|
4879
5256
|
];
|
|
4880
5257
|
const margin = options.obstacleMargin ?? 0;
|
|
4881
5258
|
const softObstacles = [
|
|
@@ -4908,7 +5285,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
4908
5285
|
hardObstacles,
|
|
4909
5286
|
diagram.direction,
|
|
4910
5287
|
options,
|
|
4911
|
-
diagnostics
|
|
5288
|
+
diagnostics,
|
|
5289
|
+
coordinatedGroups
|
|
4912
5290
|
);
|
|
4913
5291
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4914
5292
|
coordinatedEdges,
|
|
@@ -5024,22 +5402,7 @@ function expandLabelLayoutToNode(layout2, nodeSize) {
|
|
|
5024
5402
|
y: layout2.box.y + offsetY,
|
|
5025
5403
|
width: layout2.box.width,
|
|
5026
5404
|
height: layout2.box.height
|
|
5027
|
-
}
|
|
5028
|
-
contentBox: {
|
|
5029
|
-
x: layout2.contentBox.x + offsetX,
|
|
5030
|
-
y: layout2.contentBox.y + offsetY,
|
|
5031
|
-
width: layout2.contentBox.width,
|
|
5032
|
-
height: layout2.contentBox.height
|
|
5033
|
-
},
|
|
5034
|
-
lines: layout2.lines.map((line) => ({
|
|
5035
|
-
...line,
|
|
5036
|
-
box: {
|
|
5037
|
-
x: line.box.x + offsetX,
|
|
5038
|
-
y: line.box.y + offsetY,
|
|
5039
|
-
width: line.box.width,
|
|
5040
|
-
height: line.box.height
|
|
5041
|
-
}
|
|
5042
|
-
}))
|
|
5405
|
+
}
|
|
5043
5406
|
};
|
|
5044
5407
|
}
|
|
5045
5408
|
function reportPageOverflow(contentBounds, pageBounds) {
|
|
@@ -5986,6 +6349,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5986
6349
|
});
|
|
5987
6350
|
continue;
|
|
5988
6351
|
}
|
|
6352
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
5989
6353
|
const geometry = computeShapeGeometry({
|
|
5990
6354
|
shape: node.shape,
|
|
5991
6355
|
box,
|
|
@@ -5995,7 +6359,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
5995
6359
|
id: node.id,
|
|
5996
6360
|
...node.label === void 0 ? {} : { label: node.label },
|
|
5997
6361
|
...node.style === void 0 ? {} : { style: node.style },
|
|
5998
|
-
...
|
|
6362
|
+
...ports === void 0 ? {} : { ports },
|
|
5999
6363
|
...node.compartments === void 0 ? {} : { compartments: node.compartments },
|
|
6000
6364
|
...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
|
|
6001
6365
|
shape: node.shape,
|
|
@@ -6007,6 +6371,78 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
6007
6371
|
}
|
|
6008
6372
|
return coordinated;
|
|
6009
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
|
+
}
|
|
6010
6446
|
function coordinatePorts(node, nodeBox, portShifting) {
|
|
6011
6447
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
6012
6448
|
for (const port of node.ports ?? []) {
|
|
@@ -6043,7 +6479,11 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6043
6479
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
6044
6480
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
6045
6481
|
const availableSpan = 2 * maxOffset;
|
|
6046
|
-
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;
|
|
6047
6487
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
6048
6488
|
switch (side) {
|
|
6049
6489
|
case "left":
|
|
@@ -6069,7 +6509,7 @@ function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
|
6069
6509
|
}
|
|
6070
6510
|
}
|
|
6071
6511
|
function portBox(anchor) {
|
|
6072
|
-
const size =
|
|
6512
|
+
const size = PORT_BOX_SIZE;
|
|
6073
6513
|
return {
|
|
6074
6514
|
x: anchor.x - size / 2,
|
|
6075
6515
|
y: anchor.y - size / 2,
|
|
@@ -6658,7 +7098,7 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
6658
7098
|
}
|
|
6659
7099
|
};
|
|
6660
7100
|
}
|
|
6661
|
-
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) {
|
|
6662
7102
|
const coordinated = [];
|
|
6663
7103
|
const coordinatedNodeById = new Map(
|
|
6664
7104
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -6682,8 +7122,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6682
7122
|
}
|
|
6683
7123
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
6684
7124
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
6685
|
-
const
|
|
6686
|
-
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);
|
|
6687
7126
|
const route = routeEdge({
|
|
6688
7127
|
kind: options.routeKind ?? "orthogonal",
|
|
6689
7128
|
direction,
|
|
@@ -6696,9 +7135,11 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6696
7135
|
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
6697
7136
|
),
|
|
6698
7137
|
...softObstacles,
|
|
7138
|
+
...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
|
|
6699
7139
|
...routeTextObstacles
|
|
6700
7140
|
],
|
|
6701
|
-
hardObstacles
|
|
7141
|
+
hardObstacles,
|
|
7142
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
|
|
6702
7143
|
});
|
|
6703
7144
|
diagnostics.push(
|
|
6704
7145
|
...route.diagnostics.map((diagnostic) => ({
|
|
@@ -6713,15 +7154,52 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
6713
7154
|
}
|
|
6714
7155
|
return coordinated;
|
|
6715
7156
|
}
|
|
6716
|
-
function
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
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;
|
|
6720
7170
|
}
|
|
6721
|
-
|
|
6722
|
-
|
|
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
|
+
}
|
|
6723
7191
|
}
|
|
6724
|
-
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));
|
|
6725
7203
|
}
|
|
6726
7204
|
function coordinateBaseTextAnnotations(input) {
|
|
6727
7205
|
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
@@ -6909,6 +7387,55 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, label
|
|
|
6909
7387
|
}
|
|
6910
7388
|
return annotations;
|
|
6911
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
|
+
}
|
|
6912
7439
|
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
6913
7440
|
const layout2 = fitLabel(
|
|
6914
7441
|
frame.titleTab,
|
|
@@ -7029,9 +7556,8 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7029
7556
|
const diagnostics = [];
|
|
7030
7557
|
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
7031
7558
|
for (const edge of edges) {
|
|
7032
|
-
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
7033
7559
|
for (const annotation of relevantAnnotations) {
|
|
7034
|
-
if (
|
|
7560
|
+
if (isEdgeConnectedTextAnnotation(edge, annotation)) {
|
|
7035
7561
|
continue;
|
|
7036
7562
|
}
|
|
7037
7563
|
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
@@ -7055,9 +7581,6 @@ function reportRouteTextClearance(edges, annotations) {
|
|
|
7055
7581
|
return diagnostics;
|
|
7056
7582
|
}
|
|
7057
7583
|
function isPreRouteTextObstacle(annotation) {
|
|
7058
|
-
if (annotation.surfaceKind === "edge-label") {
|
|
7059
|
-
return false;
|
|
7060
|
-
}
|
|
7061
7584
|
return isRouteClearanceText(annotation);
|
|
7062
7585
|
}
|
|
7063
7586
|
function isRouteClearanceText(annotation) {
|
|
@@ -7068,8 +7591,9 @@ function isRouteClearanceText(annotation) {
|
|
|
7068
7591
|
case "frame-title":
|
|
7069
7592
|
return true;
|
|
7070
7593
|
case "node-label":
|
|
7071
|
-
case "group-label":
|
|
7072
7594
|
case "compartment-row":
|
|
7595
|
+
return true;
|
|
7596
|
+
case "group-label":
|
|
7073
7597
|
return textExtendsOutsideAnchor(annotation);
|
|
7074
7598
|
}
|
|
7075
7599
|
}
|
|
@@ -7102,17 +7626,17 @@ function segmentIntersectsBox2(start, end, box) {
|
|
|
7102
7626
|
return true;
|
|
7103
7627
|
}
|
|
7104
7628
|
if (start.x === end.x) {
|
|
7105
|
-
return start.x > left && start.x < right &&
|
|
7629
|
+
return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
|
|
7106
7630
|
}
|
|
7107
7631
|
if (start.y === end.y) {
|
|
7108
|
-
return start.y > top && start.y < bottom &&
|
|
7632
|
+
return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
|
|
7109
7633
|
}
|
|
7110
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);
|
|
7111
7635
|
}
|
|
7112
7636
|
function pointInsideBox2(point2, box) {
|
|
7113
7637
|
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
7114
7638
|
}
|
|
7115
|
-
function
|
|
7639
|
+
function rangesOverlap3(a, b, min, max) {
|
|
7116
7640
|
const low = Math.min(a, b);
|
|
7117
7641
|
const high = Math.max(a, b);
|
|
7118
7642
|
return high > min && low < max;
|
|
@@ -7181,7 +7705,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
7181
7705
|
for (const candidate of edgeLabelAnchorCandidates(
|
|
7182
7706
|
edge.points,
|
|
7183
7707
|
placement,
|
|
7184
|
-
layout2
|
|
7708
|
+
layout2,
|
|
7709
|
+
baseOffset
|
|
7185
7710
|
)) {
|
|
7186
7711
|
const labelBox = {
|
|
7187
7712
|
x: candidate.x - layout2.box.width / 2,
|
|
@@ -7213,8 +7738,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
|
|
|
7213
7738
|
}
|
|
7214
7739
|
return placement;
|
|
7215
7740
|
}
|
|
7216
|
-
function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
7217
|
-
const segment = labelSegmentOnPolyline(points);
|
|
7741
|
+
function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
|
|
7742
|
+
const segment = labelSegmentOnPolyline(points, baseOffset);
|
|
7218
7743
|
if (segment === void 0) {
|
|
7219
7744
|
return [placement];
|
|
7220
7745
|
}
|
|
@@ -7264,7 +7789,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
|
|
|
7264
7789
|
}, 0);
|
|
7265
7790
|
if (totalLen > 200) {
|
|
7266
7791
|
for (const ratio of [0.25, 0.75]) {
|
|
7267
|
-
const qp = labelPlacementAtRatio(points, ratio, totalLen);
|
|
7792
|
+
const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
|
|
7268
7793
|
if (qp !== void 0) {
|
|
7269
7794
|
candidates.push(qp);
|
|
7270
7795
|
const qTargetDist = totalLen * ratio;
|
|
@@ -7350,7 +7875,7 @@ function labelSegmentOnPolyline(points, baseOffset = 10) {
|
|
|
7350
7875
|
if (last === void 0) {
|
|
7351
7876
|
return void 0;
|
|
7352
7877
|
}
|
|
7353
|
-
const offset = labelOffset2(last);
|
|
7878
|
+
const offset = labelOffset2(last, baseOffset);
|
|
7354
7879
|
return {
|
|
7355
7880
|
start: last.start,
|
|
7356
7881
|
end: last.end,
|
|
@@ -7372,7 +7897,7 @@ function nonZeroSegments2(points) {
|
|
|
7372
7897
|
}
|
|
7373
7898
|
return segments;
|
|
7374
7899
|
}
|
|
7375
|
-
function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
7900
|
+
function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
|
|
7376
7901
|
if (points.length < 2 || ratio < 0 || ratio > 1) {
|
|
7377
7902
|
return void 0;
|
|
7378
7903
|
}
|
|
@@ -7390,7 +7915,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
|
|
|
7390
7915
|
}
|
|
7391
7916
|
if (travelled + segLen >= targetDist) {
|
|
7392
7917
|
const t = (targetDist - travelled) / segLen;
|
|
7393
|
-
const offset = labelOffset2(
|
|
7918
|
+
const offset = labelOffset2(
|
|
7919
|
+
{ start: prev, end: curr, length: segLen },
|
|
7920
|
+
baseOffset
|
|
7921
|
+
);
|
|
7394
7922
|
return {
|
|
7395
7923
|
x: prev.x + (curr.x - prev.x) * t + offset.x,
|
|
7396
7924
|
y: prev.y + (curr.y - prev.y) * t + offset.y
|
|
@@ -7704,6 +8232,6 @@ function isPointLikeRecord(value) {
|
|
|
7704
8232
|
return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
|
|
7705
8233
|
}
|
|
7706
8234
|
|
|
7707
|
-
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 };
|
|
7708
8236
|
//# sourceMappingURL=index.js.map
|
|
7709
8237
|
//# sourceMappingURL=index.js.map
|