vizcraft 0.2.2 → 1.0.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/CHANGELOG.md +12 -0
- package/LICENSE.txt +21 -0
- package/README.md +104 -2
- package/dist/anim/animationBuilder.d.ts +2 -0
- package/dist/anim/animationBuilder.js +6 -1
- package/dist/anim/spec.d.ts +1 -1
- package/dist/anim/vizcraftAdapter.js +68 -1
- package/dist/builder.d.ts +70 -2
- package/dist/builder.js +719 -118
- package/dist/edgeLabels.d.ts +15 -0
- package/dist/edgeLabels.js +26 -0
- package/dist/edgePaths.d.ts +43 -0
- package/dist/edgePaths.js +253 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/overlayBuilder.d.ts +50 -0
- package/dist/overlayBuilder.js +80 -0
- package/dist/overlays.d.ts +113 -21
- package/dist/overlays.js +319 -1
- package/dist/runtimePatcher.d.ts +3 -3
- package/dist/runtimePatcher.js +231 -31
- package/dist/shapes.d.ts +25 -3
- package/dist/shapes.js +1009 -0
- package/dist/styles.d.ts +1 -1
- package/dist/styles.js +24 -0
- package/dist/types.d.ts +207 -1
- package/dist/types.js +2 -1
- package/package.json +1 -1
- package/dist/anim/player.test.d.ts +0 -1
- package/dist/anim/player.test.js +0 -49
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -66
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EdgeLabel, VizEdge } from './types';
|
|
2
|
+
import type { EdgePathResult } from './edgePaths';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the (x, y) position of an edge label given an EdgePathResult.
|
|
5
|
+
* Falls back to `mid` for unknown positions.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveEdgeLabelPosition(lbl: EdgeLabel, path: EdgePathResult): {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Collect all labels for an edge, preferring `labels[]` when present
|
|
13
|
+
* and falling back to the legacy `label` field.
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectEdgeLabels(edge: VizEdge): EdgeLabel[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the (x, y) position of an edge label given an EdgePathResult.
|
|
3
|
+
* Falls back to `mid` for unknown positions.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveEdgeLabelPosition(lbl, path) {
|
|
6
|
+
const base = lbl.position === 'start'
|
|
7
|
+
? path.start
|
|
8
|
+
: lbl.position === 'end'
|
|
9
|
+
? path.end
|
|
10
|
+
: path.mid;
|
|
11
|
+
return {
|
|
12
|
+
x: base.x + (lbl.dx || 0),
|
|
13
|
+
y: base.y + (lbl.dy || 0),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Collect all labels for an edge, preferring `labels[]` when present
|
|
18
|
+
* and falling back to the legacy `label` field.
|
|
19
|
+
*/
|
|
20
|
+
export function collectEdgeLabels(edge) {
|
|
21
|
+
if (edge.labels && edge.labels.length > 0)
|
|
22
|
+
return edge.labels;
|
|
23
|
+
if (edge.label)
|
|
24
|
+
return [edge.label];
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge path computation helpers.
|
|
3
|
+
*
|
|
4
|
+
* Given start/end points, routing mode, and optional waypoints this module
|
|
5
|
+
* produces:
|
|
6
|
+
* 1. An SVG `d` attribute string (for `<path>` elements).
|
|
7
|
+
* 2. A midpoint along the path (for label positioning).
|
|
8
|
+
*/
|
|
9
|
+
import type { Vec2, VizNode, VizEdge, EdgeRouting } from './types';
|
|
10
|
+
export interface EdgePathResult {
|
|
11
|
+
/** SVG path `d` attribute. */
|
|
12
|
+
d: string;
|
|
13
|
+
/** Approximate label position along the path (exact for straight/quadratic, approximated for spline and orthogonal-with-waypoints paths). */
|
|
14
|
+
mid: Vec2;
|
|
15
|
+
/** Position near the source end (~15% along the path). */
|
|
16
|
+
start: Vec2;
|
|
17
|
+
/** Position near the target end (~85% along the path). */
|
|
18
|
+
end: Vec2;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compute anchor-resolved start/end points for an edge.
|
|
22
|
+
*
|
|
23
|
+
* When an edge specifies `fromPort` / `toPort`, the endpoint is resolved
|
|
24
|
+
* to the port's absolute position. Otherwise the legacy `anchor` mode
|
|
25
|
+
* (`'center'` | `'boundary'`) is used.
|
|
26
|
+
*
|
|
27
|
+
* This replicates the logic used internally by the core builder and
|
|
28
|
+
* runtime patcher so that external renderers (e.g. React) can
|
|
29
|
+
* resolve boundary anchors consistently.
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeEdgeEndpoints(start: VizNode, end: VizNode, edge: VizEdge): {
|
|
32
|
+
start: Vec2;
|
|
33
|
+
end: Vec2;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Compute the SVG path for an edge.
|
|
37
|
+
*
|
|
38
|
+
* @param start Start point (already anchor-resolved).
|
|
39
|
+
* @param end End point (already anchor-resolved).
|
|
40
|
+
* @param routing Routing algorithm (default `'straight'`).
|
|
41
|
+
* @param waypoints Optional intermediate points.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeEdgePath(start: Vec2, end: Vec2, routing?: EdgeRouting, waypoints?: Vec2[]): EdgePathResult;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge path computation helpers.
|
|
3
|
+
*
|
|
4
|
+
* Given start/end points, routing mode, and optional waypoints this module
|
|
5
|
+
* produces:
|
|
6
|
+
* 1. An SVG `d` attribute string (for `<path>` elements).
|
|
7
|
+
* 2. A midpoint along the path (for label positioning).
|
|
8
|
+
*/
|
|
9
|
+
import { computeNodeAnchor, effectivePos, resolvePortPosition } from './shapes';
|
|
10
|
+
/**
|
|
11
|
+
* Compute anchor-resolved start/end points for an edge.
|
|
12
|
+
*
|
|
13
|
+
* When an edge specifies `fromPort` / `toPort`, the endpoint is resolved
|
|
14
|
+
* to the port's absolute position. Otherwise the legacy `anchor` mode
|
|
15
|
+
* (`'center'` | `'boundary'`) is used.
|
|
16
|
+
*
|
|
17
|
+
* This replicates the logic used internally by the core builder and
|
|
18
|
+
* runtime patcher so that external renderers (e.g. React) can
|
|
19
|
+
* resolve boundary anchors consistently.
|
|
20
|
+
*/
|
|
21
|
+
export function computeEdgeEndpoints(start, end, edge) {
|
|
22
|
+
const anchor = edge.anchor ?? 'boundary';
|
|
23
|
+
const startPos = effectivePos(start);
|
|
24
|
+
const endPos = effectivePos(end);
|
|
25
|
+
// Port-based resolution takes precedence over anchor mode.
|
|
26
|
+
const startAnchor = edge.fromPort
|
|
27
|
+
? (resolvePortPosition(start, edge.fromPort) ??
|
|
28
|
+
computeNodeAnchor(start, endPos, anchor))
|
|
29
|
+
: computeNodeAnchor(start, endPos, anchor);
|
|
30
|
+
const endAnchor = edge.toPort
|
|
31
|
+
? (resolvePortPosition(end, edge.toPort) ??
|
|
32
|
+
computeNodeAnchor(end, startPos, anchor))
|
|
33
|
+
: computeNodeAnchor(end, startPos, anchor);
|
|
34
|
+
return { start: startAnchor, end: endAnchor };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compute the SVG path for an edge.
|
|
38
|
+
*
|
|
39
|
+
* @param start Start point (already anchor-resolved).
|
|
40
|
+
* @param end End point (already anchor-resolved).
|
|
41
|
+
* @param routing Routing algorithm (default `'straight'`).
|
|
42
|
+
* @param waypoints Optional intermediate points.
|
|
43
|
+
*/
|
|
44
|
+
export function computeEdgePath(start, end, routing = 'straight', waypoints) {
|
|
45
|
+
switch (routing) {
|
|
46
|
+
case 'curved':
|
|
47
|
+
return curvedPath(start, end, waypoints);
|
|
48
|
+
case 'orthogonal':
|
|
49
|
+
return orthogonalPath(start, end, waypoints);
|
|
50
|
+
case 'straight':
|
|
51
|
+
default:
|
|
52
|
+
return straightPath(start, end, waypoints);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ── Straight routing ────────────────────────────────────────────────────────
|
|
56
|
+
function straightPath(start, end, waypoints) {
|
|
57
|
+
const pts = [start, ...(waypoints ?? []), end];
|
|
58
|
+
const segments = pts.map((p, i) => i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`);
|
|
59
|
+
const positions = polylineLabelPositions(pts);
|
|
60
|
+
return {
|
|
61
|
+
d: segments.join(' '),
|
|
62
|
+
...positions,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ── Curved routing ──────────────────────────────────────────────────────────
|
|
66
|
+
function curvedPath(start, end, waypoints) {
|
|
67
|
+
if (waypoints && waypoints.length > 0) {
|
|
68
|
+
// Use waypoints as control/through points – produce a smooth cubic path.
|
|
69
|
+
return curvedThroughPoints(start, end, waypoints);
|
|
70
|
+
}
|
|
71
|
+
// No waypoints: single quadratic bezier with auto-computed control point.
|
|
72
|
+
const cp = autoControlPoint(start, end);
|
|
73
|
+
const d = `M ${start.x} ${start.y} Q ${cp.x} ${cp.y} ${end.x} ${end.y}`;
|
|
74
|
+
const mid = quadraticAt(start, cp, end, 0.5);
|
|
75
|
+
const startPos = quadraticAt(start, cp, end, LABEL_FRACTION_START);
|
|
76
|
+
const endPos = quadraticAt(start, cp, end, LABEL_FRACTION_END);
|
|
77
|
+
return { d, mid, start: startPos, end: endPos };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Auto-compute a control point that creates a gentle arc.
|
|
81
|
+
* The control point is offset perpendicular to the line at the midpoint.
|
|
82
|
+
*/
|
|
83
|
+
function autoControlPoint(start, end) {
|
|
84
|
+
const mx = (start.x + end.x) / 2;
|
|
85
|
+
const my = (start.y + end.y) / 2;
|
|
86
|
+
const dx = end.x - start.x;
|
|
87
|
+
const dy = end.y - start.y;
|
|
88
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
89
|
+
// Perpendicular offset: 20% of the line length
|
|
90
|
+
const offset = len * 0.2;
|
|
91
|
+
return {
|
|
92
|
+
x: mx + (-dy / len) * offset,
|
|
93
|
+
y: my + (dx / len) * offset,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Point on a quadratic bezier at parameter t (0–1). */
|
|
97
|
+
function quadraticAt(p0, cp, p1, t) {
|
|
98
|
+
const mt = 1 - t;
|
|
99
|
+
return {
|
|
100
|
+
x: mt * mt * p0.x + 2 * mt * t * cp.x + t * t * p1.x,
|
|
101
|
+
y: mt * mt * p0.y + 2 * mt * t * cp.y + t * t * p1.y,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a smooth cubic bezier path that goes through all waypoints.
|
|
106
|
+
*
|
|
107
|
+
* Strategy: use Catmull-Rom → cubic conversion for smooth interpolation
|
|
108
|
+
* through all points.
|
|
109
|
+
*/
|
|
110
|
+
function curvedThroughPoints(start, end, waypoints) {
|
|
111
|
+
const allPts = [start, ...waypoints, end];
|
|
112
|
+
if (allPts.length === 2) {
|
|
113
|
+
// Degenerate — just a quadratic
|
|
114
|
+
const cp = autoControlPoint(start, end);
|
|
115
|
+
return {
|
|
116
|
+
d: `M ${start.x} ${start.y} Q ${cp.x} ${cp.y} ${end.x} ${end.y}`,
|
|
117
|
+
mid: quadraticAt(start, cp, end, 0.5),
|
|
118
|
+
start: quadraticAt(start, cp, end, LABEL_FRACTION_START),
|
|
119
|
+
end: quadraticAt(start, cp, end, LABEL_FRACTION_END),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Convert Catmull-Rom through allPts to cubic bezier segments.
|
|
123
|
+
let d = `M ${allPts[0].x} ${allPts[0].y}`;
|
|
124
|
+
const tension = 0.3; // 0 = straight, 0.5 = Catmull-Rom
|
|
125
|
+
for (let i = 0; i < allPts.length - 1; i++) {
|
|
126
|
+
const p0 = allPts[Math.max(i - 1, 0)];
|
|
127
|
+
const p1 = allPts[i];
|
|
128
|
+
const p2 = allPts[i + 1];
|
|
129
|
+
const p3 = allPts[Math.min(i + 2, allPts.length - 1)];
|
|
130
|
+
const cp1x = p1.x + (p2.x - p0.x) * tension;
|
|
131
|
+
const cp1y = p1.y + (p2.y - p0.y) * tension;
|
|
132
|
+
const cp2x = p2.x - (p3.x - p1.x) * tension;
|
|
133
|
+
const cp2y = p2.y - (p3.y - p1.y) * tension;
|
|
134
|
+
d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`;
|
|
135
|
+
}
|
|
136
|
+
// Approximate label positions using the through-points polyline
|
|
137
|
+
const positions = polylineLabelPositions(allPts);
|
|
138
|
+
return { d, ...positions };
|
|
139
|
+
}
|
|
140
|
+
// ── Orthogonal routing ──────────────────────────────────────────────────────
|
|
141
|
+
function orthogonalPath(start, end, waypoints) {
|
|
142
|
+
if (waypoints && waypoints.length > 0) {
|
|
143
|
+
// With waypoints, route through each using orthogonal segments.
|
|
144
|
+
return orthogonalThroughWaypoints(start, end, waypoints);
|
|
145
|
+
}
|
|
146
|
+
// Default: auto-compute an L-shaped or Z-shaped orthogonal route.
|
|
147
|
+
return autoOrthogonal(start, end);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Auto-orthogonal routing (no waypoints).
|
|
151
|
+
* Creates an elbow connector: H → V (or V → H depending on direction).
|
|
152
|
+
*/
|
|
153
|
+
function autoOrthogonal(start, end) {
|
|
154
|
+
const dx = Math.abs(end.x - start.x);
|
|
155
|
+
const dy = Math.abs(end.y - start.y);
|
|
156
|
+
let d;
|
|
157
|
+
let mid;
|
|
158
|
+
let startPos;
|
|
159
|
+
let endPos;
|
|
160
|
+
if (dx >= dy) {
|
|
161
|
+
// Horizontal-first elbow: start → (midX, start.y) → (midX, end.y) → end
|
|
162
|
+
const midX = (start.x + end.x) / 2;
|
|
163
|
+
d = `M ${start.x} ${start.y} L ${midX} ${start.y} L ${midX} ${end.y} L ${end.x} ${end.y}`;
|
|
164
|
+
const pts = [
|
|
165
|
+
start,
|
|
166
|
+
{ x: midX, y: start.y },
|
|
167
|
+
{ x: midX, y: end.y },
|
|
168
|
+
end,
|
|
169
|
+
];
|
|
170
|
+
mid = polylinePointAt(pts, 0.5);
|
|
171
|
+
startPos = polylinePointAt(pts, LABEL_FRACTION_START);
|
|
172
|
+
endPos = polylinePointAt(pts, LABEL_FRACTION_END);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Vertical-first elbow: start → (start.x, midY) → (end.x, midY) → end
|
|
176
|
+
const midY = (start.y + end.y) / 2;
|
|
177
|
+
d = `M ${start.x} ${start.y} L ${start.x} ${midY} L ${end.x} ${midY} L ${end.x} ${end.y}`;
|
|
178
|
+
const pts = [
|
|
179
|
+
start,
|
|
180
|
+
{ x: start.x, y: midY },
|
|
181
|
+
{ x: end.x, y: midY },
|
|
182
|
+
end,
|
|
183
|
+
];
|
|
184
|
+
mid = polylinePointAt(pts, 0.5);
|
|
185
|
+
startPos = polylinePointAt(pts, LABEL_FRACTION_START);
|
|
186
|
+
endPos = polylinePointAt(pts, LABEL_FRACTION_END);
|
|
187
|
+
}
|
|
188
|
+
return { d, mid, start: startPos, end: endPos };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Orthogonal routing through user-specified waypoints.
|
|
192
|
+
* Each waypoint pair is connected by an elbow (H-V or V-H).
|
|
193
|
+
*/
|
|
194
|
+
function orthogonalThroughWaypoints(start, end, waypoints) {
|
|
195
|
+
const allPts = [start, ...waypoints, end];
|
|
196
|
+
let d = `M ${allPts[0].x} ${allPts[0].y}`;
|
|
197
|
+
// Build the actual rendered points (including elbow intermediaries)
|
|
198
|
+
const renderedPts = [allPts[0]];
|
|
199
|
+
for (let i = 1; i < allPts.length; i++) {
|
|
200
|
+
const prev = allPts[i - 1];
|
|
201
|
+
const cur = allPts[i];
|
|
202
|
+
// Elbow: go horizontal first, then vertical
|
|
203
|
+
d += ` L ${cur.x} ${prev.y} L ${cur.x} ${cur.y}`;
|
|
204
|
+
renderedPts.push({ x: cur.x, y: prev.y });
|
|
205
|
+
renderedPts.push(cur);
|
|
206
|
+
}
|
|
207
|
+
const positions = polylineLabelPositions(renderedPts);
|
|
208
|
+
return { d, ...positions };
|
|
209
|
+
}
|
|
210
|
+
// ── Shared helpers ──────────────────────────────────────────────────────────
|
|
211
|
+
/** Fraction along the path for each label position. */
|
|
212
|
+
const LABEL_FRACTION_START = 0.15;
|
|
213
|
+
const LABEL_FRACTION_END = 0.85;
|
|
214
|
+
/**
|
|
215
|
+
* Point along a polyline at a given fraction (0–1) of total arc length.
|
|
216
|
+
* Fraction 0 = first point, 1 = last point.
|
|
217
|
+
*/
|
|
218
|
+
function polylinePointAt(pts, fraction) {
|
|
219
|
+
if (pts.length === 0)
|
|
220
|
+
return { x: 0, y: 0 };
|
|
221
|
+
if (pts.length === 1)
|
|
222
|
+
return pts[0];
|
|
223
|
+
let totalLen = 0;
|
|
224
|
+
for (let i = 1; i < pts.length; i++) {
|
|
225
|
+
const dx = pts[i].x - pts[i - 1].x;
|
|
226
|
+
const dy = pts[i].y - pts[i - 1].y;
|
|
227
|
+
totalLen += Math.sqrt(dx * dx + dy * dy);
|
|
228
|
+
}
|
|
229
|
+
const target = totalLen * fraction;
|
|
230
|
+
let accumulated = 0;
|
|
231
|
+
for (let i = 1; i < pts.length; i++) {
|
|
232
|
+
const dx = pts[i].x - pts[i - 1].x;
|
|
233
|
+
const dy = pts[i].y - pts[i - 1].y;
|
|
234
|
+
const segLen = Math.sqrt(dx * dx + dy * dy);
|
|
235
|
+
if (accumulated + segLen >= target) {
|
|
236
|
+
const t = segLen === 0 ? 0 : (target - accumulated) / segLen;
|
|
237
|
+
return {
|
|
238
|
+
x: pts[i - 1].x + dx * t,
|
|
239
|
+
y: pts[i - 1].y + dy * t,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
accumulated += segLen;
|
|
243
|
+
}
|
|
244
|
+
return pts[pts.length - 1];
|
|
245
|
+
}
|
|
246
|
+
/** Shorthand: compute start, mid, end label positions for a polyline. */
|
|
247
|
+
function polylineLabelPositions(pts) {
|
|
248
|
+
return {
|
|
249
|
+
start: polylinePointAt(pts, LABEL_FRACTION_START),
|
|
250
|
+
mid: polylinePointAt(pts, 0.5),
|
|
251
|
+
end: polylinePointAt(pts, LABEL_FRACTION_END),
|
|
252
|
+
};
|
|
253
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export * from './builder';
|
|
|
3
3
|
export * from './styles';
|
|
4
4
|
export * from './animations';
|
|
5
5
|
export * from './overlays';
|
|
6
|
+
export * from './overlayBuilder';
|
|
7
|
+
export * from './edgePaths';
|
|
8
|
+
export * from './edgeLabels';
|
|
9
|
+
export { getDefaultPorts, getNodePorts, findPort, resolvePortPosition, } from './shapes';
|
|
6
10
|
export * from './anim/spec';
|
|
7
11
|
export * from './anim/animationBuilder';
|
|
8
12
|
export * from './anim/playback';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,10 @@ export * from './builder';
|
|
|
3
3
|
export * from './styles';
|
|
4
4
|
export * from './animations';
|
|
5
5
|
export * from './overlays';
|
|
6
|
+
export * from './overlayBuilder';
|
|
7
|
+
export * from './edgePaths';
|
|
8
|
+
export * from './edgeLabels';
|
|
9
|
+
export { getDefaultPorts, getNodePorts, findPort, resolvePortPosition, } from './shapes';
|
|
6
10
|
export * from './anim/spec';
|
|
7
11
|
export * from './anim/animationBuilder';
|
|
8
12
|
export * from './anim/playback';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { OverlayId, OverlayParams, VizOverlaySpec } from './types';
|
|
2
|
+
import type { CircleOverlayParams, GroupOverlayParams, RectOverlayParams, TextOverlayParams } from './overlays';
|
|
3
|
+
export type OverlayAddOptions = {
|
|
4
|
+
/** Optional stable key used to uniquely identify an overlay instance. */
|
|
5
|
+
key?: string;
|
|
6
|
+
/** Optional class applied by the overlay renderer. */
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Fluent overlay authoring.
|
|
11
|
+
*
|
|
12
|
+
* - Produces portable `VizOverlaySpec[]` data (rendering remains registry-driven).
|
|
13
|
+
* - Typed by `OverlayKindRegistry` when available, with a back-compat escape hatch.
|
|
14
|
+
*/
|
|
15
|
+
export declare class OverlayBuilder {
|
|
16
|
+
private readonly specs;
|
|
17
|
+
private readonly keyCounters;
|
|
18
|
+
/**
|
|
19
|
+
* Add an overlay spec.
|
|
20
|
+
*
|
|
21
|
+
* Overload 1: typed overlay ids via `OverlayKindRegistry`.
|
|
22
|
+
*/
|
|
23
|
+
add<K extends OverlayId>(id: K, params: OverlayParams<K>, options?: OverlayAddOptions): this;
|
|
24
|
+
/**
|
|
25
|
+
* Add an overlay spec.
|
|
26
|
+
*
|
|
27
|
+
* Overload 2: back-compat escape hatch for arbitrary ids.
|
|
28
|
+
*/
|
|
29
|
+
add(id: string, params: any, options?: OverlayAddOptions): this;
|
|
30
|
+
/** Remove overlays by key, or (if unkeyed) by id. */
|
|
31
|
+
remove(keyOrId: string): this;
|
|
32
|
+
/** Remove all overlays. */
|
|
33
|
+
clear(): this;
|
|
34
|
+
build(): VizOverlaySpec[];
|
|
35
|
+
/** Add a generic rectangle overlay (built-in, no custom registry needed). */
|
|
36
|
+
rect(params: RectOverlayParams, options?: OverlayAddOptions): this;
|
|
37
|
+
/** Add a generic circle overlay (built-in, no custom registry needed). */
|
|
38
|
+
circle(params: CircleOverlayParams, options?: OverlayAddOptions): this;
|
|
39
|
+
/** Add a generic text overlay (built-in, no custom registry needed). */
|
|
40
|
+
text(params: TextOverlayParams, options?: OverlayAddOptions): this;
|
|
41
|
+
/**
|
|
42
|
+
* Add a composite group overlay (built-in).
|
|
43
|
+
*
|
|
44
|
+
* Children are authored via the callback and rendered inside a single SVG <g>.
|
|
45
|
+
* You can animate the group by targeting its key and tweening `x`, `y`, `scale`, `rotation`, `opacity`.
|
|
46
|
+
*/
|
|
47
|
+
group(params: Omit<GroupOverlayParams, 'children'>, buildChildren: (overlay: OverlayBuilder) => unknown, options?: OverlayAddOptions): this;
|
|
48
|
+
}
|
|
49
|
+
/** Convenience helper for one-off overlay list compilation. */
|
|
50
|
+
export declare function buildOverlaySpecs(cb: (overlay: OverlayBuilder) => unknown): VizOverlaySpec[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluent overlay authoring.
|
|
3
|
+
*
|
|
4
|
+
* - Produces portable `VizOverlaySpec[]` data (rendering remains registry-driven).
|
|
5
|
+
* - Typed by `OverlayKindRegistry` when available, with a back-compat escape hatch.
|
|
6
|
+
*/
|
|
7
|
+
export class OverlayBuilder {
|
|
8
|
+
specs = [];
|
|
9
|
+
keyCounters = new Map();
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
add(id, params, options) {
|
|
12
|
+
const className = options?.className;
|
|
13
|
+
// If a user adds multiple overlays of the same id without keys, they will
|
|
14
|
+
// collide at reconcile time (since the DOM layer uses `key || id`).
|
|
15
|
+
// We auto-generate a stable-ish key in that case.
|
|
16
|
+
let key = options?.key;
|
|
17
|
+
if (!key) {
|
|
18
|
+
const hasUnkeyedSameId = this.specs.some((s) => s.id === id && (s.key === undefined || s.key === ''));
|
|
19
|
+
if (hasUnkeyedSameId) {
|
|
20
|
+
const next = (this.keyCounters.get(id) ?? 0) + 1;
|
|
21
|
+
this.keyCounters.set(id, next);
|
|
22
|
+
key = `${id}#${next}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
this.specs.push({ id, params, key, className });
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
/** Remove overlays by key, or (if unkeyed) by id. */
|
|
29
|
+
remove(keyOrId) {
|
|
30
|
+
for (let i = this.specs.length - 1; i >= 0; i--) {
|
|
31
|
+
const s = this.specs[i];
|
|
32
|
+
if (!s)
|
|
33
|
+
continue;
|
|
34
|
+
const matchesKey = s.key === keyOrId;
|
|
35
|
+
const matchesUnkeyedId = !s.key && s.id === keyOrId;
|
|
36
|
+
if (matchesKey || matchesUnkeyedId)
|
|
37
|
+
this.specs.splice(i, 1);
|
|
38
|
+
}
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
/** Remove all overlays. */
|
|
42
|
+
clear() {
|
|
43
|
+
this.specs.length = 0;
|
|
44
|
+
this.keyCounters.clear();
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
build() {
|
|
48
|
+
return [...this.specs];
|
|
49
|
+
}
|
|
50
|
+
/** Add a generic rectangle overlay (built-in, no custom registry needed). */
|
|
51
|
+
rect(params, options) {
|
|
52
|
+
return this.add('rect', params, options);
|
|
53
|
+
}
|
|
54
|
+
/** Add a generic circle overlay (built-in, no custom registry needed). */
|
|
55
|
+
circle(params, options) {
|
|
56
|
+
return this.add('circle', params, options);
|
|
57
|
+
}
|
|
58
|
+
/** Add a generic text overlay (built-in, no custom registry needed). */
|
|
59
|
+
text(params, options) {
|
|
60
|
+
return this.add('text', params, options);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Add a composite group overlay (built-in).
|
|
64
|
+
*
|
|
65
|
+
* Children are authored via the callback and rendered inside a single SVG <g>.
|
|
66
|
+
* You can animate the group by targeting its key and tweening `x`, `y`, `scale`, `rotation`, `opacity`.
|
|
67
|
+
*/
|
|
68
|
+
group(params, buildChildren, options) {
|
|
69
|
+
const childOverlay = new OverlayBuilder();
|
|
70
|
+
buildChildren(childOverlay);
|
|
71
|
+
const children = childOverlay.build();
|
|
72
|
+
return this.add('group', { ...params, children }, options);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Convenience helper for one-off overlay list compilation. */
|
|
76
|
+
export function buildOverlaySpecs(cb) {
|
|
77
|
+
const overlay = new OverlayBuilder();
|
|
78
|
+
cb(overlay);
|
|
79
|
+
return overlay.build();
|
|
80
|
+
}
|
package/dist/overlays.d.ts
CHANGED
|
@@ -1,9 +1,115 @@
|
|
|
1
1
|
import type { VizNode, VizEdge, VizOverlaySpec, VizScene } from './types';
|
|
2
|
+
export type SignalOverlayParams = {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
progress: number;
|
|
6
|
+
magnitude?: number;
|
|
7
|
+
};
|
|
8
|
+
export type GridLabelsOverlayParams = {
|
|
9
|
+
colLabels?: Record<number, string>;
|
|
10
|
+
rowLabels?: Record<number, string>;
|
|
11
|
+
yOffset?: number;
|
|
12
|
+
xOffset?: number;
|
|
13
|
+
};
|
|
14
|
+
export interface DataPoint {
|
|
15
|
+
id: string;
|
|
16
|
+
currentNodeId: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
export type DataPointsOverlayParams = {
|
|
20
|
+
points: DataPoint[];
|
|
21
|
+
};
|
|
22
|
+
export type RectOverlayParams = {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
w: number;
|
|
26
|
+
h: number;
|
|
27
|
+
rx?: number;
|
|
28
|
+
ry?: number;
|
|
29
|
+
opacity?: number;
|
|
30
|
+
/** SVG fill (defaults to a visible blue). Can be overridden by CSS via className. */
|
|
31
|
+
fill?: string;
|
|
32
|
+
/** SVG stroke (defaults to a visible blue). Can be overridden by CSS via className. */
|
|
33
|
+
stroke?: string;
|
|
34
|
+
/** SVG stroke-width (defaults to 3). Can be overridden by CSS via className. */
|
|
35
|
+
strokeWidth?: number;
|
|
36
|
+
};
|
|
37
|
+
export type CircleOverlayParams = {
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
r: number;
|
|
41
|
+
opacity?: number;
|
|
42
|
+
/** SVG fill (defaults to a visible blue). Can be overridden by CSS via className. */
|
|
43
|
+
fill?: string;
|
|
44
|
+
/** SVG stroke (defaults to a visible blue). Can be overridden by CSS via className. */
|
|
45
|
+
stroke?: string;
|
|
46
|
+
/** SVG stroke-width (defaults to 3). Can be overridden by CSS via className. */
|
|
47
|
+
strokeWidth?: number;
|
|
48
|
+
};
|
|
49
|
+
export type TextOverlayParams = {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
text: string;
|
|
53
|
+
opacity?: number;
|
|
54
|
+
/** SVG fill color (defaults to #111). Can be overridden by CSS via className. */
|
|
55
|
+
fill?: string;
|
|
56
|
+
fontSize?: number;
|
|
57
|
+
fontWeight?: string | number;
|
|
58
|
+
textAnchor?: 'start' | 'middle' | 'end';
|
|
59
|
+
dominantBaseline?: string;
|
|
60
|
+
};
|
|
61
|
+
export type GroupOverlayParams = {
|
|
62
|
+
/**
|
|
63
|
+
* Translate (group-local origin).
|
|
64
|
+
*
|
|
65
|
+
* If `from`/`to` are provided, these act as an additional offset.
|
|
66
|
+
*/
|
|
67
|
+
x?: number;
|
|
68
|
+
y?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Optional node ids used to drive the group's position via `progress`.
|
|
71
|
+
*
|
|
72
|
+
* When set, the group will translate along the line from `from` to `to`.
|
|
73
|
+
*/
|
|
74
|
+
from?: string;
|
|
75
|
+
to?: string;
|
|
76
|
+
/** Interpolation 0..1 used when `from`/`to` are set. */
|
|
77
|
+
progress?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Optional "pulse" value 0..1.
|
|
80
|
+
*
|
|
81
|
+
* When provided, it scales the group slightly (in addition to `scale`).
|
|
82
|
+
*/
|
|
83
|
+
magnitude?: number;
|
|
84
|
+
/** Scale around group origin. */
|
|
85
|
+
scale?: number;
|
|
86
|
+
/** Rotate (degrees) around group origin. */
|
|
87
|
+
rotation?: number;
|
|
88
|
+
/** Group opacity (multiplies with child opacity). */
|
|
89
|
+
opacity?: number;
|
|
90
|
+
/** Child overlays rendered inside this group. Coordinates are group-local. */
|
|
91
|
+
children: VizOverlaySpec[];
|
|
92
|
+
};
|
|
93
|
+
declare module './types' {
|
|
94
|
+
interface OverlayKindRegistry {
|
|
95
|
+
signal: SignalOverlayParams;
|
|
96
|
+
'grid-labels': GridLabelsOverlayParams;
|
|
97
|
+
'data-points': DataPointsOverlayParams;
|
|
98
|
+
/** Generic overlay primitives (no custom registry needed). */
|
|
99
|
+
rect: RectOverlayParams;
|
|
100
|
+
circle: CircleOverlayParams;
|
|
101
|
+
text: TextOverlayParams;
|
|
102
|
+
/** Overlay container that can hold child overlays and be animated as a unit. */
|
|
103
|
+
group: GroupOverlayParams;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
2
106
|
export interface CoreOverlayRenderContext<T = any> {
|
|
3
107
|
spec: VizOverlaySpec<T>;
|
|
4
108
|
nodesById: Map<string, VizNode>;
|
|
5
109
|
edgesById: Map<string, VizEdge>;
|
|
6
110
|
scene: VizScene;
|
|
111
|
+
/** Registry reference (useful for composite overlays like `group`). */
|
|
112
|
+
registry?: CoreOverlayRegistry;
|
|
7
113
|
}
|
|
8
114
|
export interface CoreOverlayRenderer<T = any> {
|
|
9
115
|
render: (ctx: CoreOverlayRenderContext<T>) => string;
|
|
@@ -14,25 +120,11 @@ export declare class CoreOverlayRegistry {
|
|
|
14
120
|
register(id: string, renderer: CoreOverlayRenderer): this;
|
|
15
121
|
get(id: string): CoreOverlayRenderer<any> | undefined;
|
|
16
122
|
}
|
|
17
|
-
export declare const coreSignalOverlay: CoreOverlayRenderer<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export declare const
|
|
24
|
-
colLabels?: Record<number, string>;
|
|
25
|
-
rowLabels?: Record<number, string>;
|
|
26
|
-
yOffset?: number;
|
|
27
|
-
xOffset?: number;
|
|
28
|
-
}>;
|
|
29
|
-
interface DataPoint {
|
|
30
|
-
id: string;
|
|
31
|
-
currentNodeId: string;
|
|
32
|
-
[key: string]: any;
|
|
33
|
-
}
|
|
34
|
-
export declare const coreDataPointOverlay: CoreOverlayRenderer<{
|
|
35
|
-
points: DataPoint[];
|
|
36
|
-
}>;
|
|
123
|
+
export declare const coreSignalOverlay: CoreOverlayRenderer<SignalOverlayParams>;
|
|
124
|
+
export declare const coreGridLabelsOverlay: CoreOverlayRenderer<GridLabelsOverlayParams>;
|
|
125
|
+
export declare const coreDataPointOverlay: CoreOverlayRenderer<DataPointsOverlayParams>;
|
|
126
|
+
export declare const coreRectOverlay: CoreOverlayRenderer<RectOverlayParams>;
|
|
127
|
+
export declare const coreCircleOverlay: CoreOverlayRenderer<CircleOverlayParams>;
|
|
128
|
+
export declare const coreTextOverlay: CoreOverlayRenderer<TextOverlayParams>;
|
|
129
|
+
export declare const coreGroupOverlay: CoreOverlayRenderer<GroupOverlayParams>;
|
|
37
130
|
export declare const defaultCoreOverlayRegistry: CoreOverlayRegistry;
|
|
38
|
-
export {};
|