@opendata-ai/openchart-vanilla 8.4.0 → 8.5.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/README.md +59 -0
- package/dist/chunk-2OJZBMXM.js +377 -0
- package/dist/chunk-2OJZBMXM.js.map +1 -0
- package/dist/{chunk-45XZOWYC.js → chunk-5KV3ZBBL.js} +58 -437
- package/dist/chunk-5KV3ZBBL.js.map +1 -0
- package/dist/{chunk-HGRNXLDF.js → chunk-5RX6ZQGZ.js} +10 -1
- package/dist/chunk-5RX6ZQGZ.js.map +1 -0
- package/dist/{chunk-UOB422LY.js → chunk-6B5OBGIO.js} +2 -2
- package/dist/chunk-NG7CVCI2.js +58 -0
- package/dist/chunk-NG7CVCI2.js.map +1 -0
- package/dist/{chunk-FETYAVTZ.js → chunk-RYVV3YIG.js} +3 -3
- package/dist/{chunk-LNPRDSK3.js → chunk-UAK6HWOC.js} +2 -2
- package/dist/chunk-WOLX5EZT.js +3577 -0
- package/dist/chunk-WOLX5EZT.js.map +1 -0
- package/dist/{chunk-YGYMB7KB.js → chunk-XGII4ZRT.js} +55 -30
- package/dist/chunk-XGII4ZRT.js.map +1 -0
- package/dist/export-gif.js +3 -3
- package/dist/graph-3d/index.d.ts +272 -0
- package/dist/graph-3d/index.js +1556 -0
- package/dist/graph-3d/index.js.map +1 -0
- package/dist/index.d.ts +4 -252
- package/dist/index.js +27 -3471
- package/dist/index.js.map +1 -1
- package/dist/renderer-registry-B5tW6yAZ.d.ts +376 -0
- package/dist/static.js +4 -3
- package/dist/static.js.map +1 -1
- package/dist/story/index.js +10 -7
- package/dist/story/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +26 -5
- package/dist/chunk-45XZOWYC.js.map +0 -1
- package/dist/chunk-HGRNXLDF.js.map +0 -1
- package/dist/chunk-YGYMB7KB.js.map +0 -1
- /package/dist/{chunk-UOB422LY.js.map → chunk-6B5OBGIO.js.map} +0 -0
- /package/dist/{chunk-FETYAVTZ.js.map → chunk-RYVV3YIG.js.map} +0 -0
- /package/dist/{chunk-LNPRDSK3.js.map → chunk-UAK6HWOC.js.map} +0 -0
package/README.md
CHANGED
|
@@ -78,6 +78,65 @@ graph.destroy();
|
|
|
78
78
|
|
|
79
79
|
Graphs render on canvas with a force simulation running in a web worker. Nodes support click, drag, and double-click. The simulation auto-fits nodes once it settles.
|
|
80
80
|
|
|
81
|
+
## 3D graphs
|
|
82
|
+
|
|
83
|
+
`dimensions: 3` on a graph spec renders with WebGL instead of canvas. The 3D
|
|
84
|
+
renderer ships on its own subpath so three.js never lands in the default bundle,
|
|
85
|
+
and its libraries are optional peers you install yourself:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install three 3d-force-graph three-spritetext
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The subpath registers the renderer as an import side effect, so it has to be
|
|
92
|
+
imported before the graph mounts:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import '@opendata-ai/openchart-vanilla/graph-3d';
|
|
96
|
+
import { createGraph } from '@opendata-ai/openchart-vanilla';
|
|
97
|
+
|
|
98
|
+
createGraph(container, { ...graphSpec, dimensions: 3 });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The subpath import is required only when compilation actually resolves to 3D.
|
|
102
|
+
A spec above the 2000-node gate warns and falls back to 2D, so it renders
|
|
103
|
+
without the import; a `dimensions: 3` spec that stays 3D throws without it.
|
|
104
|
+
|
|
105
|
+
Framework hosts import their own package's mirror of this subpath rather than
|
|
106
|
+
reaching past it into vanilla: `@opendata-ai/openchart-react/graph-3d`,
|
|
107
|
+
`@opendata-ai/openchart-vue/graph-3d`, `@opendata-ai/openchart-svelte/graph-3d`.
|
|
108
|
+
|
|
109
|
+
Notes:
|
|
110
|
+
|
|
111
|
+
- **SSR**: `3d-force-graph` touches `window` at import time. Import the subpath
|
|
112
|
+
client-side only (inside `useEffect`, a dynamic `import()`, or a browser-only
|
|
113
|
+
module) and render your own placeholder until it resolves.
|
|
114
|
+
- **One copy of three**: run `npm ls three` (or `bun pm ls three`) and confirm a
|
|
115
|
+
single copy. Two copies fail at runtime with `Cannot read properties of
|
|
116
|
+
undefined (reading 'VERTEX')` (vasturiano/react-force-graph#595).
|
|
117
|
+
|
|
118
|
+
Differences from 2D:
|
|
119
|
+
|
|
120
|
+
- Labels use a fixed budget re-ranked by camera distance, so distant labels drop
|
|
121
|
+
out instead of being decluttered by priority.
|
|
122
|
+
- The force simulation runs on the main thread. Above 2000 nodes the spec warns
|
|
123
|
+
and renders in 2D.
|
|
124
|
+
- Keyboard navigation, SVG export, `interaction.cursorRepulsion`, and
|
|
125
|
+
`interaction.springyDrag` are unsupported. Each warns and is ignored.
|
|
126
|
+
(`layout.type` of `radial` or `hierarchical` is rejected by spec validation in
|
|
127
|
+
both dimensions, so it never reaches either renderer.)
|
|
128
|
+
- `getCamera()` returns a pose, not a zoom transform: `x`/`y` are the look-at
|
|
129
|
+
point in world units, alongside `position` and `target`. In 2D they are the
|
|
130
|
+
zoom transform's translate in pixels. `onCameraChange` carries the same
|
|
131
|
+
payload, so persisted camera state has to be keyed on the dimension it came
|
|
132
|
+
from. Both are typed `GraphCamera`, whose `position`/`target` are optional
|
|
133
|
+
because only 3D sets them; `flyTo` accepts them back, and 2D ignores them.
|
|
134
|
+
- A structural `update()` reheats the whole layout, so settled nodes drift. 2D
|
|
135
|
+
applies a local impulse instead.
|
|
136
|
+
- `nodeOverrides[*].stroke` and `strokeWidth` are ignored (no ring).
|
|
137
|
+
- A dimension change is a remount, not an `update()`. The framework wrappers do
|
|
138
|
+
this for you; `update()` with a changed `dimensions` warns and no-ops.
|
|
139
|
+
|
|
81
140
|
## Export utilities
|
|
82
141
|
|
|
83
142
|
Standalone export functions if you need them outside of an instance:
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// src/resize-observer.ts
|
|
2
|
+
var DEBOUNCE_MS = 16;
|
|
3
|
+
function observeResize(container, callback) {
|
|
4
|
+
let timeoutId = null;
|
|
5
|
+
let lastDelivered = null;
|
|
6
|
+
const observer = new ResizeObserver((entries) => {
|
|
7
|
+
if (timeoutId !== null) {
|
|
8
|
+
clearTimeout(timeoutId);
|
|
9
|
+
}
|
|
10
|
+
timeoutId = setTimeout(() => {
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
const { width, height } = entry.contentRect;
|
|
13
|
+
if (lastDelivered && lastDelivered.width === width && lastDelivered.height === height) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
lastDelivered = { width, height };
|
|
17
|
+
callback(width, height);
|
|
18
|
+
}
|
|
19
|
+
timeoutId = null;
|
|
20
|
+
}, DEBOUNCE_MS);
|
|
21
|
+
});
|
|
22
|
+
observer.observe(container);
|
|
23
|
+
return () => {
|
|
24
|
+
if (timeoutId !== null) {
|
|
25
|
+
clearTimeout(timeoutId);
|
|
26
|
+
}
|
|
27
|
+
observer.disconnect();
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/tooltip.ts
|
|
32
|
+
import { computePosition, flip, offset, shift } from "@floating-ui/dom";
|
|
33
|
+
var TOOLTIP_OFFSET = 12;
|
|
34
|
+
function createTooltipManager(container) {
|
|
35
|
+
const tooltip = document.createElement("div");
|
|
36
|
+
tooltip.className = "oc-tooltip";
|
|
37
|
+
tooltip.setAttribute("role", "tooltip");
|
|
38
|
+
container.style.position = container.style.position || "relative";
|
|
39
|
+
container.appendChild(tooltip);
|
|
40
|
+
let lastContentKey = "";
|
|
41
|
+
let currentPositionId = 0;
|
|
42
|
+
const handleDocumentTouch = (e) => {
|
|
43
|
+
if (!container.contains(e.target)) {
|
|
44
|
+
hide();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
document.addEventListener("touchstart", handleDocumentTouch);
|
|
48
|
+
const handleDocumentKeydown = (e) => {
|
|
49
|
+
if (e.key === "Escape" && tooltip.style.display === "block") {
|
|
50
|
+
hide();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
document.addEventListener("keydown", handleDocumentKeydown);
|
|
54
|
+
function show(input, x, y, opts) {
|
|
55
|
+
if ("element" in input) {
|
|
56
|
+
lastContentKey = "";
|
|
57
|
+
tooltip.replaceChildren(input.element);
|
|
58
|
+
position(x, y, opts);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if ("text" in input) {
|
|
62
|
+
lastContentKey = "";
|
|
63
|
+
tooltip.textContent = input.text;
|
|
64
|
+
position(x, y, opts);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const content = input;
|
|
68
|
+
const emphasisIndex = content.fields.findIndex((f) => f.emphasis);
|
|
69
|
+
const contentKey = `${content.title}|${content.fields.length}|${content.fields[0]?.value}|${content.fields[content.fields.length - 1]?.value}|${emphasisIndex}`;
|
|
70
|
+
if (contentKey !== lastContentKey) {
|
|
71
|
+
lastContentKey = contentKey;
|
|
72
|
+
let html = "";
|
|
73
|
+
const colorRowCount = content.fields.filter((f) => f.color && f.role !== "total").length;
|
|
74
|
+
const perRowSwatches = colorRowCount > 1;
|
|
75
|
+
if (content.title) {
|
|
76
|
+
const titleColor = perRowSwatches ? void 0 : content.fields.find((f) => f.color)?.color;
|
|
77
|
+
html += '<div class="oc-tooltip-header">';
|
|
78
|
+
if (titleColor) {
|
|
79
|
+
html += `<span class="oc-tooltip-dot" style="background:${esc(titleColor)}"></span>`;
|
|
80
|
+
}
|
|
81
|
+
html += `<span class="oc-tooltip-title">${esc(content.title)}</span>`;
|
|
82
|
+
html += "</div>";
|
|
83
|
+
}
|
|
84
|
+
if (content.fields.length > 0) {
|
|
85
|
+
html += '<div class="oc-tooltip-body">';
|
|
86
|
+
for (const field of content.fields) {
|
|
87
|
+
let rowClass = "oc-tooltip-row";
|
|
88
|
+
if (field.emphasis) rowClass += " oc-tooltip-row--emphasis";
|
|
89
|
+
if (field.role === "total") rowClass += " oc-tooltip-row--total";
|
|
90
|
+
html += `<div class="${rowClass}">`;
|
|
91
|
+
html += '<span class="oc-tooltip-label">';
|
|
92
|
+
if (perRowSwatches && field.color) {
|
|
93
|
+
html += `<span class="oc-tooltip-row-swatch" style="background:${esc(field.color)}"></span>`;
|
|
94
|
+
}
|
|
95
|
+
html += `${esc(field.label)}</span>`;
|
|
96
|
+
html += `<span class="oc-tooltip-value">${esc(field.value)}</span>`;
|
|
97
|
+
html += "</div>";
|
|
98
|
+
}
|
|
99
|
+
html += "</div>";
|
|
100
|
+
}
|
|
101
|
+
tooltip.innerHTML = html;
|
|
102
|
+
}
|
|
103
|
+
position(x, y, opts);
|
|
104
|
+
}
|
|
105
|
+
function position(x, y, opts) {
|
|
106
|
+
tooltip.style.display = "block";
|
|
107
|
+
const positionId = ++currentPositionId;
|
|
108
|
+
const virtualRef = {
|
|
109
|
+
getBoundingClientRect() {
|
|
110
|
+
const rect = container.getBoundingClientRect();
|
|
111
|
+
return {
|
|
112
|
+
x: rect.left + x,
|
|
113
|
+
y: rect.top + y,
|
|
114
|
+
width: 0,
|
|
115
|
+
height: 0,
|
|
116
|
+
top: rect.top + y,
|
|
117
|
+
left: rect.left + x,
|
|
118
|
+
right: rect.left + x,
|
|
119
|
+
bottom: rect.top + y
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
computePosition(virtualRef, tooltip, {
|
|
124
|
+
placement: opts?.placement ?? "bottom-start",
|
|
125
|
+
middleware: [offset(TOOLTIP_OFFSET), flip(), shift({ padding: 8 })]
|
|
126
|
+
}).then(({ x: fx, y: fy }) => {
|
|
127
|
+
if (positionId !== currentPositionId) return;
|
|
128
|
+
tooltip.style.left = `${fx}px`;
|
|
129
|
+
tooltip.style.top = `${fy}px`;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function move(x, y, opts) {
|
|
133
|
+
if (tooltip.style.display !== "block") return;
|
|
134
|
+
position(x, y, opts);
|
|
135
|
+
}
|
|
136
|
+
function hide() {
|
|
137
|
+
tooltip.style.display = "none";
|
|
138
|
+
lastContentKey = "";
|
|
139
|
+
}
|
|
140
|
+
function destroy() {
|
|
141
|
+
document.removeEventListener("touchstart", handleDocumentTouch);
|
|
142
|
+
document.removeEventListener("keydown", handleDocumentKeydown);
|
|
143
|
+
if (tooltip.parentNode) {
|
|
144
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { show, move, hide, destroy };
|
|
148
|
+
}
|
|
149
|
+
function esc(str) {
|
|
150
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/motion/easing.ts
|
|
154
|
+
function linear(t) {
|
|
155
|
+
return t;
|
|
156
|
+
}
|
|
157
|
+
function cubicOut(t) {
|
|
158
|
+
const f = 1 - t;
|
|
159
|
+
return 1 - f * f * f;
|
|
160
|
+
}
|
|
161
|
+
function cubicInOut(t) {
|
|
162
|
+
return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
163
|
+
}
|
|
164
|
+
var clamp01 = (t) => Math.min(1, Math.max(0, t));
|
|
165
|
+
var lerp = (a, b, t) => a + (b - a) * t;
|
|
166
|
+
|
|
167
|
+
// src/resolve-dark-mode.ts
|
|
168
|
+
function resolveDarkMode(mode) {
|
|
169
|
+
if (mode === "force") return true;
|
|
170
|
+
if (mode === "off" || mode === void 0) return false;
|
|
171
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
172
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/motion/scheduler.ts
|
|
178
|
+
var AnimationScheduler = class {
|
|
179
|
+
animations = /* @__PURE__ */ new Set();
|
|
180
|
+
requestFrame;
|
|
181
|
+
/**
|
|
182
|
+
* @param requestFrame Called on the idle→active transition to arm the first
|
|
183
|
+
* frame (typically the mount's `scheduleRender`).
|
|
184
|
+
*/
|
|
185
|
+
constructor(requestFrame) {
|
|
186
|
+
this.requestFrame = requestFrame;
|
|
187
|
+
}
|
|
188
|
+
/** Add an animation. Arms the first frame if the scheduler was idle. */
|
|
189
|
+
add(a) {
|
|
190
|
+
const wasIdle = this.animations.size === 0;
|
|
191
|
+
this.animations.add(a);
|
|
192
|
+
if (wasIdle) this.requestFrame();
|
|
193
|
+
}
|
|
194
|
+
/** Remove an animation without finishing or cancelling it. Safe mid-tick. */
|
|
195
|
+
remove(a) {
|
|
196
|
+
this.animations.delete(a);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Advance all animations to `now`. Completed animations are removed. Returns
|
|
200
|
+
* true if any animation ran this frame (so the caller marks the frame dirty).
|
|
201
|
+
*
|
|
202
|
+
* Iterates a snapshot so an `onDone` that mutates the set (via update/destroy)
|
|
203
|
+
* can't corrupt the loop.
|
|
204
|
+
*/
|
|
205
|
+
tick(now) {
|
|
206
|
+
if (this.animations.size === 0) return false;
|
|
207
|
+
const snapshot = [...this.animations];
|
|
208
|
+
let ran = false;
|
|
209
|
+
for (const a of snapshot) {
|
|
210
|
+
if (!this.animations.has(a)) continue;
|
|
211
|
+
ran = true;
|
|
212
|
+
const running = a.tick(now);
|
|
213
|
+
if (!running) this.animations.delete(a);
|
|
214
|
+
}
|
|
215
|
+
return ran;
|
|
216
|
+
}
|
|
217
|
+
/** True while any animation is active (drives rAF re-arming). */
|
|
218
|
+
get active() {
|
|
219
|
+
return this.animations.size > 0;
|
|
220
|
+
}
|
|
221
|
+
/** Cancel every animation (no final state, no onDone). Used on teardown. */
|
|
222
|
+
cancelAll() {
|
|
223
|
+
const snapshot = [...this.animations];
|
|
224
|
+
this.animations.clear();
|
|
225
|
+
for (const a of snapshot) a.cancel();
|
|
226
|
+
}
|
|
227
|
+
/** Finish every animation (snap to final, fire onDone). Used before update(). */
|
|
228
|
+
finishAll() {
|
|
229
|
+
const snapshot = [...this.animations];
|
|
230
|
+
this.animations.clear();
|
|
231
|
+
for (const a of snapshot) a.finish();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/motion/tween.ts
|
|
236
|
+
function resolveEase(ease) {
|
|
237
|
+
return ease === "snappy" ? cubicOut : cubicInOut;
|
|
238
|
+
}
|
|
239
|
+
function prefersReducedMotion() {
|
|
240
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
241
|
+
try {
|
|
242
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function createTween(opts) {
|
|
248
|
+
const { duration, ease, apply, onDone } = opts;
|
|
249
|
+
let startTime = null;
|
|
250
|
+
let finished = false;
|
|
251
|
+
function settleAt(t) {
|
|
252
|
+
apply(ease(t));
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
tick(now) {
|
|
256
|
+
if (finished) return false;
|
|
257
|
+
if (startTime === null) startTime = now;
|
|
258
|
+
const elapsed = now - startTime;
|
|
259
|
+
const raw = duration <= 0 ? 1 : Math.min(1, elapsed / duration);
|
|
260
|
+
settleAt(raw);
|
|
261
|
+
if (raw >= 1) {
|
|
262
|
+
finished = true;
|
|
263
|
+
onDone?.();
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
},
|
|
268
|
+
finish() {
|
|
269
|
+
if (finished) return;
|
|
270
|
+
finished = true;
|
|
271
|
+
settleAt(1);
|
|
272
|
+
onDone?.();
|
|
273
|
+
},
|
|
274
|
+
cancel() {
|
|
275
|
+
finished = true;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/spatial-index.ts
|
|
281
|
+
import { quadtree } from "d3-quadtree";
|
|
282
|
+
var SpatialIndex = class {
|
|
283
|
+
tree = null;
|
|
284
|
+
nodes = [];
|
|
285
|
+
maxRadius = 0;
|
|
286
|
+
generation = 0;
|
|
287
|
+
/** Rebuild the quadtree from the current set of positioned nodes. */
|
|
288
|
+
rebuild(nodes) {
|
|
289
|
+
this.nodes = nodes;
|
|
290
|
+
this.maxRadius = 0;
|
|
291
|
+
for (const n of nodes) {
|
|
292
|
+
if (n.radius > this.maxRadius) this.maxRadius = n.radius;
|
|
293
|
+
}
|
|
294
|
+
this.tree = quadtree().x((d) => d.x).y((d) => d.y).addAll(nodes);
|
|
295
|
+
this.generation++;
|
|
296
|
+
}
|
|
297
|
+
/** Current generation counter. Increments on each rebuild. */
|
|
298
|
+
getGeneration() {
|
|
299
|
+
return this.generation;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Find the nearest node to (x, y) within maxDistance.
|
|
303
|
+
* Accounts for node radius: a hit occurs if the point is inside
|
|
304
|
+
* the node circle (distance to center < node.radius), or if the
|
|
305
|
+
* edge-to-edge distance is within maxDistance.
|
|
306
|
+
*/
|
|
307
|
+
findNearest(x, y, maxDistance = Infinity) {
|
|
308
|
+
if (!this.tree || this.nodes.length === 0) return null;
|
|
309
|
+
const searchRadius = maxDistance + this.maxRadius;
|
|
310
|
+
let best = null;
|
|
311
|
+
let bestEffectiveDist = maxDistance + this.maxRadius + 1;
|
|
312
|
+
this.tree.visit((node, x0, y0, x1, y1) => {
|
|
313
|
+
const closestX = Math.max(x0, Math.min(x, x1));
|
|
314
|
+
const closestY = Math.max(y0, Math.min(y, y1));
|
|
315
|
+
const quadDist = Math.hypot(closestX - x, closestY - y);
|
|
316
|
+
if (quadDist > searchRadius) return true;
|
|
317
|
+
if (!node.length) {
|
|
318
|
+
let current = node;
|
|
319
|
+
do {
|
|
320
|
+
const d = current.data;
|
|
321
|
+
if (d) {
|
|
322
|
+
const dist = Math.hypot(d.x - x, d.y - y);
|
|
323
|
+
const effectiveDist = Math.max(0, dist - d.radius);
|
|
324
|
+
if (effectiveDist <= maxDistance && effectiveDist < bestEffectiveDist) {
|
|
325
|
+
bestEffectiveDist = effectiveDist;
|
|
326
|
+
best = d;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} while (current = current.next);
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
});
|
|
333
|
+
return best;
|
|
334
|
+
}
|
|
335
|
+
/** Find all nodes whose centers fall within the given rectangle. */
|
|
336
|
+
findInRect(x1, y1, x2, y2) {
|
|
337
|
+
if (!this.tree) return [];
|
|
338
|
+
const minX = Math.min(x1, x2);
|
|
339
|
+
const minY = Math.min(y1, y2);
|
|
340
|
+
const maxX = Math.max(x1, x2);
|
|
341
|
+
const maxY = Math.max(y1, y2);
|
|
342
|
+
const results = [];
|
|
343
|
+
this.tree.visit((node, qx0, qy0, qx1, qy1) => {
|
|
344
|
+
if (qx0 > maxX || qx1 < minX || qy0 > maxY || qy1 < minY) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
if (!node.length) {
|
|
348
|
+
let current = node;
|
|
349
|
+
do {
|
|
350
|
+
const d = current.data;
|
|
351
|
+
if (d && d.x >= minX && d.x <= maxX && d.y >= minY && d.y <= maxY) {
|
|
352
|
+
results.push(d);
|
|
353
|
+
}
|
|
354
|
+
} while (current = current.next);
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
});
|
|
358
|
+
return results;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export {
|
|
363
|
+
observeResize,
|
|
364
|
+
resolveDarkMode,
|
|
365
|
+
createTooltipManager,
|
|
366
|
+
AnimationScheduler,
|
|
367
|
+
SpatialIndex,
|
|
368
|
+
linear,
|
|
369
|
+
cubicOut,
|
|
370
|
+
cubicInOut,
|
|
371
|
+
clamp01,
|
|
372
|
+
lerp,
|
|
373
|
+
resolveEase,
|
|
374
|
+
prefersReducedMotion,
|
|
375
|
+
createTween
|
|
376
|
+
};
|
|
377
|
+
//# sourceMappingURL=chunk-2OJZBMXM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resize-observer.ts","../src/tooltip.ts","../src/motion/easing.ts","../src/resolve-dark-mode.ts","../src/motion/scheduler.ts","../src/motion/tween.ts","../src/spatial-index.ts"],"sourcesContent":["/**\n * Resize observer: thin wrapper around ResizeObserver with debounce.\n *\n * Watches a container element for size changes and calls back with\n * the new width and height. Debounced at ~60fps (16ms) to avoid\n * excessive re-renders during drag resizes.\n */\n\nconst DEBOUNCE_MS = 16;\n\n/**\n * Observe a container element for size changes.\n *\n * @param container - The element to watch.\n * @param callback - Called with (width, height) when size changes.\n * @returns A cleanup function that disconnects the observer.\n */\nexport function observeResize(\n container: HTMLElement,\n callback: (width: number, height: number) => void,\n): () => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let lastDelivered: { width: number; height: number } | null = null;\n\n const observer = new ResizeObserver((entries) => {\n // Debounce to ~60fps\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n timeoutId = setTimeout(() => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect;\n // Always deliver the first callback (the initial fire on insertion is\n // load-bearing, see .claude/rules/svg-animation.md). Only dedupe\n // subsequent deliveries whose size didn't actually change.\n if (lastDelivered && lastDelivered.width === width && lastDelivered.height === height) {\n continue;\n }\n lastDelivered = { width, height };\n callback(width, height);\n }\n timeoutId = null;\n }, DEBOUNCE_MS);\n });\n\n observer.observe(container);\n\n return () => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n observer.disconnect();\n };\n}\n","/**\n * Tooltip manager: creates and positions a floating tooltip element.\n *\n * Shows tooltip content near the mouse/touch position with viewport\n * edge avoidance via @floating-ui/dom. Touch support via tap-to-show,\n * tap-outside-to-hide.\n */\n\nimport type { Placement } from '@floating-ui/dom';\nimport { computePosition, flip, offset, shift } from '@floating-ui/dom';\nimport type { TooltipContent } from '@opendata-ai/openchart-core';\n\nexport interface TooltipShowOptions {\n /**\n * Floating-ui placement. Defaults to 'bottom-start' for mark hover\n * (the legacy behavior). Line/area snap-tooltips pass 'right-start'\n * so the card sits beside the snapped point and flips to 'left-start'\n * via the flip middleware when crowding the right edge.\n */\n placement?: Placement;\n}\n\n/**\n * Content accepted by {@link TooltipManager.show}. A structured\n * {@link TooltipContent} is rendered by the manager; `{ text }` is inserted as\n * plain text (escaped via textContent); `{ element }` is a caller-owned node\n * trusted verbatim (the host owns sanitization).\n */\nexport type TooltipInput = TooltipContent | { text: string } | { element: HTMLElement };\n\nexport interface TooltipManager {\n /** Show the tooltip with content at a given position. */\n show(content: TooltipInput, x: number, y: number, opts?: TooltipShowOptions): void;\n /**\n * Re-anchor the open tooltip without re-rendering its content.\n *\n * The 3D graph re-anchors on every engine tick and every camera change, and\n * `show()` would re-run the host formatter and rewrite the DOM at frame rate\n * for content that has not changed.\n */\n move(x: number, y: number, opts?: TooltipShowOptions): void;\n /** Hide the tooltip. */\n hide(): void;\n /** Remove the tooltip element and clean up event listeners. */\n destroy(): void;\n}\n\nconst TOOLTIP_OFFSET = 12;\n\n/**\n * Create a tooltip manager attached to a container element.\n *\n * The manager creates a floating div positioned relative to the container.\n * Content is rendered as a title line with optional color indicator,\n * followed by a compact list of field-value pairs.\n *\n * @param container - The parent element for the tooltip.\n * @returns TooltipManager with show/hide/destroy methods.\n */\nexport function createTooltipManager(container: HTMLElement): TooltipManager {\n const tooltip = document.createElement('div');\n tooltip.className = 'oc-tooltip';\n tooltip.setAttribute('role', 'tooltip');\n\n container.style.position = container.style.position || 'relative';\n container.appendChild(tooltip);\n\n // Track last content to skip innerHTML when only position changes\n let lastContentKey = '';\n // Generation counter to discard stale async position callbacks\n let currentPositionId = 0;\n\n // Hide on tap-outside for touch devices\n const handleDocumentTouch = (e: Event): void => {\n if (!container.contains(e.target as Node)) {\n hide();\n }\n };\n document.addEventListener('touchstart', handleDocumentTouch);\n\n // WCAG 1.4.13: hover-triggered content must be dismissible without moving\n // the pointer. Document-level so it works while hovering, when the chart\n // container does not have keyboard focus.\n const handleDocumentKeydown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape' && tooltip.style.display === 'block') {\n hide();\n }\n };\n document.addEventListener('keydown', handleDocumentKeydown);\n\n function show(input: TooltipInput, x: number, y: number, opts?: TooltipShowOptions): void {\n // Custom-element form: trusted verbatim (host owns sanitization).\n if ('element' in input) {\n lastContentKey = '';\n tooltip.replaceChildren(input.element);\n position(x, y, opts);\n return;\n }\n // Plain-text form: escaped via textContent, never innerHTML.\n if ('text' in input) {\n lastContentKey = '';\n tooltip.textContent = input.text;\n position(x, y, opts);\n return;\n }\n\n const content = input;\n // Fast content identity check: title + field count + first/last field values\n // Includes the emphasized row: moving the pointer between series at the\n // same x changes only which row is bold, and the fast path would skip it.\n const emphasisIndex = content.fields.findIndex((f) => f.emphasis);\n const contentKey = `${content.title}|${content.fields.length}|${content.fields[0]?.value}|${content.fields[content.fields.length - 1]?.value}|${emphasisIndex}`;\n\n if (contentKey !== lastContentKey) {\n lastContentKey = contentKey;\n\n let html = '';\n\n // Multi-series tooltips put a swatch on each row instead of in the\n // header, so detect that case once.\n const colorRowCount = content.fields.filter((f) => f.color && f.role !== 'total').length;\n const perRowSwatches = colorRowCount > 1;\n\n // Title row: header dot only when there's a single color (single-series)\n if (content.title) {\n const titleColor = perRowSwatches ? undefined : content.fields.find((f) => f.color)?.color;\n html += '<div class=\"oc-tooltip-header\">';\n if (titleColor) {\n html += `<span class=\"oc-tooltip-dot\" style=\"background:${esc(titleColor)}\"></span>`;\n }\n html += `<span class=\"oc-tooltip-title\">${esc(content.title)}</span>`;\n html += '</div>';\n }\n\n // Field rows\n if (content.fields.length > 0) {\n html += '<div class=\"oc-tooltip-body\">';\n for (const field of content.fields) {\n let rowClass = 'oc-tooltip-row';\n if (field.emphasis) rowClass += ' oc-tooltip-row--emphasis';\n if (field.role === 'total') rowClass += ' oc-tooltip-row--total';\n html += `<div class=\"${rowClass}\">`;\n html += '<span class=\"oc-tooltip-label\">';\n if (perRowSwatches && field.color) {\n html += `<span class=\"oc-tooltip-row-swatch\" style=\"background:${esc(field.color)}\"></span>`;\n }\n html += `${esc(field.label)}</span>`;\n html += `<span class=\"oc-tooltip-value\">${esc(field.value)}</span>`;\n html += '</div>';\n }\n html += '</div>';\n }\n\n tooltip.innerHTML = html;\n }\n\n position(x, y, opts);\n }\n\n /** Show and position the tooltip at a container-relative coordinate. */\n function position(x: number, y: number, opts?: TooltipShowOptions): void {\n tooltip.style.display = 'block';\n\n // Position with viewport-aware edge avoidance via @floating-ui/dom.\n // Uses a virtual element since the reference point is a coordinate,\n // not a DOM element.\n const positionId = ++currentPositionId;\n const virtualRef = {\n getBoundingClientRect() {\n const rect = container.getBoundingClientRect();\n return {\n x: rect.left + x,\n y: rect.top + y,\n width: 0,\n height: 0,\n top: rect.top + y,\n left: rect.left + x,\n right: rect.left + x,\n bottom: rect.top + y,\n };\n },\n };\n\n computePosition(virtualRef, tooltip, {\n placement: opts?.placement ?? 'bottom-start',\n middleware: [offset(TOOLTIP_OFFSET), flip(), shift({ padding: 8 })],\n }).then(({ x: fx, y: fy }) => {\n // Discard stale callbacks from earlier show() calls\n if (positionId !== currentPositionId) return;\n // computePosition returns coordinates relative to the tooltip's offset\n // parent (the container with position: relative), so apply directly.\n tooltip.style.left = `${fx}px`;\n tooltip.style.top = `${fy}px`;\n });\n }\n\n /** Re-anchor the tooltip at a new coordinate, leaving its content alone. */\n function move(x: number, y: number, opts?: TooltipShowOptions): void {\n // `position()` unhides the element, so moving a hidden tooltip would put\n // whatever content was last rendered back on screen. A move only ever\n // re-anchors something already visible.\n if (tooltip.style.display !== 'block') return;\n position(x, y, opts);\n }\n\n function hide(): void {\n tooltip.style.display = 'none';\n lastContentKey = '';\n }\n\n function destroy(): void {\n document.removeEventListener('touchstart', handleDocumentTouch);\n document.removeEventListener('keydown', handleDocumentKeydown);\n if (tooltip.parentNode) {\n tooltip.parentNode.removeChild(tooltip);\n }\n }\n\n return { show, move, hide, destroy };\n}\n\nfunction esc(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","/**\n * Shared easing and interpolation primitives.\n *\n * Single source of truth for the hand-rolled easings used by the canvas\n * scheduler (motion/tween.ts), the update-transition driver (transition.ts)\n * and the story tweens (story/tween.ts). No `d3-ease` dependency.\n */\n\n/** Linear (identity) easing. Required for stacked/segmented handoffs. */\nexport function linear(t: number): number {\n return t;\n}\n\n/** Cubic-out: decelerates into place. Maps to `'snappy'`. */\nexport function cubicOut(t: number): number {\n const f = 1 - t;\n return 1 - f * f * f;\n}\n\n/** Cubic-in-out: eases in and out. Maps to `'smooth'`. */\nexport function cubicInOut(t: number): number {\n return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;\n}\n\nexport const clamp01 = (t: number): number => Math.min(1, Math.max(0, t));\n\nexport const lerp = (a: number, b: number, t: number): number => a + (b - a) * t;\n","import type { DarkMode } from '@opendata-ai/openchart-core';\n\n/**\n * Resolve a DarkMode option to a boolean. 'auto' consults the OS preference\n * via matchMedia; environments without matchMedia (SSR, happy-dom) resolve\n * to light.\n */\nexport function resolveDarkMode(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n if (mode === 'off' || mode === undefined) return false;\n if (typeof window !== 'undefined' && window.matchMedia) {\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n return false;\n}\n","/**\n * Canvas animation scheduler.\n *\n * Owns the set of running rAF-driven animations and the first-frame arming.\n * The mount's render loop ticks the scheduler once per frame and re-arms rAF\n * only while animations are active — a continuous loop that costs nothing when\n * idle (the base render loop stays strictly dirty-flag).\n *\n * Shared by the graph canvas and the scatter canvas mark layer.\n *\n * Two hazards this design guards against:\n *\n * 1. **First-frame arming is the scheduler's job, not the call site's.** `add()`\n * invokes the injected `requestFrame` on the idle→active transition, so no\n * call site can forget to arm the first frame.\n *\n * 2. **Reentrancy.** A tween's `onDone` may call `update()` / `destroy()`, which\n * trigger `finishAll` / `cancelAll` mid-tick. `tick()` iterates a SNAPSHOT of\n * the animation list; `remove` / `cancelAll` are safe to call mid-tick.\n */\n\n/** A single rAF-driven animation. */\nexport interface Animation {\n /** Advance to `now` (ms). Returns true while running, false when complete. */\n tick(now: number): boolean;\n /** Jump to the final state and fire completion callbacks. */\n finish(): void;\n /** Stop immediately without applying the final state or firing completion. */\n cancel(): void;\n}\n\n/** Manages a set of active animations and drives frame arming. */\nexport class AnimationScheduler {\n private animations = new Set<Animation>();\n private readonly requestFrame: () => void;\n\n /**\n * @param requestFrame Called on the idle→active transition to arm the first\n * frame (typically the mount's `scheduleRender`).\n */\n constructor(requestFrame: () => void) {\n this.requestFrame = requestFrame;\n }\n\n /** Add an animation. Arms the first frame if the scheduler was idle. */\n add(a: Animation): void {\n const wasIdle = this.animations.size === 0;\n this.animations.add(a);\n if (wasIdle) this.requestFrame();\n }\n\n /** Remove an animation without finishing or cancelling it. Safe mid-tick. */\n remove(a: Animation): void {\n this.animations.delete(a);\n }\n\n /**\n * Advance all animations to `now`. Completed animations are removed. Returns\n * true if any animation ran this frame (so the caller marks the frame dirty).\n *\n * Iterates a snapshot so an `onDone` that mutates the set (via update/destroy)\n * can't corrupt the loop.\n */\n tick(now: number): boolean {\n if (this.animations.size === 0) return false;\n const snapshot = [...this.animations];\n let ran = false;\n for (const a of snapshot) {\n // An earlier animation's onDone may have removed this one mid-loop.\n if (!this.animations.has(a)) continue;\n ran = true;\n const running = a.tick(now);\n if (!running) this.animations.delete(a);\n }\n return ran;\n }\n\n /** True while any animation is active (drives rAF re-arming). */\n get active(): boolean {\n return this.animations.size > 0;\n }\n\n /** Cancel every animation (no final state, no onDone). Used on teardown. */\n cancelAll(): void {\n const snapshot = [...this.animations];\n this.animations.clear();\n for (const a of snapshot) a.cancel();\n }\n\n /** Finish every animation (snap to final, fire onDone). Used before update(). */\n finishAll(): void {\n const snapshot = [...this.animations];\n this.animations.clear();\n for (const a of snapshot) a.finish();\n }\n}\n","/**\n * Canvas motion primitives: easings, reduced-motion detection, and a generic\n * time-based tween.\n *\n * Canvas marks animate via a rAF scheduler (see scheduler.ts), NOT via CSS\n * keyframes like SVG charts. Only the easing vocabulary is shared with charts\n * (`'smooth'` / `'snappy'`). No `d3-ease` dependency — easings are hand-rolled,\n * matching `transition.ts`'s `cubicOut`.\n */\n\nimport type { AnimationEase } from '@opendata-ai/openchart-core';\nimport { cubicInOut, cubicOut } from './easing';\nimport type { Animation } from './scheduler';\n\n// ---------------------------------------------------------------------------\n// Easings (shared definitions live in easing.ts; re-exported for callers)\n// ---------------------------------------------------------------------------\n\nexport { cubicInOut, cubicOut, linear } from './easing';\n\n/** Resolve a named ease preset to an easing function. */\nexport function resolveEase(ease: AnimationEase): (t: number) => number {\n return ease === 'snappy' ? cubicOut : cubicInOut;\n}\n\n// ---------------------------------------------------------------------------\n// Reduced motion\n// ---------------------------------------------------------------------------\n\n/**\n * Whether the user prefers reduced motion. Mirrors `transition.ts`: guards\n * SSR/happy-dom where matchMedia may be missing or throw.\n */\nexport function prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;\n try {\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tween\n// ---------------------------------------------------------------------------\n\n/** Options for {@link createTween}. */\nexport interface TweenOptions {\n /** Duration in ms. A duration <= 0 completes on the first tick. */\n duration: number;\n /** Easing function applied to the normalized 0..1 progress. */\n ease: (t: number) => number;\n /** Called each tick with the eased progress. */\n apply: (t: number) => void;\n /** Called once when the tween completes (not on cancel). */\n onDone?: () => void;\n}\n\n/**\n * Create a time-based tween as an {@link Animation}.\n *\n * The start time locks on the FIRST `tick(now)` call (not at construction), so\n * a tween added while the scheduler is idle starts cleanly on the next frame\n * and tests get deterministic progress from a controllable clock.\n */\nexport function createTween(opts: TweenOptions): Animation {\n const { duration, ease, apply, onDone } = opts;\n let startTime: number | null = null;\n let finished = false;\n\n function settleAt(t: number): void {\n apply(ease(t));\n }\n\n return {\n tick(now: number): boolean {\n if (finished) return false;\n if (startTime === null) startTime = now;\n const elapsed = now - startTime;\n const raw = duration <= 0 ? 1 : Math.min(1, elapsed / duration);\n settleAt(raw);\n if (raw >= 1) {\n finished = true;\n onDone?.();\n return false;\n }\n return true;\n },\n finish(): void {\n if (finished) return;\n finished = true;\n settleAt(1);\n onDone?.();\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n","/**\n * Spatial index for fast node hit-testing.\n *\n * Wraps d3-quadtree to provide findNearest (accounts for node radius)\n * and findInRect queries. Tracks a generation counter to avoid unnecessary\n * rebuilds when positions haven't changed.\n */\n\nimport { type Quadtree, quadtree } from 'd3-quadtree';\n\n/** Minimum shape a spatial-index entry must have. */\nexport interface SpatialEntry {\n x: number;\n y: number;\n radius: number;\n}\n\nexport class SpatialIndex<T extends SpatialEntry = SpatialEntry> {\n private tree: Quadtree<T> | null = null;\n private nodes: T[] = [];\n private maxRadius = 0;\n private generation = 0;\n\n /** Rebuild the quadtree from the current set of positioned nodes. */\n rebuild(nodes: T[]): void {\n this.nodes = nodes;\n this.maxRadius = 0;\n for (const n of nodes) {\n if (n.radius > this.maxRadius) this.maxRadius = n.radius;\n }\n\n this.tree = quadtree<T>()\n .x((d) => d.x)\n .y((d) => d.y)\n .addAll(nodes);\n this.generation++;\n }\n\n /** Current generation counter. Increments on each rebuild. */\n getGeneration(): number {\n return this.generation;\n }\n\n /**\n * Find the nearest node to (x, y) within maxDistance.\n * Accounts for node radius: a hit occurs if the point is inside\n * the node circle (distance to center < node.radius), or if the\n * edge-to-edge distance is within maxDistance.\n */\n findNearest(x: number, y: number, maxDistance: number = Infinity): T | null {\n if (!this.tree || this.nodes.length === 0) return null;\n\n // The effective search radius for the quadtree needs to include\n // the largest node radius, because we might be \"inside\" a large node\n // whose center is far from our search point.\n const searchRadius = maxDistance + this.maxRadius;\n\n let best: T | null = null;\n let bestEffectiveDist = maxDistance + this.maxRadius + 1;\n\n this.tree.visit((node, x0, y0, x1, y1) => {\n // Closest point in this quad to our target\n const closestX = Math.max(x0, Math.min(x, x1));\n const closestY = Math.max(y0, Math.min(y, y1));\n const quadDist = Math.hypot(closestX - x, closestY - y);\n\n // Prune: if the closest edge of this quad is beyond searchRadius, skip\n if (quadDist > searchRadius) return true;\n\n // Check leaf data\n if (!node.length) {\n let current = node;\n do {\n const d = current.data;\n if (d) {\n const dist = Math.hypot(d.x - x, d.y - y);\n // Effective distance: subtract the node's radius.\n // If we're inside the circle, effective distance is 0.\n const effectiveDist = Math.max(0, dist - d.radius);\n if (effectiveDist <= maxDistance && effectiveDist < bestEffectiveDist) {\n bestEffectiveDist = effectiveDist;\n best = d;\n }\n }\n } while ((current = current.next!));\n }\n\n return false;\n });\n\n return best;\n }\n\n /** Find all nodes whose centers fall within the given rectangle. */\n findInRect(x1: number, y1: number, x2: number, y2: number): T[] {\n if (!this.tree) return [];\n\n const minX = Math.min(x1, x2);\n const minY = Math.min(y1, y2);\n const maxX = Math.max(x1, x2);\n const maxY = Math.max(y1, y2);\n\n const results: T[] = [];\n\n this.tree.visit((node, qx0, qy0, qx1, qy1) => {\n // If quad doesn't overlap the search rect, skip\n if (qx0 > maxX || qx1 < minX || qy0 > maxY || qy1 < minY) {\n return true;\n }\n\n // Check leaf nodes\n if (!node.length) {\n let current = node;\n do {\n const d = current.data;\n if (d && d.x >= minX && d.x <= maxX && d.y >= minY && d.y <= maxY) {\n results.push(d);\n }\n } while ((current = current.next!));\n }\n\n return false;\n });\n\n return results;\n }\n}\n"],"mappings":"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","names":[]}
|