@markdy/renderer-dom 1.0.8 → 1.0.10
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/README.md +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +815 -272
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -35,6 +35,9 @@ function segmentIntersectsRect(a, b, rect) {
|
|
|
35
35
|
}
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
|
+
function rectsOverlap(a, b) {
|
|
39
|
+
return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
|
|
40
|
+
}
|
|
38
41
|
function countPathIntersections(points, obstacles) {
|
|
39
42
|
let hits = 0;
|
|
40
43
|
for (let i = 0; i < points.length - 1; i++) {
|
|
@@ -49,22 +52,108 @@ function countPathIntersections(points, obstacles) {
|
|
|
49
52
|
function round1(n) {
|
|
50
53
|
return Math.round(n * 10) / 10;
|
|
51
54
|
}
|
|
52
|
-
function
|
|
55
|
+
function findSegmentHops(p1, p2, existingPaths = [], hopRadius = 5, minDistFromVertex = 10) {
|
|
56
|
+
const isHoriz = Math.abs(p1.y - p2.y) < 0.01;
|
|
57
|
+
const isVert = Math.abs(p1.x - p2.x) < 0.01;
|
|
58
|
+
if (!isHoriz && !isVert || existingPaths.length === 0) return [];
|
|
59
|
+
const crossings = [];
|
|
60
|
+
const minX = Math.min(p1.x, p2.x);
|
|
61
|
+
const maxX = Math.max(p1.x, p2.x);
|
|
62
|
+
const minY = Math.min(p1.y, p2.y);
|
|
63
|
+
const maxY = Math.max(p1.y, p2.y);
|
|
64
|
+
for (const path of existingPaths) {
|
|
65
|
+
for (let j = 0; j < path.length - 1; j++) {
|
|
66
|
+
const q1 = path[j];
|
|
67
|
+
const q2 = path[j + 1];
|
|
68
|
+
const qHoriz = Math.abs(q1.y - q2.y) < 0.01;
|
|
69
|
+
const qVert = Math.abs(q1.x - q2.x) < 0.01;
|
|
70
|
+
if (isHoriz && qVert) {
|
|
71
|
+
const crossX = q1.x;
|
|
72
|
+
const crossY = p1.y;
|
|
73
|
+
const qMinY = Math.min(q1.y, q2.y);
|
|
74
|
+
const qMaxY = Math.max(q1.y, q2.y);
|
|
75
|
+
if (crossX > minX + minDistFromVertex && crossX < maxX - minDistFromVertex && crossY > qMinY + 6 && crossY < qMaxY - 6) {
|
|
76
|
+
crossings.push({ x: crossX, y: crossY });
|
|
77
|
+
}
|
|
78
|
+
} else if (isVert && qHoriz) {
|
|
79
|
+
const crossX = p1.x;
|
|
80
|
+
const crossY = q1.y;
|
|
81
|
+
const qMinX = Math.min(q1.x, q2.x);
|
|
82
|
+
const qMaxX = Math.max(q1.x, q2.x);
|
|
83
|
+
if (crossX > qMinX + 6 && crossX < qMaxX - 6 && crossY > minY + minDistFromVertex && crossY < maxY - minDistFromVertex) {
|
|
84
|
+
crossings.push({ x: crossX, y: crossY });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
crossings.sort((a, b) => {
|
|
90
|
+
const da = Math.hypot(a.x - p1.x, a.y - p1.y);
|
|
91
|
+
const db = Math.hypot(b.x - p1.x, b.y - p1.y);
|
|
92
|
+
return da - db;
|
|
93
|
+
});
|
|
94
|
+
return crossings;
|
|
95
|
+
}
|
|
96
|
+
function appendSegmentWithHops(parts, start, end, existingPaths = [], hopRadius = 5) {
|
|
97
|
+
const isHoriz = Math.abs(start.y - end.y) < 0.01;
|
|
98
|
+
const isVert = Math.abs(start.x - end.x) < 0.01;
|
|
99
|
+
if (!isHoriz && !isVert || existingPaths.length === 0) {
|
|
100
|
+
parts.push(`L ${round1(end.x)} ${round1(end.y)}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const hops = findSegmentHops(start, end, existingPaths, hopRadius);
|
|
104
|
+
if (hops.length === 0) {
|
|
105
|
+
parts.push(`L ${round1(end.x)} ${round1(end.y)}`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const R = hopRadius;
|
|
109
|
+
if (isHoriz) {
|
|
110
|
+
const dx = end.x - start.x;
|
|
111
|
+
for (const h of hops) {
|
|
112
|
+
if (dx > 0) {
|
|
113
|
+
parts.push(`L ${round1(h.x - R)} ${round1(start.y)}`);
|
|
114
|
+
parts.push(`A ${R} ${R} 0 0 0 ${round1(h.x + R)} ${round1(start.y)}`);
|
|
115
|
+
} else {
|
|
116
|
+
parts.push(`L ${round1(h.x + R)} ${round1(start.y)}`);
|
|
117
|
+
parts.push(`A ${R} ${R} 0 0 1 ${round1(h.x - R)} ${round1(start.y)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
parts.push(`L ${round1(end.x)} ${round1(end.y)}`);
|
|
121
|
+
} else if (isVert) {
|
|
122
|
+
const dy = end.y - start.y;
|
|
123
|
+
for (const h of hops) {
|
|
124
|
+
if (dy > 0) {
|
|
125
|
+
parts.push(`L ${round1(start.x)} ${round1(h.y - R)}`);
|
|
126
|
+
parts.push(`A ${R} ${R} 0 0 1 ${round1(start.x)} ${round1(h.y + R)}`);
|
|
127
|
+
} else {
|
|
128
|
+
parts.push(`L ${round1(start.x)} ${round1(h.y + R)}`);
|
|
129
|
+
parts.push(`A ${R} ${R} 0 0 0 ${round1(start.x)} ${round1(h.y - R)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
parts.push(`L ${round1(end.x)} ${round1(end.y)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function toPathD(points, cornerRadius = 14, existingPaths = []) {
|
|
53
136
|
if (points.length < 2) return "";
|
|
54
137
|
if (points.length === 2) {
|
|
55
|
-
const [
|
|
56
|
-
|
|
138
|
+
const parts2 = [`M ${round1(points[0].x)} ${round1(points[0].y)}`];
|
|
139
|
+
appendSegmentWithHops(parts2, points[0], points[1], existingPaths);
|
|
140
|
+
return parts2.join(" ");
|
|
57
141
|
}
|
|
58
142
|
if (cornerRadius <= 0) {
|
|
59
|
-
|
|
143
|
+
const parts2 = [`M ${round1(points[0].x)} ${round1(points[0].y)}`];
|
|
144
|
+
for (let i = 1; i < points.length; i++) {
|
|
145
|
+
appendSegmentWithHops(parts2, points[i - 1], points[i], existingPaths);
|
|
146
|
+
}
|
|
147
|
+
return parts2.join(" ");
|
|
60
148
|
}
|
|
61
149
|
const parts = [`M ${round1(points[0].x)} ${round1(points[0].y)}`];
|
|
150
|
+
let prevCornerEnd = points[0];
|
|
62
151
|
for (let i = 1; i < points.length; i++) {
|
|
63
152
|
const prev = points[i - 1];
|
|
64
153
|
const cur = points[i];
|
|
65
154
|
const next = points[i + 1];
|
|
66
155
|
if (!next) {
|
|
67
|
-
parts
|
|
156
|
+
appendSegmentWithHops(parts, prevCornerEnd, cur, existingPaths);
|
|
68
157
|
continue;
|
|
69
158
|
}
|
|
70
159
|
const dx1 = cur.x - prev.x;
|
|
@@ -73,8 +162,9 @@ function toPathD(points, cornerRadius = 8) {
|
|
|
73
162
|
const dy2 = next.y - cur.y;
|
|
74
163
|
const len1 = Math.hypot(dx1, dy1);
|
|
75
164
|
const len2 = Math.hypot(dx2, dy2);
|
|
76
|
-
if (len1 < 0.
|
|
77
|
-
parts
|
|
165
|
+
if (len1 < 0.5 || len2 < 0.5) {
|
|
166
|
+
appendSegmentWithHops(parts, prevCornerEnd, cur, existingPaths);
|
|
167
|
+
prevCornerEnd = cur;
|
|
78
168
|
continue;
|
|
79
169
|
}
|
|
80
170
|
const r = Math.min(cornerRadius, len1 / 2, len2 / 2);
|
|
@@ -82,21 +172,22 @@ function toPathD(points, cornerRadius = 8) {
|
|
|
82
172
|
const by = cur.y - dy1 / len1 * r;
|
|
83
173
|
const ax = cur.x + dx2 / len2 * r;
|
|
84
174
|
const ay = cur.y + dy2 / len2 * r;
|
|
85
|
-
parts
|
|
175
|
+
appendSegmentWithHops(parts, prevCornerEnd, { x: bx, y: by }, existingPaths);
|
|
86
176
|
parts.push(`Q ${round1(cur.x)} ${round1(cur.y)} ${round1(ax)} ${round1(ay)}`);
|
|
177
|
+
prevCornerEnd = { x: ax, y: ay };
|
|
87
178
|
}
|
|
88
179
|
return parts.join(" ");
|
|
89
180
|
}
|
|
90
181
|
function selfLoopPath(rect) {
|
|
91
182
|
const top = rect.y;
|
|
92
|
-
const left = rect.x + rect.width * 0.
|
|
93
|
-
const right = rect.x + rect.width * 0.
|
|
94
|
-
const apex = top -
|
|
183
|
+
const left = rect.x + rect.width * 0.3;
|
|
184
|
+
const right = rect.x + rect.width * 0.7;
|
|
185
|
+
const apex = top - 40;
|
|
95
186
|
return [
|
|
96
|
-
{ x: left, y: top },
|
|
97
|
-
{ x: left, y: apex },
|
|
98
|
-
{ x: right, y: apex },
|
|
99
|
-
{ x: right, y: top }
|
|
187
|
+
{ x: round1(left), y: round1(top) },
|
|
188
|
+
{ x: round1(left), y: round1(apex) },
|
|
189
|
+
{ x: round1(right), y: round1(apex) },
|
|
190
|
+
{ x: round1(right), y: round1(top) }
|
|
100
191
|
];
|
|
101
192
|
}
|
|
102
193
|
function polylineLength(points) {
|
|
@@ -109,56 +200,49 @@ function polylineLength(points) {
|
|
|
109
200
|
function segmentLength(a, b) {
|
|
110
201
|
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
111
202
|
}
|
|
112
|
-
var LABEL_BOX_HEIGHT =
|
|
113
|
-
function rectsOverlap(a, b) {
|
|
114
|
-
return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
|
|
115
|
-
}
|
|
203
|
+
var LABEL_BOX_HEIGHT = 18;
|
|
116
204
|
function overlapCount(rect, obstacles) {
|
|
117
205
|
let hits = 0;
|
|
118
206
|
for (const o of obstacles) if (rectsOverlap(rect, o)) hits++;
|
|
119
207
|
return hits;
|
|
120
208
|
}
|
|
121
209
|
function placeFlowLabel(points, textWidth, obstacles, bounds) {
|
|
122
|
-
|
|
123
|
-
|
|
210
|
+
const halfW = textWidth / 2 + 5;
|
|
211
|
+
const halfH = LABEL_BOX_HEIGHT / 2 + 1;
|
|
212
|
+
const pad = 12;
|
|
213
|
+
let bestPlacement = null;
|
|
214
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
215
|
+
const stepX = Math.max(18, halfW + 6);
|
|
216
|
+
const stepY = Math.max(18, halfH + 6);
|
|
124
217
|
for (let i = 0; i < points.length - 1; i++) {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (hits < fallbackHits) {
|
|
154
|
-
fallbackHits = hits;
|
|
155
|
-
fallback = { x: round1(cx), y: round1(cy), rect };
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return fallback ?? {
|
|
159
|
-
x: round1(mid.x),
|
|
160
|
-
y: round1(mid.y),
|
|
161
|
-
rect: { x1: mid.x - half, y1: mid.y - halfH, x2: mid.x + half, y2: mid.y + halfH }
|
|
218
|
+
const a = points[i];
|
|
219
|
+
const b = points[i + 1];
|
|
220
|
+
const segLen = segmentLength(a, b);
|
|
221
|
+
if (segLen < 1) continue;
|
|
222
|
+
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
223
|
+
const horizontal = Math.abs(a.x - b.x) >= Math.abs(a.y - b.y);
|
|
224
|
+
const offsets = [0];
|
|
225
|
+
for (let k = 1; k <= 8; k++) {
|
|
226
|
+
offsets.push(-k * (horizontal ? stepX : stepY), k * (horizontal ? stepX : stepY));
|
|
227
|
+
}
|
|
228
|
+
for (const off of offsets) {
|
|
229
|
+
const cx = clamp(horizontal ? mid.x + off : mid.x, pad + halfW, bounds.width - pad - halfW);
|
|
230
|
+
const cy = clamp(horizontal ? mid.y : mid.y + off, pad + halfH, bounds.height - pad - halfH);
|
|
231
|
+
const rect = { x1: cx - halfW, y1: cy - halfH, x2: cx + halfW, y2: cy + halfH };
|
|
232
|
+
const hits = overlapCount(rect, obstacles);
|
|
233
|
+
const score = hits * 1e5 + (horizontal ? 0 : 40) + Math.abs(off) * 2 - Math.min(100, segLen) * 0.1;
|
|
234
|
+
if (score < bestScore) {
|
|
235
|
+
bestScore = score;
|
|
236
|
+
bestPlacement = { x: round1(cx), y: round1(cy), rect };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (bestPlacement) return bestPlacement;
|
|
241
|
+
const firstMid = points.length >= 2 ? { x: (points[0].x + points[1].x) / 2, y: (points[0].y + points[1].y) / 2 } : { x: 0, y: 0 };
|
|
242
|
+
return {
|
|
243
|
+
x: round1(firstMid.x),
|
|
244
|
+
y: round1(firstMid.y),
|
|
245
|
+
rect: { x1: firstMid.x - halfW, y1: firstMid.y - halfH, x2: firstMid.x + halfW, y2: firstMid.y + halfH }
|
|
162
246
|
};
|
|
163
247
|
}
|
|
164
248
|
function clamp(n, min, max) {
|
|
@@ -166,16 +250,19 @@ function clamp(n, min, max) {
|
|
|
166
250
|
return Math.min(max, Math.max(min, n));
|
|
167
251
|
}
|
|
168
252
|
function clampPointToScene(point, bounds) {
|
|
169
|
-
const pad =
|
|
253
|
+
const pad = 16;
|
|
170
254
|
return {
|
|
171
255
|
x: round1(clamp(point.x, pad, bounds.width - pad)),
|
|
172
256
|
y: round1(clamp(point.y, pad, bounds.height - pad))
|
|
173
257
|
};
|
|
174
258
|
}
|
|
175
259
|
function laneOffset(lane) {
|
|
176
|
-
if (lane
|
|
177
|
-
|
|
178
|
-
|
|
260
|
+
if (lane === 0) return -8;
|
|
261
|
+
if (lane === 1) return 8;
|
|
262
|
+
if (lane === 2) return -16;
|
|
263
|
+
if (lane === 3) return 16;
|
|
264
|
+
const step = Math.ceil((lane + 1) / 2);
|
|
265
|
+
return (lane % 2 === 1 ? 1 : -1) * step * 8;
|
|
179
266
|
}
|
|
180
267
|
function routeLength(points) {
|
|
181
268
|
let total = 0;
|
|
@@ -196,63 +283,170 @@ function routeBends(points) {
|
|
|
196
283
|
}
|
|
197
284
|
return bends;
|
|
198
285
|
}
|
|
286
|
+
function cleanCollinearPoints(points) {
|
|
287
|
+
if (points.length <= 2) return points;
|
|
288
|
+
const result = [points[0]];
|
|
289
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
290
|
+
const prev = result[result.length - 1];
|
|
291
|
+
const cur = points[i];
|
|
292
|
+
const next = points[i + 1];
|
|
293
|
+
const isCollinearX = Math.abs(prev.x - cur.x) < 0.01 && Math.abs(cur.x - next.x) < 0.01;
|
|
294
|
+
const isCollinearY = Math.abs(prev.y - cur.y) < 0.01 && Math.abs(cur.y - next.y) < 0.01;
|
|
295
|
+
const isDuplicate = Math.abs(prev.x - cur.x) < 0.01 && Math.abs(prev.y - cur.y) < 0.01;
|
|
296
|
+
if (!isCollinearX && !isCollinearY && !isDuplicate) {
|
|
297
|
+
result.push(cur);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
result.push(points[points.length - 1]);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
199
303
|
function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
|
|
200
304
|
const laneShift = laneOffset(lane);
|
|
201
305
|
const sourceCenter = rectCenter(sourceRect);
|
|
202
306
|
const targetCenter = rectCenter(targetRect);
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} : {
|
|
208
|
-
x: clamp(sourceCenter.x + laneShift, sourceRect.x1 + 12, sourceRect.x2 - 12),
|
|
209
|
-
y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1
|
|
210
|
-
};
|
|
211
|
-
const target = horizontalPrimary ? {
|
|
212
|
-
x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2,
|
|
213
|
-
y: clamp(targetCenter.y + laneShift, targetRect.y1 + 12, targetRect.y2 - 12)
|
|
214
|
-
} : {
|
|
215
|
-
x: clamp(targetCenter.x + laneShift, targetRect.x1 + 12, targetRect.x2 - 12),
|
|
216
|
-
y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2
|
|
217
|
-
};
|
|
218
|
-
const infl = obstacles.map((o) => inflateRect(o, 8));
|
|
307
|
+
const dx = targetCenter.x - sourceCenter.x;
|
|
308
|
+
const dy = targetCenter.y - sourceCenter.y;
|
|
309
|
+
const stubLen = 18;
|
|
310
|
+
const allObstacles = obstacles;
|
|
219
311
|
const candidates = [];
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
312
|
+
if (dx >= 30) {
|
|
313
|
+
let sY = sourceCenter.y;
|
|
314
|
+
let tY = targetCenter.y;
|
|
315
|
+
if (Math.abs(dy) > 16) {
|
|
316
|
+
const dirSign = Math.sign(dy);
|
|
317
|
+
sY = clamp(sourceCenter.y + dirSign * 12, sourceRect.y1 + 10, sourceRect.y2 - 10);
|
|
318
|
+
tY = clamp(targetCenter.y - dirSign * 12, targetRect.y1 + 10, targetRect.y2 - 10);
|
|
319
|
+
} else {
|
|
320
|
+
sY = clamp(sourceCenter.y + (lane > 0 ? lane % 2 === 1 ? 4 : -4 : 0), sourceRect.y1 + 10, sourceRect.y2 - 10);
|
|
321
|
+
tY = clamp(targetCenter.y + (lane > 0 ? lane % 2 === 1 ? 4 : -4 : 0), targetRect.y1 + 10, targetRect.y2 - 10);
|
|
322
|
+
}
|
|
323
|
+
const sPort = { x: sourceRect.x2, y: sY };
|
|
324
|
+
const tPort = { x: targetRect.x1, y: tY };
|
|
325
|
+
if (Math.abs(sY - tY) < 1) {
|
|
326
|
+
candidates.push([sPort, tPort]);
|
|
327
|
+
}
|
|
328
|
+
const rawMidX = (sPort.x + tPort.x) / 2 + laneShift;
|
|
329
|
+
const midX = round1(clamp(rawMidX, sourceRect.x2 + 8, targetRect.x1 - 8));
|
|
330
|
+
candidates.push([
|
|
331
|
+
sPort,
|
|
332
|
+
{ x: midX, y: sY },
|
|
333
|
+
{ x: midX, y: tY },
|
|
334
|
+
tPort
|
|
335
|
+
]);
|
|
336
|
+
let minObstacleY = Math.min(sourceRect.y1, targetRect.y1);
|
|
337
|
+
for (const o of allObstacles) {
|
|
338
|
+
if (o.x2 > sourceRect.x2 && o.x1 < targetRect.x1) {
|
|
339
|
+
minObstacleY = Math.min(minObstacleY, o.y1);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const bypassYTop = Math.max(16, minObstacleY - 24 - Math.abs(laneShift));
|
|
343
|
+
candidates.push([
|
|
344
|
+
{ x: sourceCenter.x, y: sourceRect.y1 },
|
|
345
|
+
{ x: sourceCenter.x, y: bypassYTop },
|
|
346
|
+
{ x: targetCenter.x, y: bypassYTop },
|
|
347
|
+
{ x: targetCenter.x, y: targetRect.y1 }
|
|
348
|
+
]);
|
|
349
|
+
} else if (dx < -30) {
|
|
350
|
+
const sX = clamp(sourceCenter.x + (lane > 0 ? lane % 2 === 1 ? 8 : -8 : 0), sourceRect.x1 + 16, sourceRect.x2 - 16);
|
|
351
|
+
const tX = clamp(targetCenter.x + (lane > 0 ? lane % 2 === 1 ? 8 : -8 : 0), targetRect.x1 + 16, targetRect.x2 - 16);
|
|
352
|
+
let minObstacleY = Math.min(sourceRect.y1, targetRect.y1);
|
|
353
|
+
for (const o of allObstacles) {
|
|
354
|
+
if (o.x2 > targetRect.x1 && o.x1 < sourceRect.x2) {
|
|
355
|
+
minObstacleY = Math.min(minObstacleY, o.y1);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const highwayYTop = Math.max(16, minObstacleY - 28 - Math.abs(laneShift));
|
|
359
|
+
candidates.push([
|
|
360
|
+
{ x: sX, y: sourceRect.y1 },
|
|
361
|
+
{ x: sX, y: highwayYTop },
|
|
362
|
+
{ x: tX, y: highwayYTop },
|
|
363
|
+
{ x: tX, y: targetRect.y1 }
|
|
364
|
+
]);
|
|
365
|
+
let maxObstacleY = Math.max(sourceRect.y2, targetRect.y2);
|
|
366
|
+
for (const o of allObstacles) {
|
|
367
|
+
if (o.x2 > targetRect.x1 && o.x1 < sourceRect.x2) {
|
|
368
|
+
maxObstacleY = Math.max(maxObstacleY, o.y2);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const highwayYBottom = Math.min(bounds.height - 16, maxObstacleY + 28 + Math.abs(laneShift));
|
|
372
|
+
candidates.push([
|
|
373
|
+
{ x: sX, y: sourceRect.y2 },
|
|
374
|
+
{ x: sX, y: highwayYBottom },
|
|
375
|
+
{ x: tX, y: highwayYBottom },
|
|
376
|
+
{ x: tX, y: targetRect.y2 }
|
|
377
|
+
]);
|
|
378
|
+
let sY = sourceCenter.y;
|
|
379
|
+
let tY = targetCenter.y;
|
|
380
|
+
if (Math.abs(dy) > 16) {
|
|
381
|
+
const dirSign = Math.sign(dy);
|
|
382
|
+
sY = clamp(sourceCenter.y + dirSign * 12, sourceRect.y1 + 10, sourceRect.y2 - 10);
|
|
383
|
+
tY = clamp(targetCenter.y - dirSign * 12, targetRect.y1 + 10, targetRect.y2 - 10);
|
|
384
|
+
}
|
|
385
|
+
const sPortL = { x: sourceRect.x1, y: sY };
|
|
386
|
+
const tPortR = { x: targetRect.x2, y: tY };
|
|
387
|
+
const midX = round1((sPortL.x + tPortR.x) / 2 + laneShift);
|
|
388
|
+
candidates.push([
|
|
389
|
+
sPortL,
|
|
390
|
+
{ x: midX, y: sY },
|
|
391
|
+
{ x: midX, y: tY },
|
|
392
|
+
tPortR
|
|
393
|
+
]);
|
|
394
|
+
} else {
|
|
395
|
+
const sX = clamp(sourceCenter.x + laneShift, sourceRect.x1 + 16, sourceRect.x2 - 16);
|
|
396
|
+
const tX = clamp(targetCenter.x + laneShift, targetRect.x1 + 16, targetRect.x2 - 16);
|
|
397
|
+
const sourceDown = dy >= 0;
|
|
398
|
+
const sPort = { x: sX, y: sourceDown ? sourceRect.y2 : sourceRect.y1 };
|
|
399
|
+
const tPort = { x: tX, y: sourceDown ? targetRect.y1 : targetRect.y2 };
|
|
400
|
+
if (Math.abs(sX - tX) < 1) {
|
|
401
|
+
candidates.push([sPort, tPort]);
|
|
402
|
+
} else {
|
|
403
|
+
const midY = round1((sPort.y + tPort.y) / 2 + laneShift);
|
|
404
|
+
candidates.push([
|
|
405
|
+
sPort,
|
|
406
|
+
{ x: sPort.x, y: midY },
|
|
407
|
+
{ x: tPort.x, y: midY },
|
|
408
|
+
tPort
|
|
409
|
+
]);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const obstacle of allObstacles) {
|
|
413
|
+
const bypassYTop = Math.max(16, obstacle.y1 - 18 - Math.abs(laneShift));
|
|
414
|
+
const bypassYBottom = Math.min(bounds.height - 16, obstacle.y2 + 18 + Math.abs(laneShift));
|
|
415
|
+
const sourceRight = dx >= 0;
|
|
416
|
+
const targetLeft = dx >= 0;
|
|
417
|
+
const sP = { x: sourceRight ? sourceRect.x2 : sourceRect.x1, y: sourceCenter.y };
|
|
418
|
+
const tP = { x: targetLeft ? targetRect.x1 : targetRect.x2, y: targetCenter.y };
|
|
419
|
+
candidates.push([
|
|
420
|
+
sP,
|
|
421
|
+
{ x: sP.x + (sourceRight ? stubLen : -stubLen), y: sP.y },
|
|
422
|
+
{ x: sP.x + (sourceRight ? stubLen : -stubLen), y: bypassYTop },
|
|
423
|
+
{ x: tP.x + (targetLeft ? -stubLen : stubLen), y: bypassYTop },
|
|
424
|
+
{ x: tP.x + (targetLeft ? -stubLen : stubLen), y: tP.y },
|
|
425
|
+
tP
|
|
426
|
+
]);
|
|
427
|
+
candidates.push([
|
|
428
|
+
sP,
|
|
429
|
+
{ x: sP.x + (sourceRight ? stubLen : -stubLen), y: sP.y },
|
|
430
|
+
{ x: sP.x + (sourceRight ? stubLen : -stubLen), y: bypassYBottom },
|
|
431
|
+
{ x: tP.x + (targetLeft ? -stubLen : stubLen), y: bypassYBottom },
|
|
432
|
+
{ x: tP.x + (targetLeft ? -stubLen : stubLen), y: tP.y },
|
|
433
|
+
tP
|
|
434
|
+
]);
|
|
435
|
+
}
|
|
245
436
|
let best = candidates[0];
|
|
246
437
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
247
438
|
for (const candidate of candidates) {
|
|
248
|
-
const
|
|
249
|
-
const
|
|
439
|
+
const cleaned = cleanCollinearPoints(candidate);
|
|
440
|
+
const hits = countPathIntersections(cleaned, allObstacles);
|
|
441
|
+
const bends = routeBends(cleaned);
|
|
442
|
+
const length = routeLength(cleaned);
|
|
443
|
+
const score = hits * 1e6 + bends * 500 + length;
|
|
250
444
|
if (score < bestScore) {
|
|
251
|
-
best = candidate;
|
|
252
445
|
bestScore = score;
|
|
446
|
+
best = cleaned;
|
|
253
447
|
}
|
|
254
448
|
}
|
|
255
|
-
return best.map((
|
|
449
|
+
return cleanCollinearPoints(best.map((p) => clampPointToScene(p, bounds)));
|
|
256
450
|
}
|
|
257
451
|
|
|
258
452
|
// src/edges.ts
|
|
@@ -324,23 +518,25 @@ function ensureDefs(svg, theme, id) {
|
|
|
324
518
|
for (const [kind, color] of Object.entries(theme.edges)) {
|
|
325
519
|
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
326
520
|
arrow.setAttribute("id", `${id}-arrow-${kind}`);
|
|
327
|
-
arrow.setAttribute("viewBox", "0 0
|
|
328
|
-
arrow.setAttribute("refX", "
|
|
329
|
-
arrow.setAttribute("refY", "
|
|
330
|
-
arrow.setAttribute("markerWidth", "
|
|
331
|
-
arrow.setAttribute("markerHeight", "
|
|
521
|
+
arrow.setAttribute("viewBox", "0 0 12 12");
|
|
522
|
+
arrow.setAttribute("refX", "9.5");
|
|
523
|
+
arrow.setAttribute("refY", "6");
|
|
524
|
+
arrow.setAttribute("markerWidth", "8");
|
|
525
|
+
arrow.setAttribute("markerHeight", "8");
|
|
332
526
|
arrow.setAttribute("orient", "auto-start-reverse");
|
|
333
527
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
334
528
|
if (kind === "response") {
|
|
335
|
-
path.setAttribute("d", "M
|
|
529
|
+
path.setAttribute("d", "M 2.5 2.5 L 9.5 6 L 2.5 9.5");
|
|
336
530
|
path.setAttribute("fill", "none");
|
|
337
531
|
path.setAttribute("stroke", color);
|
|
338
|
-
path.setAttribute("stroke-width", "1.
|
|
532
|
+
path.setAttribute("stroke-width", "1.6");
|
|
533
|
+
path.setAttribute("stroke-linecap", "round");
|
|
534
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
339
535
|
} else if (kind === "event") {
|
|
340
|
-
path.setAttribute("d", "M
|
|
536
|
+
path.setAttribute("d", "M 6 2.5 A 3.5 3.5 0 1 1 6 9.5 A 3.5 3.5 0 1 1 6 2.5");
|
|
341
537
|
path.setAttribute("fill", color);
|
|
342
538
|
} else {
|
|
343
|
-
path.setAttribute("d", "M
|
|
539
|
+
path.setAttribute("d", "M 2 2.5 L 10 6 L 2 9.5 L 4 6 Z");
|
|
344
540
|
path.setAttribute("fill", color);
|
|
345
541
|
}
|
|
346
542
|
arrow.appendChild(path);
|
|
@@ -367,16 +563,38 @@ function ensureDefs(svg, theme, id) {
|
|
|
367
563
|
defs.appendChild(filter);
|
|
368
564
|
svg.prepend(defs);
|
|
369
565
|
}
|
|
370
|
-
function
|
|
566
|
+
function isDarkTheme(theme) {
|
|
567
|
+
if (theme.name === "paper" || theme.name === "editorial" || theme.name === "sketchy") {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
const canvas = (theme.canvas || theme.surface || "").trim();
|
|
571
|
+
if (canvas.startsWith("#")) {
|
|
572
|
+
const hex = canvas.slice(1);
|
|
573
|
+
const r = parseInt(hex.length === 3 ? hex[0] + hex[0] : hex.slice(0, 2), 16) || 0;
|
|
574
|
+
const g = parseInt(hex.length === 3 ? hex[1] + hex[1] : hex.slice(2, 4), 16) || 0;
|
|
575
|
+
const b = parseInt(hex.length === 3 ? hex[2] + hex[2] : hex.slice(4, 6), 16) || 0;
|
|
576
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
577
|
+
return luminance < 0.5;
|
|
578
|
+
}
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
function computeEdgeLabelColor(edgeColor, isDark) {
|
|
582
|
+
if (isDark) {
|
|
583
|
+
return `color-mix(in srgb, ${edgeColor} 65%, #f8fafc)`;
|
|
584
|
+
}
|
|
585
|
+
return `color-mix(in srgb, ${edgeColor} 75%, #090d16)`;
|
|
586
|
+
}
|
|
587
|
+
function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane, existingPaths = []) {
|
|
371
588
|
ensureDefs(svg, theme, sceneId);
|
|
372
589
|
const color = theme.edges[kind];
|
|
373
590
|
const style = EDGE_STYLES[kind];
|
|
374
591
|
const isSelfLoop = from.id === to.id;
|
|
375
592
|
const points = isSelfLoop ? dedupePoints(selfLoopPath(from)) : dedupePoints(routeEdgePoints(from, to, routeObstacles, bounds, lane));
|
|
376
|
-
const d = toPathD(points);
|
|
593
|
+
const d = toPathD(points, 14, existingPaths);
|
|
377
594
|
const len = polylineLength(points);
|
|
378
595
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
379
596
|
group.setAttribute("data-edge-kind", kind);
|
|
597
|
+
group.setAttribute("class", `markdy-edge markdy-edge--${kind}`);
|
|
380
598
|
group.style.opacity = "0";
|
|
381
599
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
382
600
|
path.setAttribute("d", d);
|
|
@@ -385,47 +603,57 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
385
603
|
path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2");
|
|
386
604
|
path.setAttribute("stroke-linejoin", "round");
|
|
387
605
|
path.setAttribute("stroke-linecap", "round");
|
|
606
|
+
path.setAttribute("class", `markdy-edge-path markdy-edge-path--${kind}`);
|
|
388
607
|
if (style.dash) path.setAttribute("stroke-dasharray", style.dash);
|
|
389
608
|
if (style.marker !== "none") {
|
|
390
609
|
path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
|
|
391
610
|
}
|
|
392
611
|
if (kind !== "dependency") {
|
|
393
|
-
path.style.filter = `drop-shadow(0 0
|
|
612
|
+
path.style.filter = `drop-shadow(0 0 4px ${translucentColor(color, "44")})`;
|
|
394
613
|
}
|
|
395
614
|
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
396
|
-
dot.setAttribute("r", "
|
|
615
|
+
dot.setAttribute("r", "4");
|
|
397
616
|
dot.setAttribute("fill", color);
|
|
617
|
+
dot.setAttribute("class", "markdy-edge-dot");
|
|
398
618
|
dot.style.opacity = "0";
|
|
399
|
-
dot.style.filter = `drop-shadow(0 0
|
|
619
|
+
dot.style.filter = `drop-shadow(0 0 6px ${color}) drop-shadow(0 0 12px ${color}88)`;
|
|
400
620
|
group.append(path, dot);
|
|
401
621
|
let labelEl;
|
|
402
622
|
let labelRect;
|
|
403
623
|
if (label) {
|
|
404
|
-
const
|
|
624
|
+
const isDark = isDarkTheme(theme);
|
|
625
|
+
const labelColor = computeEdgeLabelColor(color, isDark);
|
|
626
|
+
const textWidth = Math.max(36, label.length * 6.6 + 8);
|
|
405
627
|
const placement = placeFlowLabel(points, textWidth, labelObstacles, bounds);
|
|
406
628
|
labelRect = placement.rect;
|
|
407
629
|
const plate = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
408
|
-
const padX =
|
|
630
|
+
const padX = 6;
|
|
409
631
|
const halfW = textWidth / 2;
|
|
632
|
+
plate.setAttribute("class", "markdy-edge-plate");
|
|
410
633
|
plate.setAttribute("x", String(placement.x - halfW - padX));
|
|
411
634
|
plate.setAttribute("y", String(placement.y - 9));
|
|
412
635
|
plate.setAttribute("width", String(textWidth + padX * 2));
|
|
413
636
|
plate.setAttribute("height", "18");
|
|
414
|
-
plate.setAttribute("rx", "
|
|
415
|
-
plate.setAttribute("
|
|
416
|
-
plate.setAttribute("fill
|
|
417
|
-
plate.setAttribute("
|
|
418
|
-
plate.setAttribute("stroke
|
|
637
|
+
plate.setAttribute("rx", "4");
|
|
638
|
+
plate.setAttribute("ry", "4");
|
|
639
|
+
plate.setAttribute("fill", theme.canvas ?? theme.surface ?? "#ffffff");
|
|
640
|
+
plate.setAttribute("fill-opacity", "0.85");
|
|
641
|
+
plate.setAttribute("stroke", translucentColor(color, "28"));
|
|
642
|
+
plate.setAttribute("stroke-width", "0.85");
|
|
419
643
|
plate.style.opacity = "0";
|
|
644
|
+
plate.style.filter = "none";
|
|
420
645
|
group.appendChild(plate);
|
|
421
646
|
labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
647
|
+
labelEl.setAttribute("class", "markdy-edge-label");
|
|
422
648
|
labelEl.setAttribute("x", String(placement.x));
|
|
423
|
-
labelEl.setAttribute("y", String(placement.y));
|
|
649
|
+
labelEl.setAttribute("y", String(placement.y + 0.5));
|
|
424
650
|
labelEl.setAttribute("text-anchor", "middle");
|
|
425
651
|
labelEl.setAttribute("dominant-baseline", "middle");
|
|
426
|
-
labelEl.setAttribute("font-size", "
|
|
427
|
-
labelEl.setAttribute("font-
|
|
428
|
-
labelEl.setAttribute("
|
|
652
|
+
labelEl.setAttribute("font-size", "10.5");
|
|
653
|
+
labelEl.setAttribute("font-weight", "500");
|
|
654
|
+
labelEl.setAttribute("letter-spacing", "0.02em");
|
|
655
|
+
labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace");
|
|
656
|
+
labelEl.setAttribute("fill", labelColor);
|
|
429
657
|
labelEl.textContent = label;
|
|
430
658
|
labelEl.style.opacity = "0";
|
|
431
659
|
group.appendChild(labelEl);
|
|
@@ -450,6 +678,11 @@ function setEdgeVisible(runtime, visible) {
|
|
|
450
678
|
runtime.group.style.opacity = visible ? "1" : "0";
|
|
451
679
|
if (runtime.label) runtime.label.style.opacity = visible ? "1" : "0";
|
|
452
680
|
if (runtime.labelPlate) runtime.labelPlate.style.opacity = visible ? "1" : "0";
|
|
681
|
+
if (visible) {
|
|
682
|
+
runtime.path.classList.add("markdy-edge-path--flowing");
|
|
683
|
+
} else {
|
|
684
|
+
runtime.path.classList.remove("markdy-edge-path--flowing");
|
|
685
|
+
}
|
|
453
686
|
}
|
|
454
687
|
function translucentColor(color, alpha = "aa") {
|
|
455
688
|
const value = color.trim();
|
|
@@ -461,8 +694,16 @@ function translucentColor(color, alpha = "aa") {
|
|
|
461
694
|
return `color-mix(in srgb, ${value} 67%, transparent)`;
|
|
462
695
|
}
|
|
463
696
|
function nextEdgeLane(lanes, from, to) {
|
|
464
|
-
const
|
|
465
|
-
const
|
|
697
|
+
const fromId = typeof from === "string" ? from : from.id;
|
|
698
|
+
const toId = typeof to === "string" ? to : to.id;
|
|
699
|
+
const pair = [fromId, toId].sort().join("|");
|
|
700
|
+
const keys = [`pair:${pair}`, `out:${fromId}`, `in:${toId}`];
|
|
701
|
+
if (typeof from !== "string" && typeof to !== "string") {
|
|
702
|
+
const colFrom = Math.round(from.x / 80);
|
|
703
|
+
const colTo = Math.round(to.x / 80);
|
|
704
|
+
const corridorKey = colFrom !== colTo ? `corridor-x:${Math.min(colFrom, colTo)}-${Math.max(colFrom, colTo)}` : `corridor-y:${Math.round(from.y / 60)}-${Math.round(to.y / 60)}`;
|
|
705
|
+
keys.push(corridorKey);
|
|
706
|
+
}
|
|
466
707
|
const lane = Math.max(...keys.map((key) => lanes.get(key) ?? 0));
|
|
467
708
|
for (const key of keys) lanes.set(key, (lanes.get(key) ?? 0) + 1);
|
|
468
709
|
return lane;
|
|
@@ -492,20 +733,35 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
492
733
|
path.style.strokeDasharray = String(pathLen);
|
|
493
734
|
path.style.strokeDashoffset = String(pathLen);
|
|
494
735
|
}
|
|
495
|
-
const drawMs = Math.min(
|
|
496
|
-
anims.push(
|
|
736
|
+
const drawMs = Math.min(260, durMs * 0.65);
|
|
737
|
+
anims.push(
|
|
738
|
+
group.animate(
|
|
739
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
740
|
+
{ duration: 140, delay: startMs, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
741
|
+
)
|
|
742
|
+
);
|
|
497
743
|
if (runtime.drawReveal) {
|
|
498
744
|
anims.push(
|
|
499
745
|
path.animate(
|
|
500
746
|
[{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
|
|
501
|
-
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "
|
|
747
|
+
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
502
748
|
)
|
|
503
749
|
);
|
|
504
750
|
}
|
|
505
751
|
if (label) {
|
|
506
|
-
anims.push(
|
|
752
|
+
anims.push(
|
|
753
|
+
label.animate(
|
|
754
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
755
|
+
{ duration: 200, delay: startMs + 90, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
756
|
+
)
|
|
757
|
+
);
|
|
507
758
|
if (runtime.labelPlate) {
|
|
508
|
-
anims.push(
|
|
759
|
+
anims.push(
|
|
760
|
+
runtime.labelPlate.animate(
|
|
761
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
762
|
+
{ duration: 200, delay: startMs + 90, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
763
|
+
)
|
|
764
|
+
);
|
|
509
765
|
}
|
|
510
766
|
}
|
|
511
767
|
if (points.length >= 2) {
|
|
@@ -514,21 +770,49 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
514
770
|
duration: Math.max(drawMs, durMs),
|
|
515
771
|
delay: startMs,
|
|
516
772
|
fill: "forwards",
|
|
517
|
-
easing: "
|
|
773
|
+
easing: "cubic-bezier(0.2, 0.85, 0.4, 1)"
|
|
518
774
|
})
|
|
519
775
|
);
|
|
520
776
|
}
|
|
521
777
|
return anims;
|
|
522
778
|
}
|
|
523
779
|
function animateEdgeEmphasis(runtime, startMs, durMs, strength, color) {
|
|
780
|
+
const anims = [];
|
|
524
781
|
const baseFilter = runtime.kind === "dependency" ? "none" : `drop-shadow(0 0 3px ${translucentColor(runtime.color, "33")})`;
|
|
525
782
|
const glowColor = color ?? runtime.color;
|
|
526
783
|
const radius = Math.max(4, Math.min(16, 5 + strength * 4));
|
|
527
784
|
const peakFilter = `drop-shadow(0 0 ${radius}px ${translucentColor(glowColor)}) brightness(${1 + Math.min(strength, 2) * 0.08})`;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
785
|
+
anims.push(
|
|
786
|
+
runtime.path.animate(
|
|
787
|
+
[{ filter: baseFilter }, { filter: peakFilter }, { filter: baseFilter }],
|
|
788
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
|
|
789
|
+
)
|
|
531
790
|
);
|
|
791
|
+
if (runtime.label) {
|
|
792
|
+
anims.push(
|
|
793
|
+
runtime.label.animate(
|
|
794
|
+
[
|
|
795
|
+
{ filter: "none", opacity: 1 },
|
|
796
|
+
{ filter: `drop-shadow(0 0 4px ${translucentColor(glowColor, "88")}) brightness(1.25)`, opacity: 1 },
|
|
797
|
+
{ filter: "none", opacity: 1 }
|
|
798
|
+
],
|
|
799
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
|
|
800
|
+
)
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (runtime.labelPlate) {
|
|
804
|
+
anims.push(
|
|
805
|
+
runtime.labelPlate.animate(
|
|
806
|
+
[
|
|
807
|
+
{ stroke: translucentColor(runtime.color, "22") },
|
|
808
|
+
{ stroke: translucentColor(glowColor, "88") },
|
|
809
|
+
{ stroke: translucentColor(runtime.color, "22") }
|
|
810
|
+
],
|
|
811
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
|
|
812
|
+
)
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return anims;
|
|
532
816
|
}
|
|
533
817
|
function computeFrameTransform(targetIds, nodes, bounds, requestedZoom = DEFAULT_FRAME_ZOOM) {
|
|
534
818
|
const targets = new Set(targetIds);
|
|
@@ -567,13 +851,14 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
567
851
|
const edgeRectById = new Map(nodes.map((node) => [node.id, boxRect(node)]));
|
|
568
852
|
const edgeLabels = [];
|
|
569
853
|
const edgeLanes = /* @__PURE__ */ new Map();
|
|
854
|
+
const placedPaths = [];
|
|
570
855
|
for (const edge of diagramType === "sequence" ? [] : edges) {
|
|
571
856
|
if (edge.structural || edgeRuntimes.has(edge.id)) continue;
|
|
572
857
|
const from = nodeById.get(edge.from);
|
|
573
858
|
const to = nodeById.get(edge.to);
|
|
574
859
|
if (!from || !to) continue;
|
|
575
|
-
const routeObstacles = [...
|
|
576
|
-
const lane = nextEdgeLane(edgeLanes,
|
|
860
|
+
const routeObstacles = [...allNodeRects, ...edgeLabels.map((l) => inflateRect(l, 4))];
|
|
861
|
+
const lane = nextEdgeLane(edgeLanes, from, to);
|
|
577
862
|
const runtime = createEdgeRuntime(
|
|
578
863
|
svg,
|
|
579
864
|
from,
|
|
@@ -583,10 +868,12 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
583
868
|
theme,
|
|
584
869
|
sceneId,
|
|
585
870
|
routeObstacles,
|
|
586
|
-
[...
|
|
871
|
+
[...allNodeRects, ...edgeLabels],
|
|
587
872
|
bounds,
|
|
588
|
-
lane
|
|
873
|
+
lane,
|
|
874
|
+
placedPaths
|
|
589
875
|
);
|
|
876
|
+
placedPaths.push(runtime.points);
|
|
590
877
|
if (runtime.labelRect) edgeLabels.push(runtime.labelRect);
|
|
591
878
|
edgeRuntimes.set(edge.id, runtime);
|
|
592
879
|
}
|
|
@@ -606,14 +893,14 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
606
893
|
if (el) {
|
|
607
894
|
anims.push(
|
|
608
895
|
el.animate(
|
|
609
|
-
[{ opacity: 0, transform: "translateY(
|
|
610
|
-
{ duration: durMs, delay, fill: "forwards", easing: "
|
|
896
|
+
[{ opacity: 0, transform: "translateY(10px) scale(0.98)" }, { opacity: 1, transform: "translateY(0) scale(1)" }],
|
|
897
|
+
{ duration: durMs, delay, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
611
898
|
)
|
|
612
899
|
);
|
|
613
900
|
return;
|
|
614
901
|
}
|
|
615
902
|
const runtime = edgeRuntimes.get(id);
|
|
616
|
-
if (runtime) anims.push(runtime.group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durMs, delay, fill: "forwards", easing: "
|
|
903
|
+
if (runtime) anims.push(runtime.group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durMs, delay, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }));
|
|
617
904
|
});
|
|
618
905
|
continue;
|
|
619
906
|
}
|
|
@@ -644,13 +931,13 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
644
931
|
{ filter: `drop-shadow(0 0 ${Math.max(4, 4 + strength * 5)}px ${glowColor}) brightness(${peak})` },
|
|
645
932
|
{ filter: "brightness(1)" }
|
|
646
933
|
],
|
|
647
|
-
{ duration: durMs, delay: startMs, fill: "none", easing: "
|
|
934
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
648
935
|
)
|
|
649
936
|
);
|
|
650
937
|
continue;
|
|
651
938
|
}
|
|
652
939
|
const runtime = edgeRuntimes.get(id);
|
|
653
|
-
if (runtime) anims.push(animateEdgeEmphasis(runtime, startMs, durMs, strength, typeof cue.params.color === "string" ? cue.params.color : void 0));
|
|
940
|
+
if (runtime) anims.push(...animateEdgeEmphasis(runtime, startMs, durMs, strength, typeof cue.params.color === "string" ? cue.params.color : void 0));
|
|
654
941
|
}
|
|
655
942
|
continue;
|
|
656
943
|
}
|
|
@@ -666,13 +953,13 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
666
953
|
{ transform: `scale(${zoom})`, filter: "drop-shadow(0 0 7px var(--md-accent))" },
|
|
667
954
|
{ transform: "scale(1)", filter: "none" }
|
|
668
955
|
],
|
|
669
|
-
{ duration: durMs, delay: startMs, fill: "none", easing: "
|
|
956
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
670
957
|
)
|
|
671
958
|
);
|
|
672
959
|
continue;
|
|
673
960
|
}
|
|
674
961
|
const runtime = edgeRuntimes.get(id);
|
|
675
|
-
if (runtime) anims.push(animateEdgeEmphasis(runtime, startMs, durMs, zoom, void 0));
|
|
962
|
+
if (runtime) anims.push(...animateEdgeEmphasis(runtime, startMs, durMs, zoom, void 0));
|
|
676
963
|
}
|
|
677
964
|
continue;
|
|
678
965
|
}
|
|
@@ -691,7 +978,7 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
691
978
|
anims.push(
|
|
692
979
|
scene.animate(
|
|
693
980
|
[{ transform: cameraTransform }, { transform: nextTransform }],
|
|
694
|
-
{ duration: durMs, delay: startMs, fill: "forwards", easing: "
|
|
981
|
+
{ duration: durMs, delay: startMs, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
695
982
|
)
|
|
696
983
|
);
|
|
697
984
|
cameraTransform = nextTransform;
|
|
@@ -702,22 +989,46 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
702
989
|
const from = nodeById.get(seg.from);
|
|
703
990
|
const to = nodeById.get(seg.to);
|
|
704
991
|
if (!from || !to) continue;
|
|
705
|
-
const routeObstacles = [];
|
|
706
|
-
|
|
707
|
-
if (id !== seg.from && id !== seg.to) routeObstacles.push(rect);
|
|
708
|
-
}
|
|
709
|
-
const lane = nextEdgeLane(laneByPair, seg.from, seg.to);
|
|
992
|
+
const routeObstacles = [...allNodeRects, ...placedLabels.map((l) => inflateRect(l, 4))];
|
|
993
|
+
const lane = nextEdgeLane(laneByPair, from, to);
|
|
710
994
|
const labelObstacles = [...allNodeRects, ...placedLabels];
|
|
711
995
|
const edgeId = cue.edgeId ?? edges.find(
|
|
712
996
|
(edge) => !edge.structural && edge.from === seg.from && edge.to === seg.to && edge.kind === seg.op && edge.label === seg.label
|
|
713
997
|
)?.id;
|
|
714
998
|
let runtime = edgeId ? edgeRuntimes.get(edgeId) : void 0;
|
|
715
999
|
if (!runtime) {
|
|
716
|
-
runtime = createEdgeRuntime(
|
|
1000
|
+
runtime = createEdgeRuntime(
|
|
1001
|
+
svg,
|
|
1002
|
+
from,
|
|
1003
|
+
to,
|
|
1004
|
+
seg.op,
|
|
1005
|
+
seg.label,
|
|
1006
|
+
theme,
|
|
1007
|
+
sceneId,
|
|
1008
|
+
routeObstacles,
|
|
1009
|
+
labelObstacles,
|
|
1010
|
+
bounds,
|
|
1011
|
+
lane,
|
|
1012
|
+
placedPaths
|
|
1013
|
+
);
|
|
1014
|
+
placedPaths.push(runtime.points);
|
|
717
1015
|
if (runtime.labelRect) placedLabels.push(runtime.labelRect);
|
|
718
1016
|
if (edgeId) edgeRuntimes.set(edgeId, runtime);
|
|
719
1017
|
}
|
|
720
1018
|
anims.push(...animateEdgeReveal(runtime, startMs, durMs));
|
|
1019
|
+
const toEl = nodeEls.get(seg.to);
|
|
1020
|
+
if (toEl) {
|
|
1021
|
+
anims.push(
|
|
1022
|
+
toEl.animate(
|
|
1023
|
+
[
|
|
1024
|
+
{ transform: "scale(1)", filter: "none" },
|
|
1025
|
+
{ transform: "scale(1.02)", filter: `drop-shadow(0 0 8px ${translucentColor(runtime.color, "66")})` },
|
|
1026
|
+
{ transform: "scale(1)", filter: "none" }
|
|
1027
|
+
],
|
|
1028
|
+
{ duration: 280, delay: startMs + durMs * 0.75, fill: "none", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
1029
|
+
)
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
721
1032
|
}
|
|
722
1033
|
}
|
|
723
1034
|
for (const a of anims) a.pause();
|
|
@@ -732,16 +1043,14 @@ function buildStructuralEdgeAnimations(edges, nodes, theme, scene, bounds, edgeR
|
|
|
732
1043
|
const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
|
|
733
1044
|
const allNodeRects = [...rectById.values()];
|
|
734
1045
|
const placedLabels = [];
|
|
1046
|
+
const placedPaths = [];
|
|
735
1047
|
const laneByPair = /* @__PURE__ */ new Map();
|
|
736
1048
|
const svg = ensureEdgeLayer(scene);
|
|
737
1049
|
for (const edge of structural) {
|
|
738
1050
|
const from = nodeById.get(edge.from);
|
|
739
1051
|
const to = nodeById.get(edge.to);
|
|
740
1052
|
if (!from || !to) continue;
|
|
741
|
-
const routeObstacles = [];
|
|
742
|
-
for (const [id, rect] of rectById) {
|
|
743
|
-
if (id !== edge.from && id !== edge.to) routeObstacles.push(rect);
|
|
744
|
-
}
|
|
1053
|
+
const routeObstacles = [...allNodeRects, ...placedLabels.map((l) => inflateRect(l, 4))];
|
|
745
1054
|
const lane = nextEdgeLane(laneByPair, edge.from, edge.to);
|
|
746
1055
|
const labelObstacles = [...allNodeRects, ...placedLabels];
|
|
747
1056
|
const runtime = createEdgeRuntime(
|
|
@@ -755,8 +1064,10 @@ function buildStructuralEdgeAnimations(edges, nodes, theme, scene, bounds, edgeR
|
|
|
755
1064
|
routeObstacles,
|
|
756
1065
|
labelObstacles,
|
|
757
1066
|
bounds,
|
|
758
|
-
lane
|
|
1067
|
+
lane,
|
|
1068
|
+
placedPaths
|
|
759
1069
|
);
|
|
1070
|
+
placedPaths.push(runtime.points);
|
|
760
1071
|
if (runtime.labelRect) placedLabels.push(runtime.labelRect);
|
|
761
1072
|
setEdgeVisible(runtime, true);
|
|
762
1073
|
edgeRuntimes.set(edge.id, runtime);
|
|
@@ -830,6 +1141,8 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
830
1141
|
const pos = positionForAnnotation(ann.position, bounds, index);
|
|
831
1142
|
const textEl = doc.createElement("div");
|
|
832
1143
|
textEl.className = "markdy-annotation";
|
|
1144
|
+
textEl.dataset.visible = "1";
|
|
1145
|
+
if (ann.intent) textEl.dataset.intent = ann.intent;
|
|
833
1146
|
textEl.textContent = ann.text;
|
|
834
1147
|
textEl.style.left = `${pos.x}px`;
|
|
835
1148
|
textEl.style.top = `${pos.y}px`;
|
|
@@ -837,11 +1150,13 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
837
1150
|
const target = ann.target ? nodeById.get(ann.target) : void 0;
|
|
838
1151
|
if (!target) return;
|
|
839
1152
|
const tx = target.x + target.width / 2;
|
|
840
|
-
const ty = target.y
|
|
841
|
-
const ax = pos.x +
|
|
842
|
-
const ay = pos.y +
|
|
1153
|
+
const ty = target.y - 2;
|
|
1154
|
+
const ax = pos.x > tx ? pos.x : pos.x + 160;
|
|
1155
|
+
const ay = pos.y + 12;
|
|
843
1156
|
const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
844
|
-
|
|
1157
|
+
const midX = (ax + tx) / 2;
|
|
1158
|
+
const midY = Math.min(ay, ty) - 16;
|
|
1159
|
+
path.setAttribute("d", `M ${ax} ${ay} Q ${midX} ${midY} ${tx} ${ty}`);
|
|
845
1160
|
path.setAttribute("fill", "none");
|
|
846
1161
|
const intent = typeof ann.intent === "string" ? ann.intent : "neutral";
|
|
847
1162
|
const leaderColor = intent === "accent" ? theme.accent : intent === "muted" ? theme.soft ?? theme.textMuted : theme.textMuted;
|
|
@@ -854,8 +1169,8 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
854
1169
|
const dot = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
855
1170
|
dot.setAttribute("cx", String(tx));
|
|
856
1171
|
dot.setAttribute("cy", String(ty));
|
|
857
|
-
dot.setAttribute("r", "2");
|
|
858
|
-
dot.setAttribute("fill",
|
|
1172
|
+
dot.setAttribute("r", "2.5");
|
|
1173
|
+
dot.setAttribute("fill", leaderColor);
|
|
859
1174
|
svg.appendChild(dot);
|
|
860
1175
|
});
|
|
861
1176
|
}
|
|
@@ -956,6 +1271,141 @@ function mountConstellationLayer(layer, nodes, theme, bounds) {
|
|
|
956
1271
|
}
|
|
957
1272
|
}
|
|
958
1273
|
|
|
1274
|
+
// src/radar.ts
|
|
1275
|
+
function mountRadarLayer(layer, nodes, theme, bounds) {
|
|
1276
|
+
if (nodes.length < 3) return;
|
|
1277
|
+
const doc = layer.ownerDocument;
|
|
1278
|
+
Object.assign(layer.style, {
|
|
1279
|
+
position: "absolute",
|
|
1280
|
+
inset: "0",
|
|
1281
|
+
zIndex: "38",
|
|
1282
|
+
pointerEvents: "none"
|
|
1283
|
+
});
|
|
1284
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1285
|
+
svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
|
|
1286
|
+
Object.assign(svg.style, {
|
|
1287
|
+
position: "absolute",
|
|
1288
|
+
inset: "0",
|
|
1289
|
+
width: "100%",
|
|
1290
|
+
height: "100%",
|
|
1291
|
+
overflow: "visible"
|
|
1292
|
+
});
|
|
1293
|
+
layer.appendChild(svg);
|
|
1294
|
+
const centerX = bounds.width / 2;
|
|
1295
|
+
const centerY = (bounds.height + 40) / 2;
|
|
1296
|
+
const nodeCenters = nodes.map((n) => ({
|
|
1297
|
+
x: n.x + n.width / 2,
|
|
1298
|
+
y: n.y + n.height / 2
|
|
1299
|
+
}));
|
|
1300
|
+
const strokeBorder = theme?.border ?? "#cbd5e1";
|
|
1301
|
+
const strokeHairline = theme?.hairline ?? theme?.border ?? "#e2e8f0";
|
|
1302
|
+
const accentColor = theme?.accent ?? "#38bdf8";
|
|
1303
|
+
for (const nc of nodeCenters) {
|
|
1304
|
+
const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1305
|
+
line.setAttribute("x1", String(centerX));
|
|
1306
|
+
line.setAttribute("y1", String(centerY));
|
|
1307
|
+
line.setAttribute("x2", String(nc.x));
|
|
1308
|
+
line.setAttribute("y2", String(nc.y));
|
|
1309
|
+
line.setAttribute("stroke", strokeBorder);
|
|
1310
|
+
line.setAttribute("stroke-width", "1");
|
|
1311
|
+
line.setAttribute("stroke-dasharray", "4 4");
|
|
1312
|
+
line.setAttribute("opacity", "0.45");
|
|
1313
|
+
svg.appendChild(line);
|
|
1314
|
+
}
|
|
1315
|
+
const fractions = [0.33, 0.66, 1];
|
|
1316
|
+
for (const f of fractions) {
|
|
1317
|
+
const points = nodeCenters.map((nc) => {
|
|
1318
|
+
const px = centerX + (nc.x - centerX) * f;
|
|
1319
|
+
const py = centerY + (nc.y - centerY) * f;
|
|
1320
|
+
return `${px.toFixed(1)},${py.toFixed(1)}`;
|
|
1321
|
+
}).join(" ");
|
|
1322
|
+
const poly = doc.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
|
1323
|
+
poly.setAttribute("points", points);
|
|
1324
|
+
poly.setAttribute("fill", "none");
|
|
1325
|
+
poly.setAttribute("stroke", strokeHairline);
|
|
1326
|
+
poly.setAttribute("stroke-width", "1");
|
|
1327
|
+
poly.setAttribute("stroke-dasharray", f === 1 ? "none" : "3 3");
|
|
1328
|
+
poly.setAttribute("opacity", String(0.3 + f * 0.25));
|
|
1329
|
+
svg.appendChild(poly);
|
|
1330
|
+
}
|
|
1331
|
+
const areaPoints = nodeCenters.map((nc, idx) => {
|
|
1332
|
+
const f = 0.75 + idx % 3 * 0.12;
|
|
1333
|
+
const px = centerX + (nc.x - centerX) * f;
|
|
1334
|
+
const py = centerY + (nc.y - centerY) * f;
|
|
1335
|
+
return `${px.toFixed(1)},${py.toFixed(1)}`;
|
|
1336
|
+
}).join(" ");
|
|
1337
|
+
const area = doc.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
|
1338
|
+
area.setAttribute("points", areaPoints);
|
|
1339
|
+
area.setAttribute("fill", accentColor);
|
|
1340
|
+
area.setAttribute("fill-opacity", "0.08");
|
|
1341
|
+
area.setAttribute("stroke", accentColor);
|
|
1342
|
+
area.setAttribute("stroke-width", "1.5");
|
|
1343
|
+
area.setAttribute("stroke-dasharray", "4 4");
|
|
1344
|
+
area.setAttribute("opacity", "0.6");
|
|
1345
|
+
svg.appendChild(area);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// src/timeline.ts
|
|
1349
|
+
function mountTimelineLayer(layer, nodes, theme, bounds) {
|
|
1350
|
+
if (nodes.length === 0) return;
|
|
1351
|
+
const doc = layer.ownerDocument;
|
|
1352
|
+
Object.assign(layer.style, {
|
|
1353
|
+
position: "absolute",
|
|
1354
|
+
inset: "0",
|
|
1355
|
+
zIndex: "38",
|
|
1356
|
+
pointerEvents: "none"
|
|
1357
|
+
});
|
|
1358
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1359
|
+
svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
|
|
1360
|
+
Object.assign(svg.style, {
|
|
1361
|
+
position: "absolute",
|
|
1362
|
+
inset: "0",
|
|
1363
|
+
width: "100%",
|
|
1364
|
+
height: "100%",
|
|
1365
|
+
overflow: "visible"
|
|
1366
|
+
});
|
|
1367
|
+
layer.appendChild(svg);
|
|
1368
|
+
const baselineY = (bounds.height + 40) / 2;
|
|
1369
|
+
const minX = Math.min(...nodes.map((n) => n.x)) - 20;
|
|
1370
|
+
const maxX = Math.max(...nodes.map((n) => n.x + n.width)) + 20;
|
|
1371
|
+
const strokeAxis = theme?.hairline ?? theme?.border ?? "#cbd5e1";
|
|
1372
|
+
const strokeBorder = theme?.border ?? "#cbd5e1";
|
|
1373
|
+
const accentColor = theme?.accent ?? "#38bdf8";
|
|
1374
|
+
const surfaceFill = theme?.surfaceRaised ?? theme?.surface ?? "#ffffff";
|
|
1375
|
+
const axis = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1376
|
+
axis.setAttribute("x1", String(Math.max(40, minX)));
|
|
1377
|
+
axis.setAttribute("y1", String(baselineY));
|
|
1378
|
+
axis.setAttribute("x2", String(Math.min(bounds.width - 40, maxX)));
|
|
1379
|
+
axis.setAttribute("y2", String(baselineY));
|
|
1380
|
+
axis.setAttribute("stroke", strokeAxis);
|
|
1381
|
+
axis.setAttribute("stroke-width", "2");
|
|
1382
|
+
axis.setAttribute("opacity", "0.6");
|
|
1383
|
+
svg.appendChild(axis);
|
|
1384
|
+
for (const node of nodes) {
|
|
1385
|
+
const nodeCenterX = node.x + node.width / 2;
|
|
1386
|
+
const isAbove = node.y + node.height <= baselineY + 10;
|
|
1387
|
+
const targetY = isAbove ? node.y + node.height : node.y;
|
|
1388
|
+
const stem = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1389
|
+
stem.setAttribute("x1", String(nodeCenterX));
|
|
1390
|
+
stem.setAttribute("y1", String(baselineY));
|
|
1391
|
+
stem.setAttribute("x2", String(nodeCenterX));
|
|
1392
|
+
stem.setAttribute("y2", String(targetY));
|
|
1393
|
+
stem.setAttribute("stroke", node.focal ? accentColor : strokeBorder);
|
|
1394
|
+
stem.setAttribute("stroke-width", node.focal ? "1.5" : "1");
|
|
1395
|
+
stem.setAttribute("stroke-dasharray", node.focal ? "none" : "3 3");
|
|
1396
|
+
stem.setAttribute("opacity", node.focal ? "0.9" : "0.5");
|
|
1397
|
+
svg.appendChild(stem);
|
|
1398
|
+
const pip = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
1399
|
+
pip.setAttribute("cx", String(nodeCenterX));
|
|
1400
|
+
pip.setAttribute("cy", String(baselineY));
|
|
1401
|
+
pip.setAttribute("r", node.focal ? "5" : "3.5");
|
|
1402
|
+
pip.setAttribute("fill", node.focal ? accentColor : surfaceFill);
|
|
1403
|
+
pip.setAttribute("stroke", node.focal ? accentColor : strokeBorder);
|
|
1404
|
+
pip.setAttribute("stroke-width", "1.5");
|
|
1405
|
+
svg.appendChild(pip);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
959
1409
|
// src/groups.ts
|
|
960
1410
|
var STYLE_ID2 = "markdy-group-boundary-styles";
|
|
961
1411
|
function ensureGroupStyles(doc) {
|
|
@@ -966,26 +1416,34 @@ function ensureGroupStyles(doc) {
|
|
|
966
1416
|
.markdy-group-boundary {
|
|
967
1417
|
position: absolute;
|
|
968
1418
|
box-sizing: border-box;
|
|
969
|
-
border: 1px
|
|
970
|
-
border-radius:
|
|
971
|
-
background: color-mix(in srgb, var(--md-surface-raised)
|
|
1419
|
+
border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border) 45%, transparent));
|
|
1420
|
+
border-radius: 16px;
|
|
1421
|
+
background: color-mix(in srgb, var(--md-surface-raised) 32%, transparent);
|
|
1422
|
+
box-shadow:
|
|
1423
|
+
0 4px 20px -4px var(--md-shadow, rgba(0, 0, 0, 0.25)),
|
|
1424
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
|
1425
|
+
backdrop-filter: blur(8px);
|
|
1426
|
+
-webkit-backdrop-filter: blur(8px);
|
|
972
1427
|
pointer-events: none;
|
|
973
1428
|
z-index: 40;
|
|
974
1429
|
}
|
|
975
1430
|
.markdy-group-boundary__label {
|
|
976
1431
|
position: absolute;
|
|
977
|
-
left:
|
|
978
|
-
top:
|
|
979
|
-
padding:
|
|
980
|
-
font-size:
|
|
1432
|
+
left: 14px;
|
|
1433
|
+
top: 10px;
|
|
1434
|
+
padding: 4px 10px;
|
|
1435
|
+
font-size: 10.5px;
|
|
981
1436
|
font-weight: 600;
|
|
982
1437
|
letter-spacing: 0.08em;
|
|
983
1438
|
text-transform: uppercase;
|
|
984
|
-
color: var(--md-text
|
|
985
|
-
background: var(--md-
|
|
986
|
-
border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border)
|
|
987
|
-
border-radius:
|
|
988
|
-
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
1439
|
+
color: var(--md-text);
|
|
1440
|
+
background: color-mix(in srgb, var(--md-surface-raised) 85%, transparent);
|
|
1441
|
+
border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border) 50%, transparent));
|
|
1442
|
+
border-radius: 6px;
|
|
1443
|
+
font-family: var(--md-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1444
|
+
backdrop-filter: blur(6px);
|
|
1445
|
+
-webkit-backdrop-filter: blur(6px);
|
|
1446
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
|
|
989
1447
|
}
|
|
990
1448
|
`;
|
|
991
1449
|
doc.head.appendChild(style);
|
|
@@ -999,10 +1457,11 @@ function createGroupBoundaryEl(boundary, theme, doc = document) {
|
|
|
999
1457
|
el.style.width = `${boundary.width}px`;
|
|
1000
1458
|
el.style.height = `${boundary.height}px`;
|
|
1001
1459
|
el.style.setProperty("--md-group-border", theme.hairline ?? theme.border);
|
|
1002
|
-
|
|
1003
|
-
|
|
1460
|
+
const displayLabel = boundary.label || boundary.id;
|
|
1461
|
+
if (displayLabel) {
|
|
1462
|
+
const label = doc.createElement("div");
|
|
1004
1463
|
label.className = "markdy-group-boundary__label";
|
|
1005
|
-
label.textContent =
|
|
1464
|
+
label.textContent = displayLabel;
|
|
1006
1465
|
el.appendChild(label);
|
|
1007
1466
|
}
|
|
1008
1467
|
return el;
|
|
@@ -1024,20 +1483,22 @@ function ensureNodeStyles(doc) {
|
|
|
1024
1483
|
.markdy-node {
|
|
1025
1484
|
position: absolute;
|
|
1026
1485
|
box-sizing: border-box;
|
|
1027
|
-
width: var(--md-node-w,
|
|
1028
|
-
height: var(--md-node-h,
|
|
1486
|
+
width: var(--md-node-w, 180px);
|
|
1487
|
+
height: var(--md-node-h, 76px);
|
|
1488
|
+
min-width: 140px;
|
|
1489
|
+
min-height: 64px;
|
|
1029
1490
|
border-radius: 12px;
|
|
1030
1491
|
background:
|
|
1031
1492
|
linear-gradient(180deg,
|
|
1032
|
-
var(--md-node-surface-raised, color-mix(in srgb, var(--md-surface-raised)
|
|
1493
|
+
var(--md-node-surface-raised, color-mix(in srgb, var(--md-surface-raised) 92%, #ffffff 8%)),
|
|
1033
1494
|
var(--md-node-surface, var(--md-surface)));
|
|
1034
1495
|
color: var(--md-text);
|
|
1035
1496
|
box-shadow:
|
|
1036
|
-
0 1px
|
|
1037
|
-
0 10px
|
|
1497
|
+
0 1px 3px color-mix(in srgb, var(--md-shadow, rgba(2, 6, 23, 0.35)) 35%, transparent),
|
|
1498
|
+
0 10px 24px -10px var(--md-shadow, rgba(2, 6, 23, 0.45)),
|
|
1038
1499
|
inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent)),
|
|
1039
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
1040
|
-
font-family: var(--md-font-node,
|
|
1500
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
|
1501
|
+
font-family: var(--md-font-node, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif);
|
|
1041
1502
|
overflow: hidden;
|
|
1042
1503
|
opacity: 0;
|
|
1043
1504
|
transform: translateY(8px);
|
|
@@ -1049,33 +1510,34 @@ function ensureNodeStyles(doc) {
|
|
|
1049
1510
|
}
|
|
1050
1511
|
.markdy-node[data-focused="1"] {
|
|
1051
1512
|
box-shadow:
|
|
1052
|
-
0 2px
|
|
1053
|
-
0
|
|
1054
|
-
inset 0 0 0
|
|
1055
|
-
0 0 0 3px color-mix(in srgb, var(--md-accent)
|
|
1513
|
+
0 2px 6px rgba(2, 6, 23, 0.35),
|
|
1514
|
+
0 18px 38px -10px rgba(2, 6, 23, 0.7),
|
|
1515
|
+
inset 0 0 0 1.5px color-mix(in srgb, var(--md-accent) 80%, transparent),
|
|
1516
|
+
0 0 0 3px color-mix(in srgb, var(--md-accent) 24%, transparent);
|
|
1056
1517
|
}
|
|
1057
1518
|
.markdy-node[data-glow="1"] {
|
|
1058
1519
|
box-shadow:
|
|
1059
|
-
0 2px
|
|
1060
|
-
0 0 0
|
|
1061
|
-
0 0
|
|
1062
|
-
inset 0 0 18px -8px color-mix(in srgb, var(--md-glow-color, var(--md-accent))
|
|
1520
|
+
0 2px 6px rgba(2, 6, 23, 0.35),
|
|
1521
|
+
0 0 0 1.5px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 70%, transparent),
|
|
1522
|
+
0 0 28px -2px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 55%, transparent),
|
|
1523
|
+
inset 0 0 18px -8px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 50%, transparent);
|
|
1063
1524
|
}
|
|
1064
1525
|
.markdy-node__rail { display: none; }
|
|
1065
1526
|
.markdy-node__type { display: none; }
|
|
1066
1527
|
.markdy-node__body {
|
|
1067
1528
|
height: 100%;
|
|
1068
|
-
padding: 0
|
|
1529
|
+
padding: 0 14px;
|
|
1069
1530
|
display: flex;
|
|
1070
1531
|
align-items: center;
|
|
1071
|
-
gap:
|
|
1532
|
+
gap: 12px;
|
|
1072
1533
|
min-width: 0;
|
|
1534
|
+
box-sizing: border-box;
|
|
1073
1535
|
}
|
|
1074
1536
|
.markdy-node__icon {
|
|
1075
1537
|
flex: 0 0 auto;
|
|
1076
|
-
width:
|
|
1077
|
-
height:
|
|
1078
|
-
border-radius:
|
|
1538
|
+
width: 34px;
|
|
1539
|
+
height: 34px;
|
|
1540
|
+
border-radius: 9px;
|
|
1079
1541
|
display: flex;
|
|
1080
1542
|
align-items: center;
|
|
1081
1543
|
justify-content: center;
|
|
@@ -1083,14 +1545,14 @@ function ensureNodeStyles(doc) {
|
|
|
1083
1545
|
background:
|
|
1084
1546
|
linear-gradient(180deg,
|
|
1085
1547
|
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 24%, transparent),
|
|
1086
|
-
color-mix(in srgb, var(--md-role-color, var(--md-accent))
|
|
1548
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 10%, transparent));
|
|
1087
1549
|
box-shadow:
|
|
1088
|
-
inset 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent))
|
|
1089
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
1550
|
+
inset 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 38%, transparent),
|
|
1551
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.18);
|
|
1090
1552
|
}
|
|
1091
1553
|
.markdy-node__icon svg {
|
|
1092
|
-
width:
|
|
1093
|
-
height:
|
|
1554
|
+
width: 18px;
|
|
1555
|
+
height: 18px;
|
|
1094
1556
|
display: block;
|
|
1095
1557
|
stroke: currentColor;
|
|
1096
1558
|
}
|
|
@@ -1118,10 +1580,11 @@ function ensureNodeStyles(doc) {
|
|
|
1118
1580
|
flex: 1 1 auto;
|
|
1119
1581
|
min-width: 0;
|
|
1120
1582
|
padding: 0;
|
|
1121
|
-
font-size:
|
|
1583
|
+
font-size: 13.5px;
|
|
1122
1584
|
font-weight: 600;
|
|
1123
1585
|
letter-spacing: -0.01em;
|
|
1124
|
-
line-height: 1.
|
|
1586
|
+
line-height: 1.24;
|
|
1587
|
+
color: var(--md-text);
|
|
1125
1588
|
display: -webkit-box;
|
|
1126
1589
|
-webkit-box-orient: vertical;
|
|
1127
1590
|
-webkit-line-clamp: 3;
|
|
@@ -1129,30 +1592,30 @@ function ensureNodeStyles(doc) {
|
|
|
1129
1592
|
overflow: hidden;
|
|
1130
1593
|
overflow-wrap: anywhere;
|
|
1131
1594
|
word-break: break-word;
|
|
1132
|
-
text-wrap:
|
|
1595
|
+
text-wrap: pretty;
|
|
1133
1596
|
}
|
|
1134
1597
|
.markdy-node__value {
|
|
1135
1598
|
flex: 0 0 auto;
|
|
1136
|
-
font-size:
|
|
1599
|
+
font-size: 16px;
|
|
1137
1600
|
font-weight: 700;
|
|
1138
1601
|
color: var(--md-ink, var(--md-text));
|
|
1139
1602
|
font-variant-numeric: tabular-nums;
|
|
1140
1603
|
}
|
|
1141
|
-
.markdy-node[data-role="client"] { border-radius:
|
|
1142
|
-
.markdy-node[data-role="data"] { border-radius: 12px 12px
|
|
1604
|
+
.markdy-node[data-role="client"] { border-radius: 14px 14px 10px 10px; }
|
|
1605
|
+
.markdy-node[data-role="data"] { border-radius: 12px 12px 16px 16px; }
|
|
1143
1606
|
.markdy-scene-title {
|
|
1144
1607
|
position: absolute;
|
|
1145
|
-
left:
|
|
1146
|
-
top:
|
|
1147
|
-
right:
|
|
1608
|
+
left: 48px;
|
|
1609
|
+
top: 28px;
|
|
1610
|
+
right: 48px;
|
|
1148
1611
|
z-index: 130;
|
|
1149
|
-
font-size:
|
|
1612
|
+
font-size: 30px;
|
|
1150
1613
|
font-weight: 700;
|
|
1151
1614
|
line-height: 1.2;
|
|
1152
1615
|
color: var(--md-text);
|
|
1153
1616
|
opacity: 0;
|
|
1154
1617
|
transform: translateY(-6px);
|
|
1155
|
-
font-family: var(--md-font-title,
|
|
1618
|
+
font-family: var(--md-font-title, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif);
|
|
1156
1619
|
}
|
|
1157
1620
|
.markdy-scene-title[data-visible="1"] {
|
|
1158
1621
|
opacity: 1;
|
|
@@ -1165,12 +1628,35 @@ function ensureNodeStyles(doc) {
|
|
|
1165
1628
|
}
|
|
1166
1629
|
.markdy-node[data-shape="pill"] {
|
|
1167
1630
|
border-radius: 999px;
|
|
1168
|
-
min-height:
|
|
1631
|
+
min-height: 54px;
|
|
1169
1632
|
}
|
|
1170
1633
|
.markdy-node[data-shape="circle"],
|
|
1171
1634
|
.markdy-node[data-kind="dot"] {
|
|
1172
1635
|
border-radius: 50%;
|
|
1173
1636
|
}
|
|
1637
|
+
.markdy-node[data-shape="circle"] {
|
|
1638
|
+
display: flex;
|
|
1639
|
+
align-items: center;
|
|
1640
|
+
justify-content: center;
|
|
1641
|
+
text-align: center;
|
|
1642
|
+
background:
|
|
1643
|
+
radial-gradient(circle at 35% 35%,
|
|
1644
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 16%, var(--md-surface) 84%),
|
|
1645
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 6%, var(--md-surface) 94%));
|
|
1646
|
+
border: 1.5px solid color-mix(in srgb, var(--md-role-color, var(--md-accent)) 50%, transparent);
|
|
1647
|
+
}
|
|
1648
|
+
.markdy-node[data-shape="circle"] .markdy-node__body {
|
|
1649
|
+
flex-direction: column;
|
|
1650
|
+
justify-content: center;
|
|
1651
|
+
padding: 16px;
|
|
1652
|
+
gap: 8px;
|
|
1653
|
+
text-align: center;
|
|
1654
|
+
}
|
|
1655
|
+
.markdy-node[data-shape="circle"] .markdy-node__label {
|
|
1656
|
+
text-align: center;
|
|
1657
|
+
-webkit-line-clamp: 4;
|
|
1658
|
+
line-clamp: 4;
|
|
1659
|
+
}
|
|
1174
1660
|
.markdy-node[data-kind="dot"] {
|
|
1175
1661
|
width: 64px;
|
|
1176
1662
|
height: 64px;
|
|
@@ -1188,11 +1674,26 @@ function ensureNodeStyles(doc) {
|
|
|
1188
1674
|
.markdy-node[data-kind="token_strip"] {
|
|
1189
1675
|
border-radius: 999px;
|
|
1190
1676
|
}
|
|
1677
|
+
.markdy-node[data-is-container="1"] {
|
|
1678
|
+
background: color-mix(in srgb, var(--md-surface-raised) 25%, transparent);
|
|
1679
|
+
border: 1px solid color-mix(in srgb, var(--md-role-color, var(--md-accent)) 40%, var(--md-border) 60%);
|
|
1680
|
+
box-shadow: inset 0 0 0 1px var(--md-hairline), 0 4px 16px -4px var(--md-shadow, rgba(0,0,0,0.25));
|
|
1681
|
+
backdrop-filter: blur(8px);
|
|
1682
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1683
|
+
}
|
|
1684
|
+
.markdy-node[data-is-container="1"] .markdy-node__body {
|
|
1685
|
+
align-items: flex-start;
|
|
1686
|
+
padding: 12px 16px;
|
|
1687
|
+
}
|
|
1688
|
+
.markdy-node[data-is-container="1"][data-focal="1"] {
|
|
1689
|
+
background: color-mix(in srgb, var(--md-accent-tint, var(--md-accent)) 16%, transparent);
|
|
1690
|
+
border: 1.5px solid color-mix(in srgb, var(--md-accent) 70%, transparent);
|
|
1691
|
+
}
|
|
1191
1692
|
.markdy-node[data-shape="rounded"] {
|
|
1192
1693
|
border-radius: 16px;
|
|
1193
1694
|
}
|
|
1194
1695
|
.markdy-node[data-shape="terminal"] {
|
|
1195
|
-
border-radius:
|
|
1696
|
+
border-radius: 8px;
|
|
1196
1697
|
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
1197
1698
|
box-shadow: none;
|
|
1198
1699
|
background: var(--md-node-surface, var(--md-surface));
|
|
@@ -1584,6 +2085,7 @@ function createNodeEl(node, theme, assets) {
|
|
|
1584
2085
|
el.dataset.icon = iconKeyForNode(node);
|
|
1585
2086
|
if (node.shape) el.dataset.shape = node.shape;
|
|
1586
2087
|
if (node.focal) el.dataset.focal = "1";
|
|
2088
|
+
if (node.shape === "container") el.dataset.isContainer = "1";
|
|
1587
2089
|
el.title = `${node.label} (${typeText})`;
|
|
1588
2090
|
el.setAttribute("aria-label", el.title);
|
|
1589
2091
|
const body = document.createElement("div");
|
|
@@ -1607,6 +2109,9 @@ function createTitleEl(title) {
|
|
|
1607
2109
|
const el = document.createElement("div");
|
|
1608
2110
|
el.className = "markdy-scene-title";
|
|
1609
2111
|
el.textContent = title;
|
|
2112
|
+
if (!title) {
|
|
2113
|
+
el.style.display = "none";
|
|
2114
|
+
}
|
|
1610
2115
|
return el;
|
|
1611
2116
|
}
|
|
1612
2117
|
|
|
@@ -1690,25 +2195,26 @@ function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds)
|
|
|
1690
2195
|
lifeline.classList.add("markdy-sequence-lifeline");
|
|
1691
2196
|
lifeline.setAttribute("x1", String(x));
|
|
1692
2197
|
lifeline.setAttribute("x2", String(x));
|
|
1693
|
-
lifeline.setAttribute("y1", String(node.y + node.height +
|
|
1694
|
-
lifeline.setAttribute("y2", String(bounds.height -
|
|
1695
|
-
lifeline.setAttribute("stroke", theme.
|
|
1696
|
-
lifeline.setAttribute("stroke-width", "1");
|
|
1697
|
-
lifeline.setAttribute("stroke-dasharray", "4
|
|
1698
|
-
lifeline.setAttribute("opacity", "0.
|
|
2198
|
+
lifeline.setAttribute("y1", String(node.y + node.height + 8));
|
|
2199
|
+
lifeline.setAttribute("y2", String(bounds.height - 32));
|
|
2200
|
+
lifeline.setAttribute("stroke", theme.hairline ?? theme.border);
|
|
2201
|
+
lifeline.setAttribute("stroke-width", "1.2");
|
|
2202
|
+
lifeline.setAttribute("stroke-dasharray", "4 6");
|
|
2203
|
+
lifeline.setAttribute("opacity", "0.85");
|
|
1699
2204
|
svg.appendChild(lifeline);
|
|
1700
2205
|
}
|
|
1701
2206
|
for (const activation of activations) {
|
|
1702
2207
|
const x = centerX(activation.participant);
|
|
1703
2208
|
const bar = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
1704
2209
|
bar.classList.add("markdy-sequence-activation");
|
|
1705
|
-
bar.setAttribute("x", String(x -
|
|
2210
|
+
bar.setAttribute("x", String(x - 6));
|
|
1706
2211
|
bar.setAttribute("y", String(activation.y));
|
|
1707
|
-
bar.setAttribute("width", "
|
|
2212
|
+
bar.setAttribute("width", "12");
|
|
1708
2213
|
bar.setAttribute("height", String(activation.height));
|
|
1709
|
-
bar.setAttribute("rx", "
|
|
2214
|
+
bar.setAttribute("rx", "4");
|
|
1710
2215
|
bar.setAttribute("fill", theme.accent);
|
|
1711
2216
|
bar.setAttribute("opacity", "0");
|
|
2217
|
+
bar.style.filter = `drop-shadow(0 0 6px ${theme.accent}66)`;
|
|
1712
2218
|
svg.appendChild(bar);
|
|
1713
2219
|
}
|
|
1714
2220
|
const animations = [];
|
|
@@ -1737,17 +2243,19 @@ function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds)
|
|
|
1737
2243
|
if (message.label) {
|
|
1738
2244
|
const midX = (fromX + toX) / 2;
|
|
1739
2245
|
const plate = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
1740
|
-
const width = message.label.length * 6.
|
|
2246
|
+
const width = message.label.length * 6.8 + 16;
|
|
1741
2247
|
plate.setAttribute("x", String(midX - width / 2));
|
|
1742
|
-
plate.setAttribute("y", String(message.y -
|
|
2248
|
+
plate.setAttribute("y", String(message.y - 25));
|
|
1743
2249
|
plate.setAttribute("width", String(width));
|
|
1744
|
-
plate.setAttribute("height", "
|
|
1745
|
-
plate.setAttribute("rx", "
|
|
2250
|
+
plate.setAttribute("height", "20");
|
|
2251
|
+
plate.setAttribute("rx", "6");
|
|
1746
2252
|
plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
|
|
1747
|
-
plate.setAttribute("
|
|
2253
|
+
plate.setAttribute("fill-opacity", "0.96");
|
|
2254
|
+
plate.setAttribute("stroke", theme.hairline ?? `color-mix(in srgb, ${theme.border} 70%, transparent)`);
|
|
1748
2255
|
plate.setAttribute("stroke-width", "1");
|
|
2256
|
+
plate.style.filter = "drop-shadow(0 1px 3px rgba(0,0,0,0.12))";
|
|
1749
2257
|
group.appendChild(plate);
|
|
1750
|
-
group.appendChild(createText(doc, midX, message.y -
|
|
2258
|
+
group.appendChild(createText(doc, midX, message.y - 14.5, message.label, theme));
|
|
1751
2259
|
}
|
|
1752
2260
|
svg.appendChild(group);
|
|
1753
2261
|
animations.push(
|
|
@@ -1805,25 +2313,29 @@ function mountTreeBuses(layer, buses, theme) {
|
|
|
1805
2313
|
for (const bus of buses) {
|
|
1806
2314
|
const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1807
2315
|
group.setAttribute("data-tree-bus", bus.id);
|
|
1808
|
-
const parentLeg = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1809
|
-
parentLeg.setAttribute("d", `M ${bus.parentX} ${bus.parentY} L ${bus.parentX} ${bus.branchY}`);
|
|
1810
|
-
const childXs = [...bus.childXs].sort((a, b) => a - b);
|
|
1811
|
-
const branch = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1812
|
-
const branchStart = childXs[0] ?? bus.parentX;
|
|
1813
|
-
const branchEnd = childXs[childXs.length - 1] ?? bus.parentX;
|
|
1814
|
-
branch.setAttribute("d", `M ${branchStart} ${bus.branchY} L ${branchEnd} ${bus.branchY}`);
|
|
1815
|
-
group.append(parentLeg, branch);
|
|
1816
2316
|
for (const childX of bus.childXs) {
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2317
|
+
const pathEl = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2318
|
+
let d;
|
|
2319
|
+
if (Math.abs(childX - bus.parentX) < 1) {
|
|
2320
|
+
d = toPathD([
|
|
2321
|
+
{ x: bus.parentX, y: bus.parentY },
|
|
2322
|
+
{ x: childX, y: bus.childY }
|
|
2323
|
+
], 12);
|
|
2324
|
+
} else {
|
|
2325
|
+
d = toPathD([
|
|
2326
|
+
{ x: bus.parentX, y: bus.parentY },
|
|
2327
|
+
{ x: bus.parentX, y: bus.branchY },
|
|
2328
|
+
{ x: childX, y: bus.branchY },
|
|
2329
|
+
{ x: childX, y: bus.childY }
|
|
2330
|
+
], 12);
|
|
2331
|
+
}
|
|
2332
|
+
pathEl.setAttribute("d", d);
|
|
2333
|
+
pathEl.setAttribute("fill", "none");
|
|
2334
|
+
pathEl.setAttribute("stroke", stroke);
|
|
2335
|
+
pathEl.setAttribute("stroke-width", "1.6");
|
|
2336
|
+
pathEl.setAttribute("stroke-linecap", "round");
|
|
2337
|
+
pathEl.setAttribute("stroke-linejoin", "round");
|
|
2338
|
+
group.appendChild(pathEl);
|
|
1827
2339
|
}
|
|
1828
2340
|
svg.appendChild(group);
|
|
1829
2341
|
}
|
|
@@ -1903,6 +2415,28 @@ function ensureSceneStyles(doc) {
|
|
|
1903
2415
|
from { opacity: 0.24; transform: scale(0.85); }
|
|
1904
2416
|
to { opacity: 0.9; transform: scale(1.15); }
|
|
1905
2417
|
}
|
|
2418
|
+
@keyframes markdy-flow-dash {
|
|
2419
|
+
to { stroke-dashoffset: -24; }
|
|
2420
|
+
}
|
|
2421
|
+
.markdy-edge {
|
|
2422
|
+
transition: opacity 0.2s ease;
|
|
2423
|
+
}
|
|
2424
|
+
.markdy-edge-path--flowing {
|
|
2425
|
+
animation: markdy-flow-dash 1.2s linear infinite;
|
|
2426
|
+
}
|
|
2427
|
+
.markdy-edge-path {
|
|
2428
|
+
transition: stroke 0.2s ease, stroke-width 0.2s ease, filter 0.2s ease;
|
|
2429
|
+
}
|
|
2430
|
+
.markdy-edge-plate {
|
|
2431
|
+
transition: opacity 0.2s ease, fill-opacity 0.2s ease, stroke 0.2s ease;
|
|
2432
|
+
pointer-events: none;
|
|
2433
|
+
}
|
|
2434
|
+
.markdy-edge-label {
|
|
2435
|
+
user-select: none;
|
|
2436
|
+
-webkit-user-select: none;
|
|
2437
|
+
pointer-events: none;
|
|
2438
|
+
transition: fill 0.2s ease, opacity 0.2s ease, filter 0.2s ease;
|
|
2439
|
+
}
|
|
1906
2440
|
.markdy-constellation-star {
|
|
1907
2441
|
transform-box: fill-box;
|
|
1908
2442
|
transform-origin: center;
|
|
@@ -1979,13 +2513,17 @@ function ensureSceneStyles(doc) {
|
|
|
1979
2513
|
@media (prefers-reduced-motion: reduce) {
|
|
1980
2514
|
.markdy-node,
|
|
1981
2515
|
.markdy-beat-caption,
|
|
1982
|
-
.markdy-constellation-star
|
|
2516
|
+
.markdy-constellation-star,
|
|
2517
|
+
.markdy-edge-path,
|
|
2518
|
+
.markdy-edge-path--flowing {
|
|
1983
2519
|
animation-duration: 0.001ms !important;
|
|
1984
2520
|
animation-iteration-count: 1 !important;
|
|
1985
2521
|
transition-duration: 0.001ms !important;
|
|
2522
|
+
animation: none !important;
|
|
1986
2523
|
}
|
|
1987
2524
|
.markdy-node { opacity: 1 !important; transform: none !important; }
|
|
1988
2525
|
.markdy-constellation-star { animation: none !important; opacity: 0.7 !important; }
|
|
2526
|
+
.markdy-edge-path--flowing { animation: none !important; }
|
|
1989
2527
|
}
|
|
1990
2528
|
@media print {
|
|
1991
2529
|
.markdy-node { opacity: 1 !important; transform: none !important; }
|
|
@@ -2250,6 +2788,20 @@ function createDiagram(opts) {
|
|
|
2250
2788
|
plan.theme,
|
|
2251
2789
|
{ width: plan.meta.width, height: plan.meta.height }
|
|
2252
2790
|
);
|
|
2791
|
+
} else if (plan.diagramType === "radar") {
|
|
2792
|
+
mountRadarLayer(
|
|
2793
|
+
constellationLayer,
|
|
2794
|
+
plan.nodes,
|
|
2795
|
+
plan.theme,
|
|
2796
|
+
{ width: plan.meta.width, height: plan.meta.height }
|
|
2797
|
+
);
|
|
2798
|
+
} else if (plan.diagramType === "timeline") {
|
|
2799
|
+
mountTimelineLayer(
|
|
2800
|
+
constellationLayer,
|
|
2801
|
+
plan.nodes,
|
|
2802
|
+
plan.theme,
|
|
2803
|
+
{ width: plan.meta.width, height: plan.meta.height }
|
|
2804
|
+
);
|
|
2253
2805
|
}
|
|
2254
2806
|
const groupLayer = document.createElement("div");
|
|
2255
2807
|
groupLayer.className = "markdy-group-layer";
|
|
@@ -2336,16 +2888,8 @@ function createDiagram(opts) {
|
|
|
2336
2888
|
const vHeight = viewport.clientHeight || container.clientHeight || vWidth * plan.meta.height / plan.meta.width;
|
|
2337
2889
|
const canvasScaleX = vWidth / plan.meta.width;
|
|
2338
2890
|
const canvasScaleY = vHeight / plan.meta.height;
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const contentScaleX = vWidth * 0.94 / bounds.width;
|
|
2342
|
-
const contentScaleY = vHeight * 0.94 / bounds.height;
|
|
2343
|
-
const optimalContentScale = Math.min(contentScaleX, contentScaleY);
|
|
2344
|
-
const chosenScale = Math.max(
|
|
2345
|
-
baseCanvasScale,
|
|
2346
|
-
Math.min(optimalContentScale, baseCanvasScale * 1.45)
|
|
2347
|
-
);
|
|
2348
|
-
fitScale = Number.isFinite(chosenScale) && chosenScale > 0 ? chosenScale : 1;
|
|
2891
|
+
fitScale = Math.min(canvasScaleX, canvasScaleY);
|
|
2892
|
+
if (!Number.isFinite(fitScale) || fitScale <= 0) fitScale = 1;
|
|
2349
2893
|
const scaledWidth = plan.meta.width * fitScale;
|
|
2350
2894
|
const scaledHeight = plan.meta.height * fitScale;
|
|
2351
2895
|
sceneOffsetX = (vWidth - scaledWidth) / 2;
|
|
@@ -2866,9 +3410,6 @@ function encodeGifSequence(frames, options = {}) {
|
|
|
2866
3410
|
return new Uint8Array(buffer);
|
|
2867
3411
|
}
|
|
2868
3412
|
|
|
2869
|
-
// src/export/png-exporter.ts
|
|
2870
|
-
import html2canvas from "html2canvas";
|
|
2871
|
-
|
|
2872
3413
|
// src/export/svg-exporter.ts
|
|
2873
3414
|
function copyRenderedStyles(source, clone) {
|
|
2874
3415
|
if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
|
|
@@ -3118,6 +3659,8 @@ async function rasterizeDiagramToCanvas(containerEl, options = {}, pixelRatio =
|
|
|
3118
3659
|
document.body.appendChild(host);
|
|
3119
3660
|
try {
|
|
3120
3661
|
await document.fonts?.ready;
|
|
3662
|
+
const html2canvasModule = await import("html2canvas");
|
|
3663
|
+
const html2canvas = html2canvasModule.default ?? html2canvasModule;
|
|
3121
3664
|
return await html2canvas(clonedScene, {
|
|
3122
3665
|
allowTaint: false,
|
|
3123
3666
|
backgroundColor: options.transparentBackground ? null : void 0,
|