@opendata-ai/openchart-vanilla 8.0.0-rc.2 → 8.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-W34KN7FD.js → chunk-2NAQRP66.js} +2 -2
- package/dist/chunk-DCODCLWI.js +234 -0
- package/dist/chunk-DCODCLWI.js.map +1 -0
- package/dist/{chunk-5EOV5JQZ.js → chunk-EKAUQAAX.js} +50 -46
- package/dist/chunk-EKAUQAAX.js.map +1 -0
- package/dist/{chunk-ENXSUXC5.js → chunk-KNT5AZ2O.js} +7309 -5490
- package/dist/chunk-KNT5AZ2O.js.map +1 -0
- package/dist/chunk-RM5XS6NR.js +113 -0
- package/dist/chunk-RM5XS6NR.js.map +1 -0
- package/dist/chunk-T32UHMZA.js +282 -0
- package/dist/chunk-T32UHMZA.js.map +1 -0
- package/dist/export-gif.d.ts +113 -0
- package/dist/export-gif.js +21 -0
- package/dist/export-gif.js.map +1 -0
- package/dist/index.d.ts +283 -24
- package/dist/index.js +2012 -763
- package/dist/index.js.map +1 -1
- package/dist/{mount-CUKQLeqD.d.ts → mount-Dvv8NYIv.d.ts} +117 -3
- package/dist/simulation-worker.js +83 -2
- package/dist/static.js +7 -69
- package/dist/static.js.map +1 -1
- package/dist/story/index.d.ts +3 -2
- package/dist/story/index.js +5 -3
- package/dist/story/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +14 -6
- package/dist/chunk-5EOV5JQZ.js.map +0 -1
- package/dist/chunk-ENXSUXC5.js.map +0 -1
- package/src/__test-fixtures__/dom.ts +0 -70
- package/src/__test-fixtures__/specs.ts +0 -214
- package/src/__tests__/animation.test.ts +0 -358
- package/src/__tests__/annotations-render.test.ts +0 -201
- package/src/__tests__/compound-labels.test.ts +0 -122
- package/src/__tests__/continuous-legend.test.ts +0 -91
- package/src/__tests__/crosshair.test.ts +0 -130
- package/src/__tests__/edit-events.test.ts +0 -902
- package/src/__tests__/events.test.ts +0 -387
- package/src/__tests__/export.test.ts +0 -241
- package/src/__tests__/font-race.test.ts +0 -199
- package/src/__tests__/gradient-ids.test.ts +0 -159
- package/src/__tests__/gradient-utils.test.ts +0 -125
- package/src/__tests__/known-bugs-render.test.ts +0 -183
- package/src/__tests__/map.test.ts +0 -293
- package/src/__tests__/mount.test.ts +0 -357
- package/src/__tests__/pattern-fills.test.ts +0 -105
- package/src/__tests__/resize-timing.test.ts +0 -218
- package/src/__tests__/sankey.test.ts +0 -133
- package/src/__tests__/selection-events.test.ts +0 -869
- package/src/__tests__/series-search.test.ts +0 -311
- package/src/__tests__/static.test.ts +0 -141
- package/src/__tests__/svg-renderer.test.ts +0 -895
- package/src/__tests__/table-keyboard.test.ts +0 -361
- package/src/__tests__/table-mount.test.ts +0 -639
- package/src/__tests__/tilemap.test.ts +0 -158
- package/src/__tests__/tooltip.test.ts +0 -337
- package/src/__tests__/transition.test.ts +0 -1777
- package/src/__tests__/you-draw-it.test.ts +0 -273
- package/src/animation-vars.ts +0 -42
- package/src/animation.ts +0 -76
- package/src/barlist-mount.ts +0 -383
- package/src/barlist-renderer.ts +0 -267
- package/src/dom-helpers.ts +0 -17
- package/src/export.ts +0 -482
- package/src/gradient-utils.ts +0 -138
- package/src/graph/__tests__/canvas-renderer.test.ts +0 -713
- package/src/graph/__tests__/graph-mount.test.ts +0 -213
- package/src/graph/__tests__/interaction.test.ts +0 -205
- package/src/graph/__tests__/keyboard.test.ts +0 -653
- package/src/graph/__tests__/search.test.ts +0 -88
- package/src/graph/__tests__/simulation.test.ts +0 -233
- package/src/graph/__tests__/spatial-index.test.ts +0 -142
- package/src/graph/__tests__/zoom.test.ts +0 -203
- package/src/graph/canvas-renderer.ts +0 -747
- package/src/graph/interaction.ts +0 -367
- package/src/graph/keyboard.ts +0 -208
- package/src/graph/search.ts +0 -50
- package/src/graph/simulation-worker-url.ts +0 -12
- package/src/graph/simulation-worker.ts +0 -278
- package/src/graph/simulation.ts +0 -422
- package/src/graph/spatial-index.ts +0 -121
- package/src/graph/types.ts +0 -47
- package/src/graph/worker-protocol.ts +0 -70
- package/src/graph/zoom.ts +0 -134
- package/src/graph-mount.ts +0 -888
- package/src/index.ts +0 -76
- package/src/interactions/chart-events.ts +0 -139
- package/src/interactions/crosshair.ts +0 -233
- package/src/interactions/drag-handler.ts +0 -175
- package/src/interactions/editing-drags.ts +0 -515
- package/src/interactions/index.ts +0 -25
- package/src/interactions/keyboard-nav.ts +0 -111
- package/src/interactions/legend-interaction.ts +0 -46
- package/src/interactions/selection.ts +0 -280
- package/src/interactions/tooltip-events.ts +0 -72
- package/src/map-camera.ts +0 -88
- package/src/map-mount.ts +0 -701
- package/src/map-renderer.ts +0 -338
- package/src/map-transition.ts +0 -83
- package/src/measure-text.ts +0 -134
- package/src/mount.ts +0 -1382
- package/src/pattern-utils.ts +0 -110
- package/src/renderers/annotations.ts +0 -388
- package/src/renderers/axes.ts +0 -351
- package/src/renderers/brand.ts +0 -87
- package/src/renderers/chrome.ts +0 -196
- package/src/renderers/endpoint-labels.ts +0 -150
- package/src/renderers/legend.ts +0 -311
- package/src/renderers/marks.ts +0 -561
- package/src/renderers/metrics.ts +0 -63
- package/src/renderers/svg-dom.ts +0 -40
- package/src/renderers/table-cells.ts +0 -446
- package/src/resize-observer.ts +0 -46
- package/src/sankey-mount.ts +0 -622
- package/src/sankey-renderer.ts +0 -614
- package/src/series-search.ts +0 -392
- package/src/static.ts +0 -251
- package/src/story/__tests__/camera-math.test.ts +0 -145
- package/src/story/__tests__/create-chart-story.test.ts +0 -317
- package/src/story/__tests__/progress-math.test.ts +0 -95
- package/src/story/__tests__/scroll-driver.test.ts +0 -130
- package/src/story/__tests__/tween.test.ts +0 -159
- package/src/story/camera-math.ts +0 -125
- package/src/story/create-chart-story.ts +0 -353
- package/src/story/crossfade.ts +0 -70
- package/src/story/index.ts +0 -39
- package/src/story/progress-math.ts +0 -85
- package/src/story/resolve-camera-target.ts +0 -119
- package/src/story/scroll-driver.ts +0 -206
- package/src/story/story-camera.ts +0 -49
- package/src/story/tween.ts +0 -112
- package/src/story/types.ts +0 -76
- package/src/svg-ids.ts +0 -23
- package/src/svg-renderer.ts +0 -383
- package/src/table-keyboard.ts +0 -266
- package/src/table-mount.ts +0 -567
- package/src/table-renderer.ts +0 -414
- package/src/text-edit-overlay.ts +0 -255
- package/src/theme-tokens.ts +0 -51
- package/src/tilemap-mount.ts +0 -511
- package/src/tilemap-renderer.ts +0 -396
- package/src/tooltip.ts +0 -177
- package/src/transition.ts +0 -2816
- package/src/you-draw-it.ts +0 -480
- /package/dist/{chunk-W34KN7FD.js.map → chunk-2NAQRP66.js.map} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChartSpec, LayerSpec, GraphSpec, ElementRef, ChartLayout, ChartEventHandlers, ThemeConfig, DarkMode, DataRow } from '@opendata-ai/openchart-core';
|
|
1
|
+
import { ResolvedTheme, ChartSpec, LayerSpec, GraphSpec, ElementRef, ChartLayout, ChartEventHandlers, ThemeConfig, DarkMode, DataRow } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { GIFExportOptions } from './export-gif.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Export utilities: serialize charts to SVG, PNG, JPG, or CSV.
|
|
@@ -9,9 +10,17 @@ import { ChartSpec, LayerSpec, GraphSpec, ElementRef, ChartLayout, ChartEventHan
|
|
|
9
10
|
* - JPG: same as PNG but with JPEG compression and background fill
|
|
10
11
|
* - CSV: converts a data array to comma-separated text
|
|
11
12
|
*/
|
|
13
|
+
|
|
12
14
|
interface SVGExportOptions {
|
|
13
15
|
/** Embed fonts as base64 data URIs in the SVG. Defaults to true. */
|
|
14
16
|
embedFonts?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Resolved theme to inline as a `<style>` block, so class-based fills (metric
|
|
19
|
+
* cells, brand watermark dot, legend text, endpoint labels) survive
|
|
20
|
+
* serialization away from the page stylesheet. Omit to skip — a chart whose
|
|
21
|
+
* chrome is all inline-filled exports fine without it.
|
|
22
|
+
*/
|
|
23
|
+
theme?: ResolvedTheme;
|
|
15
24
|
}
|
|
16
25
|
interface PNGExportOptions extends SVGExportOptions {
|
|
17
26
|
/** DPI scaling factor. Defaults to 2 for retina-quality output. */
|
|
@@ -133,6 +142,103 @@ declare function camerasClose(a: Camera, b: Camera, epsilon?: number): boolean;
|
|
|
133
142
|
*/
|
|
134
143
|
declare function scrubCamera(step: number, stepProgress: number, fitted: Camera[], holdUntil: number, ease: (t: number) => number): Camera;
|
|
135
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Data-update transition driver for bar/column, line, area, scatter/dot,
|
|
147
|
+
* rule, tick, and text charts. Also transitions axis ticks, tick labels,
|
|
148
|
+
* and gridlines so the whole chart adjusts as one system.
|
|
149
|
+
*
|
|
150
|
+
* Matches marks across layout snapshots by `key`, then animates geometry
|
|
151
|
+
* changes (position, size, path) using a single rAF loop. Enter/exit/update
|
|
152
|
+
* choreography runs in one timeline with cubic-out easing for a smooth
|
|
153
|
+
* deceleration feel.
|
|
154
|
+
*
|
|
155
|
+
* For rect marks: tweens position/size directly.
|
|
156
|
+
* For line/area marks: normalizes point arrays to equal length, interpolates
|
|
157
|
+
* point positions per frame, and rebuilds SVG paths via d3 curve generators.
|
|
158
|
+
* For point marks (scatter/dot/line overlay): tweens cx/cy/r.
|
|
159
|
+
* For rule/tick marks: tweens line endpoints (x1/y1/x2/y2).
|
|
160
|
+
* For text marks: tweens x/y position.
|
|
161
|
+
* For axis ticks/gridlines: matches by value key and tweens positions.
|
|
162
|
+
*
|
|
163
|
+
* Ghost elements handle exits: cloned into the marks container, they
|
|
164
|
+
* collapse + fade out then get removed. No CSS animations are used here
|
|
165
|
+
* because transitions tween computed geometry, not class-toggled states.
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Snapshot of in-flight tween geometry, keyed by mark key.
|
|
170
|
+
* Used for interruption retargeting: the next transition starts from
|
|
171
|
+
* whatever position the previous transition had reached.
|
|
172
|
+
*/
|
|
173
|
+
type GeometrySnapshot = Map<string, SnapshotGeometry>;
|
|
174
|
+
type SnapshotGeometry = {
|
|
175
|
+
type: 'rect';
|
|
176
|
+
x: number;
|
|
177
|
+
y: number;
|
|
178
|
+
width: number;
|
|
179
|
+
height: number;
|
|
180
|
+
} | {
|
|
181
|
+
type: 'point';
|
|
182
|
+
cx: number;
|
|
183
|
+
cy: number;
|
|
184
|
+
r?: number;
|
|
185
|
+
} | {
|
|
186
|
+
type: 'rule';
|
|
187
|
+
x1: number;
|
|
188
|
+
y1: number;
|
|
189
|
+
x2: number;
|
|
190
|
+
y2: number;
|
|
191
|
+
} | {
|
|
192
|
+
type: 'tick';
|
|
193
|
+
x1: number;
|
|
194
|
+
y1: number;
|
|
195
|
+
x2: number;
|
|
196
|
+
y2: number;
|
|
197
|
+
} | {
|
|
198
|
+
type: 'textMark';
|
|
199
|
+
x: number;
|
|
200
|
+
y: number;
|
|
201
|
+
} | {
|
|
202
|
+
type: 'line';
|
|
203
|
+
d: string;
|
|
204
|
+
} | {
|
|
205
|
+
type: 'area';
|
|
206
|
+
d: string;
|
|
207
|
+
topD?: string;
|
|
208
|
+
};
|
|
209
|
+
interface TransitionHandle {
|
|
210
|
+
cancel(): void;
|
|
211
|
+
readonly running: boolean;
|
|
212
|
+
/** Capture current interpolated geometry for all in-flight tweens. */
|
|
213
|
+
snapshot(): GeometrySnapshot;
|
|
214
|
+
/**
|
|
215
|
+
* Apply the transition's DOM state at an explicit elapsed time (ms), without
|
|
216
|
+
* the rAF clock. Only meaningful when the transition was started with
|
|
217
|
+
* `manual: true` (which suppresses the internal rAF loop). Used by headless
|
|
218
|
+
* frame capture (GIF export) to sample the tween deterministically at each
|
|
219
|
+
* frame's timestamp. Returns true while the transition is still in progress
|
|
220
|
+
* (elapsed < total), false once it has reached its end.
|
|
221
|
+
*/
|
|
222
|
+
step(elapsedMs: number): boolean;
|
|
223
|
+
/** The transition's total duration in ms (update/exit, whichever is longer). */
|
|
224
|
+
readonly totalMs: number;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Default cap on the mark count that still runs a tweened data-update
|
|
228
|
+
* transition. Override per chart with `animation.update.maxMarks`.
|
|
229
|
+
*/
|
|
230
|
+
declare const DEFAULT_UPDATE_MAX_MARKS = 500;
|
|
231
|
+
/**
|
|
232
|
+
* Default cap when the destination layout renders its points on canvas.
|
|
233
|
+
*
|
|
234
|
+
* Two orders of magnitude above the SVG cap because the cost model is
|
|
235
|
+
* different: a canvas frame writes into typed arrays and issues one batched
|
|
236
|
+
* fill per color bucket, where the SVG path writes attributes on one DOM
|
|
237
|
+
* element per mark. The cap that keeps low-end devices honest for DOM writes
|
|
238
|
+
* would veto exactly the high-cardinality morphs canvas mode exists to serve.
|
|
239
|
+
*/
|
|
240
|
+
declare const CANVAS_DEFAULT_UPDATE_MAX_MARKS = 20000;
|
|
241
|
+
|
|
136
242
|
/**
|
|
137
243
|
* Mount API: the main entry point for vanilla JS usage.
|
|
138
244
|
*
|
|
@@ -165,6 +271,13 @@ interface ExportOptions extends JPGExportOptions {
|
|
|
165
271
|
interface ChartInstance {
|
|
166
272
|
/** Re-compile and re-render with a new spec. */
|
|
167
273
|
update(spec: ChartSpec | LayerSpec | GraphSpec, options?: UpdateOptions): void;
|
|
274
|
+
/**
|
|
275
|
+
* Re-render with a new spec and start a MANUAL update transition the caller
|
|
276
|
+
* drives via `handle.step(elapsedMs)`, with no rAF clock. Returns the handle,
|
|
277
|
+
* or null when no transition applies (the render already swapped instantly).
|
|
278
|
+
* For headless frame capture (e.g. GIF export of a spec-swap stepper).
|
|
279
|
+
*/
|
|
280
|
+
beginManualUpdate(spec: ChartSpec | LayerSpec | GraphSpec): TransitionHandle | null;
|
|
168
281
|
/** Re-compile at current container dimensions. */
|
|
169
282
|
resize(): void;
|
|
170
283
|
/** Export the chart. */
|
|
@@ -172,8 +285,9 @@ interface ChartInstance {
|
|
|
172
285
|
export(format: 'svg-with-fonts', options?: SVGExportOptions): Promise<string>;
|
|
173
286
|
export(format: 'png', options?: ExportOptions): Promise<Blob>;
|
|
174
287
|
export(format: 'jpg', options?: ExportOptions): Promise<Blob>;
|
|
288
|
+
export(format: 'gif', options?: GIFExportOptions): Promise<Blob>;
|
|
175
289
|
export(format: 'csv'): string;
|
|
176
|
-
export(format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg' | 'csv', options?: ExportOptions): string | Promise<Blob> | Promise<string>;
|
|
290
|
+
export(format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg' | 'gif' | 'csv', options?: ExportOptions | GIFExportOptions): string | Promise<Blob> | Promise<string>;
|
|
177
291
|
/** Remove all DOM elements and disconnect observers. */
|
|
178
292
|
destroy(): void;
|
|
179
293
|
/** The current compiled layout (for hooks / debugging). */
|
|
@@ -204,4 +318,4 @@ interface ChartInstance {
|
|
|
204
318
|
*/
|
|
205
319
|
declare function createChart<TData extends DataRow = DataRow>(container: HTMLElement, spec: ChartSpec<TData> | LayerSpec<TData> | GraphSpec, options?: MountOptions): ChartInstance;
|
|
206
320
|
|
|
207
|
-
export { type Camera as C, type ExportOptions as E, FULL_VIEW as F, type JPGExportOptions as J, type MountOptions as M, type PNGExportOptions as P, type SVGExportOptions as S, type UpdateOptions as U, type ViewBoxSize as V,
|
|
321
|
+
export { type Camera as C, DEFAULT_UPDATE_MAX_MARKS as D, type ExportOptions as E, FULL_VIEW as F, type JPGExportOptions as J, type MountOptions as M, type PNGExportOptions as P, type SVGExportOptions as S, type UpdateOptions as U, type ViewBoxSize as V, CANVAS_DEFAULT_UPDATE_MAX_MARKS as a, type ChartInstance as b, createChart as c, exportJPG as d, exportCSV as e, exportPNG as f, exportSVG as g, exportSVGWithFonts as h, type CameraTarget as i, cameraTransform as j, camerasClose as k, damp as l, dampCamera as m, fitTarget as n, interpolateCamera as o, scrubCamera as s };
|
|
@@ -1101,9 +1101,40 @@
|
|
|
1101
1101
|
}
|
|
1102
1102
|
};
|
|
1103
1103
|
}
|
|
1104
|
+
function forceCursor(nodes, pointer) {
|
|
1105
|
+
return (alpha) => {
|
|
1106
|
+
if (!pointer.active || pointer.radius <= 0)
|
|
1107
|
+
return;
|
|
1108
|
+
const r2 = pointer.radius * pointer.radius;
|
|
1109
|
+
const k = pointer.strength * alpha;
|
|
1110
|
+
for (const node of nodes) {
|
|
1111
|
+
const dx = (node.x ?? 0) - pointer.x;
|
|
1112
|
+
const dy = (node.y ?? 0) - pointer.y;
|
|
1113
|
+
const dist2 = dx * dx + dy * dy;
|
|
1114
|
+
if (dist2 >= r2)
|
|
1115
|
+
continue;
|
|
1116
|
+
const dist = Math.sqrt(dist2) || 0.000001;
|
|
1117
|
+
const falloff = (pointer.radius - dist) / pointer.radius;
|
|
1118
|
+
const push = k * falloff / dist;
|
|
1119
|
+
node.vx = (node.vx ?? 0) + dx * push;
|
|
1120
|
+
node.vy = (node.vy ?? 0) + dy * push;
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1104
1124
|
var ctx = self;
|
|
1105
1125
|
var simulation = null;
|
|
1106
1126
|
var nodeMap = new Map;
|
|
1127
|
+
var pointer = { x: 0, y: 0, active: false, radius: 0, strength: 0 };
|
|
1128
|
+
var dragAlphaTarget = 0;
|
|
1129
|
+
var cursorAlphaTarget = 0;
|
|
1130
|
+
function syncAlphaTarget() {
|
|
1131
|
+
if (!simulation)
|
|
1132
|
+
return;
|
|
1133
|
+
simulation.alphaTarget(Math.max(dragAlphaTarget, cursorAlphaTarget));
|
|
1134
|
+
}
|
|
1135
|
+
function now2() {
|
|
1136
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1137
|
+
}
|
|
1107
1138
|
function post(msg) {
|
|
1108
1139
|
ctx.postMessage(msg);
|
|
1109
1140
|
}
|
|
@@ -1135,7 +1166,7 @@
|
|
|
1135
1166
|
linkForce.strength(config.linkStrength);
|
|
1136
1167
|
}
|
|
1137
1168
|
const padding = config.collisionPadding ?? 2;
|
|
1138
|
-
simulation = simulation_default(internalNodes).force("link", linkForce).force("charge", manyBody_default().strength(config.chargeStrength)).force("collide", collide_default().radius((d) => d.radius + padding)).force("gravityX", x_default2(0).strength(0.05)).force("gravityY", y_default2(0).strength(0.05)).alphaDecay(config.alphaDecay).velocityDecay(config.velocityDecay);
|
|
1169
|
+
simulation = simulation_default(internalNodes).force("link", linkForce).force("charge", manyBody_default().strength(config.chargeStrength)).force("collide", collide_default().radius((d) => d.radius + padding)).force("gravityX", x_default2(0).strength(0.05)).force("gravityY", y_default2(0).strength(0.05)).alphaDecay(config.alphaDecay).velocityDecay(config.velocityDecay).stop();
|
|
1139
1170
|
if (config.centerForce !== false) {
|
|
1140
1171
|
simulation.force("center", center_default(0, 0));
|
|
1141
1172
|
}
|
|
@@ -1143,6 +1174,28 @@
|
|
|
1143
1174
|
const clusterFn = forceCluster(internalNodes, config.clustering.strength);
|
|
1144
1175
|
simulation.force("cluster", clusterFn);
|
|
1145
1176
|
}
|
|
1177
|
+
pointer.active = false;
|
|
1178
|
+
pointer.radius = config.cursorRepulsion?.radius ?? 0;
|
|
1179
|
+
pointer.strength = config.cursorRepulsion?.strength ?? 0;
|
|
1180
|
+
dragAlphaTarget = 0;
|
|
1181
|
+
cursorAlphaTarget = 0;
|
|
1182
|
+
const cursorFn = forceCursor(internalNodes, pointer);
|
|
1183
|
+
simulation.force("cursor", cursorFn);
|
|
1184
|
+
if (config.initialAlpha != null) {
|
|
1185
|
+
simulation.alpha(config.initialAlpha);
|
|
1186
|
+
}
|
|
1187
|
+
const warmupTicks = config.warmupTicks ?? 0;
|
|
1188
|
+
if (warmupTicks > 0) {
|
|
1189
|
+
const budgetMs = config.warmupBudgetMs ?? 250;
|
|
1190
|
+
const start = now2();
|
|
1191
|
+
for (let i = 0;i < warmupTicks; i++) {
|
|
1192
|
+
simulation.tick();
|
|
1193
|
+
if (simulation.alpha() < simulation.alphaMin())
|
|
1194
|
+
break;
|
|
1195
|
+
if (now2() - start >= budgetMs)
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1146
1199
|
simulation.on("tick", () => {
|
|
1147
1200
|
post({
|
|
1148
1201
|
type: "positions",
|
|
@@ -1153,6 +1206,12 @@
|
|
|
1153
1206
|
simulation.on("end", () => {
|
|
1154
1207
|
post({ type: "settled" });
|
|
1155
1208
|
});
|
|
1209
|
+
post({
|
|
1210
|
+
type: "positions",
|
|
1211
|
+
nodes: packPositions(internalNodes),
|
|
1212
|
+
alpha: simulation.alpha()
|
|
1213
|
+
});
|
|
1214
|
+
simulation.restart();
|
|
1156
1215
|
break;
|
|
1157
1216
|
}
|
|
1158
1217
|
case "reheat": {
|
|
@@ -1167,6 +1226,11 @@
|
|
|
1167
1226
|
node.fx = msg.x;
|
|
1168
1227
|
node.fy = msg.y;
|
|
1169
1228
|
}
|
|
1229
|
+
if (msg.alphaTarget != null && simulation) {
|
|
1230
|
+
dragAlphaTarget = msg.alphaTarget;
|
|
1231
|
+
syncAlphaTarget();
|
|
1232
|
+
simulation.restart();
|
|
1233
|
+
}
|
|
1170
1234
|
break;
|
|
1171
1235
|
}
|
|
1172
1236
|
case "unpin": {
|
|
@@ -1175,11 +1239,28 @@
|
|
|
1175
1239
|
node.fx = null;
|
|
1176
1240
|
node.fy = null;
|
|
1177
1241
|
}
|
|
1178
|
-
if (
|
|
1242
|
+
if (msg.alphaTarget != null) {
|
|
1243
|
+
dragAlphaTarget = msg.alphaTarget;
|
|
1244
|
+
syncAlphaTarget();
|
|
1245
|
+
} else if (simulation && simulation.alpha() < 0.1) {
|
|
1179
1246
|
simulation.alpha(0.1).restart();
|
|
1180
1247
|
}
|
|
1181
1248
|
break;
|
|
1182
1249
|
}
|
|
1250
|
+
case "pointer": {
|
|
1251
|
+
if (pointer.radius <= 0)
|
|
1252
|
+
break;
|
|
1253
|
+
pointer.x = msg.x;
|
|
1254
|
+
pointer.y = msg.y;
|
|
1255
|
+
pointer.active = msg.active;
|
|
1256
|
+
cursorAlphaTarget = msg.active ? 0.03 : 0;
|
|
1257
|
+
if (simulation) {
|
|
1258
|
+
syncAlphaTarget();
|
|
1259
|
+
if (msg.active)
|
|
1260
|
+
simulation.restart();
|
|
1261
|
+
}
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1183
1264
|
case "drag": {
|
|
1184
1265
|
const node = nodeMap.get(msg.nodeId);
|
|
1185
1266
|
if (node) {
|
package/dist/static.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
renderTileMapSVG
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2NAQRP66.js";
|
|
4
4
|
import {
|
|
5
|
-
SVG_NS,
|
|
6
5
|
renderChartSVG,
|
|
7
6
|
resetSvgIdCounter
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EKAUQAAX.js";
|
|
8
|
+
import {
|
|
9
|
+
SVG_NS,
|
|
10
|
+
buildThemeStyleBlock
|
|
11
|
+
} from "./chunk-RM5XS6NR.js";
|
|
9
12
|
|
|
10
13
|
// src/static.ts
|
|
11
14
|
import { createRequire } from "module";
|
|
12
|
-
import {
|
|
13
|
-
adaptForLightLineStroke,
|
|
14
|
-
cssTokenDefault,
|
|
15
|
-
isLayerSpec,
|
|
16
|
-
isTileMapSpec
|
|
17
|
-
} from "@opendata-ai/openchart-core";
|
|
15
|
+
import { isLayerSpec, isTileMapSpec } from "@opendata-ai/openchart-core";
|
|
18
16
|
import { compileChart, compileLayer, compileTileMap } from "@opendata-ai/openchart-engine";
|
|
19
17
|
var esmRequire = createRequire(import.meta.url);
|
|
20
18
|
var cachedWindow;
|
|
@@ -34,66 +32,6 @@ function resolveStaticDarkMode(mode) {
|
|
|
34
32
|
if (mode === "force") return true;
|
|
35
33
|
return false;
|
|
36
34
|
}
|
|
37
|
-
function buildThemeStyleBlock(theme) {
|
|
38
|
-
const accent = theme.colors.categorical[0] ?? cssTokenDefault("--oc-accent", "light");
|
|
39
|
-
const bg = theme.colors.background === "transparent" ? theme.isDark ? cssTokenDefault("--oc-bg", "dark") : cssTokenDefault("--oc-bg", "light") : theme.colors.background;
|
|
40
|
-
const props = [
|
|
41
|
-
`--oc-font-family: ${theme.fonts.family}`,
|
|
42
|
-
`--oc-font-mono: ${theme.fonts.mono}`,
|
|
43
|
-
`--oc-title-size: ${theme.chrome.title.fontSize}px`,
|
|
44
|
-
`--oc-title-weight: ${theme.chrome.title.fontWeight}`,
|
|
45
|
-
`--oc-title-tracking: ${cssTokenDefault("--oc-title-tracking", "light")}`,
|
|
46
|
-
`--oc-subtitle-size: ${theme.chrome.subtitle.fontSize}px`,
|
|
47
|
-
`--oc-subtitle-weight: ${theme.chrome.subtitle.fontWeight}`,
|
|
48
|
-
`--oc-source-size: ${theme.chrome.source.fontSize}px`,
|
|
49
|
-
`--oc-source-weight: ${theme.chrome.source.fontWeight}`,
|
|
50
|
-
`--oc-body-size: ${theme.fonts.sizes.body}px`,
|
|
51
|
-
`--oc-eyebrow-size: ${theme.chrome.eyebrow.fontSize}px`,
|
|
52
|
-
`--oc-eyebrow-weight: ${theme.chrome.eyebrow.fontWeight}`,
|
|
53
|
-
`--oc-eyebrow-tracking: ${cssTokenDefault("--oc-eyebrow-tracking", "light")}`,
|
|
54
|
-
`--oc-bg: ${bg}`,
|
|
55
|
-
`--oc-text: ${theme.colors.text}`,
|
|
56
|
-
`--oc-text-muted: ${theme.colors.axis}`,
|
|
57
|
-
`--oc-text-faint: ${theme.isDark ? cssTokenDefault("--oc-text-faint", "dark") : cssTokenDefault("--oc-text-faint", "light")}`,
|
|
58
|
-
`--oc-gridline: ${theme.colors.gridline}`,
|
|
59
|
-
`--oc-axis: ${theme.colors.axis}`,
|
|
60
|
-
`--oc-border-radius: ${theme.borderRadius}px`,
|
|
61
|
-
`--oc-accent: ${accent}`,
|
|
62
|
-
`--oc-accent-strong: ${adaptForLightLineStroke(accent)}`,
|
|
63
|
-
`--oc-positive: ${theme.colors.positive}`,
|
|
64
|
-
`--oc-negative: ${theme.colors.negative}`,
|
|
65
|
-
`--oc-legend-text: ${theme.isDark ? cssTokenDefault("--oc-legend-text", "dark") : cssTokenDefault("--oc-legend-text", "light")}`,
|
|
66
|
-
`--oc-space-2: ${theme.spacing.chromeGap * 2}px`,
|
|
67
|
-
`--oc-space-4: ${theme.spacing.padding}px`
|
|
68
|
-
];
|
|
69
|
-
const rules = [
|
|
70
|
-
`svg.oc-chart { ${props.join("; ")}; }`,
|
|
71
|
-
`.oc-chrome { font-family: var(--oc-font-family); }`,
|
|
72
|
-
`.oc-eyebrow { font-size: var(--oc-eyebrow-size); font-weight: var(--oc-eyebrow-weight); letter-spacing: var(--oc-eyebrow-tracking); text-transform: uppercase; fill: var(--oc-accent); }`,
|
|
73
|
-
`.oc-title { font-size: var(--oc-title-size); font-weight: var(--oc-title-weight); letter-spacing: var(--oc-title-tracking); fill: var(--oc-text); }`,
|
|
74
|
-
`.oc-subtitle { font-size: var(--oc-subtitle-size); font-weight: var(--oc-subtitle-weight); fill: var(--oc-text-muted); }`,
|
|
75
|
-
`.oc-source, .oc-byline, .oc-footer { font-size: var(--oc-source-size); font-weight: var(--oc-source-weight); fill: var(--oc-text-muted); }`,
|
|
76
|
-
`.oc-brand { font-size: 11px; font-weight: 510; letter-spacing: 0.02em; fill: var(--oc-text-faint); }`,
|
|
77
|
-
`.oc-brand-dot { fill: var(--oc-accent); }`,
|
|
78
|
-
`.oc-eyebrow-dot { fill: var(--oc-accent); }`,
|
|
79
|
-
`.oc-metrics { font-family: var(--oc-font-family); }`,
|
|
80
|
-
`.oc-metric-label { font-size: 10px; font-weight: 510; letter-spacing: 0.08em; text-transform: uppercase; fill: var(--oc-text-muted); }`,
|
|
81
|
-
`.oc-metric-value { font-size: 22px; font-weight: 510; letter-spacing: -0.01em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,
|
|
82
|
-
`.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 510; }`,
|
|
83
|
-
`.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 510; }`,
|
|
84
|
-
`.oc-axis-tick-inline { font-size: 11px; font-weight: 400; fill: var(--oc-text-muted); }`,
|
|
85
|
-
`.oc-endpoint-labels { font-family: var(--oc-font-family); }`,
|
|
86
|
-
`.oc-endpoint-label { fill: var(--oc-endpoint-label-color, var(--oc-text)); }`,
|
|
87
|
-
`.oc-endpoint-value { fill: var(--oc-endpoint-value-color, var(--oc-text-muted)); }`,
|
|
88
|
-
`.oc-endpoint-leader { stroke: var(--oc-endpoint-leader-color, currentColor); }`,
|
|
89
|
-
`.oc-annotation-subtitle { fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted)); }`,
|
|
90
|
-
`.oc-metric-secondary { fill: var(--oc-positive); font-size: 12px; font-weight: 400; }`,
|
|
91
|
-
`.oc-legend { font-family: var(--oc-font-family); font-size: var(--oc-body-size); }`,
|
|
92
|
-
`.oc-legend-entry { cursor: default; }`,
|
|
93
|
-
`.oc-legend text { fill: var(--oc-legend-text); }`
|
|
94
|
-
];
|
|
95
|
-
return rules.join("\n");
|
|
96
|
-
}
|
|
97
35
|
function stripInteractiveElements(svg) {
|
|
98
36
|
const selectors = ["[data-voronoi-overlay]", "[data-crosshair]", "[data-snap-dots]"];
|
|
99
37
|
for (const selector of selectors) {
|
package/dist/static.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/static.ts"],"sourcesContent":["// Node.js only — do not import from browser code.\nimport { createRequire } from 'node:module';\nimport type {\n ChartLayout,\n ChartSpec,\n CompileOptions,\n DarkMode,\n LayerSpec,\n ResolvedTheme,\n ThemeConfig,\n TileMapSpec,\n} from '@opendata-ai/openchart-core';\nimport {\n adaptForLightLineStroke,\n cssTokenDefault,\n isLayerSpec,\n isTileMapSpec,\n} from '@opendata-ai/openchart-core';\nimport { compileChart, compileLayer, compileTileMap } from '@opendata-ai/openchart-engine';\nimport { SVG_NS } from './renderers/svg-dom';\nimport { resetSvgIdCounter } from './svg-ids';\nimport { renderChartSVG } from './svg-renderer';\nimport { renderTileMapSVG } from './tilemap-renderer';\n\nconst esmRequire = createRequire(import.meta.url);\n\nlet cachedWindow: typeof import('happy-dom').Window | undefined;\nfunction getHappyDomWindow(): typeof import('happy-dom').Window {\n if (cachedWindow) return cachedWindow;\n try {\n ({ Window: cachedWindow } = esmRequire('happy-dom') as typeof import('happy-dom'));\n } catch {\n throw new Error(\n \"renderStaticSVG requires 'happy-dom' as a peer dependency. Install it with: npm add happy-dom\",\n );\n }\n return cachedWindow;\n}\n\nlet rendering = false;\n\nexport interface StaticRenderOptions {\n width?: number;\n height?: number;\n theme?: ThemeConfig;\n /**\n * Dark mode setting. In static rendering `'auto'` resolves to light mode\n * since there is no `matchMedia` to query. Use `'force'` for dark output.\n */\n darkMode?: DarkMode;\n watermark?: boolean;\n}\n\nfunction resolveStaticDarkMode(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n return false;\n}\n\nfunction buildThemeStyleBlock(theme: ResolvedTheme): string {\n const accent = theme.colors.categorical[0] ?? cssTokenDefault('--oc-accent', 'light');\n const bg =\n theme.colors.background === 'transparent'\n ? theme.isDark\n ? cssTokenDefault('--oc-bg', 'dark')\n : cssTokenDefault('--oc-bg', 'light')\n : theme.colors.background;\n\n const props = [\n `--oc-font-family: ${theme.fonts.family}`,\n `--oc-font-mono: ${theme.fonts.mono}`,\n `--oc-title-size: ${theme.chrome.title.fontSize}px`,\n `--oc-title-weight: ${theme.chrome.title.fontWeight}`,\n `--oc-title-tracking: ${cssTokenDefault('--oc-title-tracking', 'light')}`,\n `--oc-subtitle-size: ${theme.chrome.subtitle.fontSize}px`,\n `--oc-subtitle-weight: ${theme.chrome.subtitle.fontWeight}`,\n `--oc-source-size: ${theme.chrome.source.fontSize}px`,\n `--oc-source-weight: ${theme.chrome.source.fontWeight}`,\n `--oc-body-size: ${theme.fonts.sizes.body}px`,\n `--oc-eyebrow-size: ${theme.chrome.eyebrow.fontSize}px`,\n `--oc-eyebrow-weight: ${theme.chrome.eyebrow.fontWeight}`,\n `--oc-eyebrow-tracking: ${cssTokenDefault('--oc-eyebrow-tracking', 'light')}`,\n `--oc-bg: ${bg}`,\n `--oc-text: ${theme.colors.text}`,\n `--oc-text-muted: ${theme.colors.axis}`,\n `--oc-text-faint: ${theme.isDark ? cssTokenDefault('--oc-text-faint', 'dark') : cssTokenDefault('--oc-text-faint', 'light')}`,\n `--oc-gridline: ${theme.colors.gridline}`,\n `--oc-axis: ${theme.colors.axis}`,\n `--oc-border-radius: ${theme.borderRadius}px`,\n `--oc-accent: ${accent}`,\n `--oc-accent-strong: ${adaptForLightLineStroke(accent)}`,\n `--oc-positive: ${theme.colors.positive}`,\n `--oc-negative: ${theme.colors.negative}`,\n `--oc-legend-text: ${theme.isDark ? cssTokenDefault('--oc-legend-text', 'dark') : cssTokenDefault('--oc-legend-text', 'light')}`,\n `--oc-space-2: ${theme.spacing.chromeGap * 2}px`,\n `--oc-space-4: ${theme.spacing.padding}px`,\n ];\n\n const rules = [\n `svg.oc-chart { ${props.join('; ')}; }`,\n `.oc-chrome { font-family: var(--oc-font-family); }`,\n `.oc-eyebrow { font-size: var(--oc-eyebrow-size); font-weight: var(--oc-eyebrow-weight); letter-spacing: var(--oc-eyebrow-tracking); text-transform: uppercase; fill: var(--oc-accent); }`,\n `.oc-title { font-size: var(--oc-title-size); font-weight: var(--oc-title-weight); letter-spacing: var(--oc-title-tracking); fill: var(--oc-text); }`,\n `.oc-subtitle { font-size: var(--oc-subtitle-size); font-weight: var(--oc-subtitle-weight); fill: var(--oc-text-muted); }`,\n `.oc-source, .oc-byline, .oc-footer { font-size: var(--oc-source-size); font-weight: var(--oc-source-weight); fill: var(--oc-text-muted); }`,\n `.oc-brand { font-size: 11px; font-weight: 510; letter-spacing: 0.02em; fill: var(--oc-text-faint); }`,\n `.oc-brand-dot { fill: var(--oc-accent); }`,\n `.oc-eyebrow-dot { fill: var(--oc-accent); }`,\n `.oc-metrics { font-family: var(--oc-font-family); }`,\n `.oc-metric-label { font-size: 10px; font-weight: 510; letter-spacing: 0.08em; text-transform: uppercase; fill: var(--oc-text-muted); }`,\n `.oc-metric-value { font-size: 22px; font-weight: 510; letter-spacing: -0.01em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,\n `.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 510; }`,\n `.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 510; }`,\n `.oc-axis-tick-inline { font-size: 11px; font-weight: 400; fill: var(--oc-text-muted); }`,\n `.oc-endpoint-labels { font-family: var(--oc-font-family); }`,\n `.oc-endpoint-label { fill: var(--oc-endpoint-label-color, var(--oc-text)); }`,\n `.oc-endpoint-value { fill: var(--oc-endpoint-value-color, var(--oc-text-muted)); }`,\n `.oc-endpoint-leader { stroke: var(--oc-endpoint-leader-color, currentColor); }`,\n `.oc-annotation-subtitle { fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted)); }`,\n `.oc-metric-secondary { fill: var(--oc-positive); font-size: 12px; font-weight: 400; }`,\n `.oc-legend { font-family: var(--oc-font-family); font-size: var(--oc-body-size); }`,\n `.oc-legend-entry { cursor: default; }`,\n `.oc-legend text { fill: var(--oc-legend-text); }`,\n ];\n\n return rules.join('\\n');\n}\n\nfunction stripInteractiveElements(svg: Element): void {\n const selectors = ['[data-voronoi-overlay]', '[data-crosshair]', '[data-snap-dots]'];\n for (const selector of selectors) {\n const els = svg.querySelectorAll(selector);\n for (const el of els) {\n el.parentNode?.removeChild(el);\n }\n }\n}\n\n/**\n * Render a chart spec to a standalone SVG string without a browser DOM.\n *\n * Requires `happy-dom` as a peer dependency (`bun add happy-dom`).\n *\n * Not safe for concurrent invocation: the render pipeline relies on a global\n * SVG ID counter and a temporary global `document` swap, both of which are\n * single-threaded. In a server handling parallel requests, serialize calls\n * through a queue or mutex.\n *\n * The entire render pipeline is synchronous; the global swap is safe as long\n * as no code schedules microtasks that outlive the call.\n */\nexport function renderStaticSVG(\n spec: ChartSpec | LayerSpec | TileMapSpec,\n options?: StaticRenderOptions,\n): string {\n if (rendering) {\n throw new Error('renderStaticSVG is not reentrant — serialize calls through a queue or mutex');\n }\n rendering = true;\n\n const Window = getHappyDomWindow();\n const win = new Window({ url: 'about:blank' });\n\n const prevDocument = globalThis.document;\n const prevWindow = (globalThis as Record<string, unknown>).window;\n\n try {\n (globalThis as Record<string, unknown>).document = win.document;\n (globalThis as Record<string, unknown>).window = win;\n\n resetSvgIdCounter();\n\n const width = options?.width ?? 640;\n const height = options?.height ?? 420;\n const darkMode = resolveStaticDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n };\n\n let svg: SVGElement;\n let themeForStyle: ResolvedTheme;\n\n if (isTileMapSpec(spec)) {\n const tileMapLayout = compileTileMap(spec, compileOpts);\n svg = renderTileMapSVG(tileMapLayout, { animate: false });\n themeForStyle = tileMapLayout.theme;\n } else {\n let layout: ChartLayout;\n if (isLayerSpec(spec)) {\n layout = compileLayer(spec, compileOpts);\n } else {\n layout = compileChart(spec, compileOpts);\n }\n\n const container = win.document.createElement('div');\n Object.defineProperty(container, 'getBoundingClientRect', {\n value: () => ({\n width,\n height,\n top: 0,\n left: 0,\n right: width,\n bottom: height,\n x: 0,\n y: 0,\n toJSON: () => ({}),\n }),\n });\n\n svg = renderChartSVG(layout, container as unknown as HTMLElement, {\n animate: false,\n crosshair: false,\n });\n\n stripInteractiveElements(svg);\n themeForStyle = layout.theme;\n }\n\n const doc = win.document as unknown as Document;\n let defs = svg.querySelector('defs');\n if (!defs) {\n defs = doc.createElementNS(SVG_NS, 'defs');\n svg.insertBefore(defs as unknown as Node, svg.firstChild);\n }\n const styleEl = doc.createElementNS(SVG_NS, 'style');\n styleEl.textContent = buildThemeStyleBlock(themeForStyle);\n defs.insertBefore(styleEl as unknown as Node, defs.firstChild);\n\n const serializer = new (\n win as unknown as { XMLSerializer: typeof XMLSerializer }\n ).XMLSerializer();\n return serializer.serializeToString(svg as unknown as Node);\n } finally {\n rendering = false;\n if (prevDocument !== undefined) {\n (globalThis as Record<string, unknown>).document = prevDocument;\n } else {\n delete (globalThis as Record<string, unknown>).document;\n }\n if (prevWindow !== undefined) {\n (globalThis as Record<string, unknown>).window = prevWindow;\n } else {\n delete (globalThis as Record<string, unknown>).window;\n }\n win.close();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/static.ts"],"sourcesContent":["// Node.js only — do not import from browser code.\nimport { createRequire } from 'node:module';\nimport type {\n ChartLayout,\n ChartSpec,\n CompileOptions,\n DarkMode,\n LayerSpec,\n ResolvedTheme,\n ThemeConfig,\n TileMapSpec,\n} from '@opendata-ai/openchart-core';\nimport { isLayerSpec, isTileMapSpec } from '@opendata-ai/openchart-core';\nimport { compileChart, compileLayer, compileTileMap } from '@opendata-ai/openchart-engine';\nimport { SVG_NS } from './renderers/svg-dom';\nimport { resetSvgIdCounter } from './svg-ids';\nimport { renderChartSVG } from './svg-renderer';\nimport { buildThemeStyleBlock } from './theme-style-block';\nimport { renderTileMapSVG } from './tilemap-renderer';\n\nconst esmRequire = createRequire(import.meta.url);\n\nlet cachedWindow: typeof import('happy-dom').Window | undefined;\nfunction getHappyDomWindow(): typeof import('happy-dom').Window {\n if (cachedWindow) return cachedWindow;\n try {\n ({ Window: cachedWindow } = esmRequire('happy-dom') as typeof import('happy-dom'));\n } catch {\n throw new Error(\n \"renderStaticSVG requires 'happy-dom' as a peer dependency. Install it with: npm add happy-dom\",\n );\n }\n return cachedWindow;\n}\n\nlet rendering = false;\n\nexport interface StaticRenderOptions {\n width?: number;\n height?: number;\n theme?: ThemeConfig;\n /**\n * Dark mode setting. In static rendering `'auto'` resolves to light mode\n * since there is no `matchMedia` to query. Use `'force'` for dark output.\n */\n darkMode?: DarkMode;\n watermark?: boolean;\n}\n\nfunction resolveStaticDarkMode(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n return false;\n}\n\nfunction stripInteractiveElements(svg: Element): void {\n const selectors = ['[data-voronoi-overlay]', '[data-crosshair]', '[data-snap-dots]'];\n for (const selector of selectors) {\n const els = svg.querySelectorAll(selector);\n for (const el of els) {\n el.parentNode?.removeChild(el);\n }\n }\n}\n\n/**\n * Render a chart spec to a standalone SVG string without a browser DOM.\n *\n * Requires `happy-dom` as a peer dependency (`bun add happy-dom`).\n *\n * Not safe for concurrent invocation: the render pipeline relies on a global\n * SVG ID counter and a temporary global `document` swap, both of which are\n * single-threaded. In a server handling parallel requests, serialize calls\n * through a queue or mutex.\n *\n * The entire render pipeline is synchronous; the global swap is safe as long\n * as no code schedules microtasks that outlive the call.\n */\nexport function renderStaticSVG(\n spec: ChartSpec | LayerSpec | TileMapSpec,\n options?: StaticRenderOptions,\n): string {\n if (rendering) {\n throw new Error('renderStaticSVG is not reentrant — serialize calls through a queue or mutex');\n }\n rendering = true;\n\n const Window = getHappyDomWindow();\n const win = new Window({ url: 'about:blank' });\n\n const prevDocument = globalThis.document;\n const prevWindow = (globalThis as Record<string, unknown>).window;\n\n try {\n (globalThis as Record<string, unknown>).document = win.document;\n (globalThis as Record<string, unknown>).window = win;\n\n resetSvgIdCounter();\n\n const width = options?.width ?? 640;\n const height = options?.height ?? 420;\n const darkMode = resolveStaticDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n };\n\n let svg: SVGElement;\n let themeForStyle: ResolvedTheme;\n\n if (isTileMapSpec(spec)) {\n const tileMapLayout = compileTileMap(spec, compileOpts);\n svg = renderTileMapSVG(tileMapLayout, { animate: false });\n themeForStyle = tileMapLayout.theme;\n } else {\n let layout: ChartLayout;\n if (isLayerSpec(spec)) {\n layout = compileLayer(spec, compileOpts);\n } else {\n layout = compileChart(spec, compileOpts);\n }\n\n const container = win.document.createElement('div');\n Object.defineProperty(container, 'getBoundingClientRect', {\n value: () => ({\n width,\n height,\n top: 0,\n left: 0,\n right: width,\n bottom: height,\n x: 0,\n y: 0,\n toJSON: () => ({}),\n }),\n });\n\n svg = renderChartSVG(layout, container as unknown as HTMLElement, {\n animate: false,\n crosshair: false,\n });\n\n stripInteractiveElements(svg);\n themeForStyle = layout.theme;\n }\n\n const doc = win.document as unknown as Document;\n let defs = svg.querySelector('defs');\n if (!defs) {\n defs = doc.createElementNS(SVG_NS, 'defs');\n svg.insertBefore(defs as unknown as Node, svg.firstChild);\n }\n const styleEl = doc.createElementNS(SVG_NS, 'style');\n styleEl.textContent = buildThemeStyleBlock(themeForStyle);\n defs.insertBefore(styleEl as unknown as Node, defs.firstChild);\n\n const serializer = new (\n win as unknown as { XMLSerializer: typeof XMLSerializer }\n ).XMLSerializer();\n return serializer.serializeToString(svg as unknown as Node);\n } finally {\n rendering = false;\n if (prevDocument !== undefined) {\n (globalThis as Record<string, unknown>).document = prevDocument;\n } else {\n delete (globalThis as Record<string, unknown>).document;\n }\n if (prevWindow !== undefined) {\n (globalThis as Record<string, unknown>).window = prevWindow;\n } else {\n delete (globalThis as Record<string, unknown>).window;\n }\n win.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;AACA,SAAS,qBAAqB;AAW9B,SAAS,aAAa,qBAAqB;AAC3C,SAAS,cAAc,cAAc,sBAAsB;AAO3D,IAAM,aAAa,cAAc,YAAY,GAAG;AAEhD,IAAI;AACJ,SAAS,oBAAuD;AAC9D,MAAI,aAAc,QAAO;AACzB,MAAI;AACF,KAAC,EAAE,QAAQ,aAAa,IAAI,WAAW,WAAW;AAAA,EACpD,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAI,YAAY;AAchB,SAAS,sBAAsB,MAA0B;AACvD,MAAI,SAAS,QAAS,QAAO;AAC7B,SAAO;AACT;AAEA,SAAS,yBAAyB,KAAoB;AACpD,QAAM,YAAY,CAAC,0BAA0B,oBAAoB,kBAAkB;AACnF,aAAW,YAAY,WAAW;AAChC,UAAM,MAAM,IAAI,iBAAiB,QAAQ;AACzC,eAAW,MAAM,KAAK;AACpB,SAAG,YAAY,YAAY,EAAE;AAAA,IAC/B;AAAA,EACF;AACF;AAeO,SAAS,gBACd,MACA,SACQ;AACR,MAAI,WAAW;AACb,UAAM,IAAI,MAAM,kFAA6E;AAAA,EAC/F;AACA,cAAY;AAEZ,QAAM,SAAS,kBAAkB;AACjC,QAAM,MAAM,IAAI,OAAO,EAAE,KAAK,cAAc,CAAC;AAE7C,QAAM,eAAe,WAAW;AAChC,QAAM,aAAc,WAAuC;AAE3D,MAAI;AACF,IAAC,WAAuC,WAAW,IAAI;AACvD,IAAC,WAAuC,SAAS;AAEjD,sBAAkB;AAElB,UAAM,QAAQ,SAAS,SAAS;AAChC,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,WAAW,sBAAsB,SAAS,QAAQ;AAExD,UAAM,cAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,WAAW,SAAS;AAAA,IACtB;AAEA,QAAI;AACJ,QAAI;AAEJ,QAAI,cAAc,IAAI,GAAG;AACvB,YAAM,gBAAgB,eAAe,MAAM,WAAW;AACtD,YAAM,iBAAiB,eAAe,EAAE,SAAS,MAAM,CAAC;AACxD,sBAAgB,cAAc;AAAA,IAChC,OAAO;AACL,UAAI;AACJ,UAAI,YAAY,IAAI,GAAG;AACrB,iBAAS,aAAa,MAAM,WAAW;AAAA,MACzC,OAAO;AACL,iBAAS,aAAa,MAAM,WAAW;AAAA,MACzC;AAEA,YAAM,YAAY,IAAI,SAAS,cAAc,KAAK;AAClD,aAAO,eAAe,WAAW,yBAAyB;AAAA,QACxD,OAAO,OAAO;AAAA,UACZ;AAAA,UACA;AAAA,UACA,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,GAAG;AAAA,UACH,GAAG;AAAA,UACH,QAAQ,OAAO,CAAC;AAAA,QAClB;AAAA,MACF,CAAC;AAED,YAAM,eAAe,QAAQ,WAAqC;AAAA,QAChE,SAAS;AAAA,QACT,WAAW;AAAA,MACb,CAAC;AAED,+BAAyB,GAAG;AAC5B,sBAAgB,OAAO;AAAA,IACzB;AAEA,UAAM,MAAM,IAAI;AAChB,QAAI,OAAO,IAAI,cAAc,MAAM;AACnC,QAAI,CAAC,MAAM;AACT,aAAO,IAAI,gBAAgB,QAAQ,MAAM;AACzC,UAAI,aAAa,MAAyB,IAAI,UAAU;AAAA,IAC1D;AACA,UAAM,UAAU,IAAI,gBAAgB,QAAQ,OAAO;AACnD,YAAQ,cAAc,qBAAqB,aAAa;AACxD,SAAK,aAAa,SAA4B,KAAK,UAAU;AAE7D,UAAM,aAAa,IACjB,IACA,cAAc;AAChB,WAAO,WAAW,kBAAkB,GAAsB;AAAA,EAC5D,UAAE;AACA,gBAAY;AACZ,QAAI,iBAAiB,QAAW;AAC9B,MAAC,WAAuC,WAAW;AAAA,IACrD,OAAO;AACL,aAAQ,WAAuC;AAAA,IACjD;AACA,QAAI,eAAe,QAAW;AAC5B,MAAC,WAAuC,SAAS;AAAA,IACnD,OAAO;AACL,aAAQ,WAAuC;AAAA,IACjD;AACA,QAAI,MAAM;AAAA,EACZ;AACF;","names":[]}
|
package/dist/story/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as Camera, F as FULL_VIEW, V as ViewBoxSize,
|
|
1
|
+
import { i as CameraTarget, M as MountOptions } from '../mount-Dvv8NYIv.js';
|
|
2
|
+
export { C as Camera, F as FULL_VIEW, V as ViewBoxSize, j as cameraTransform, k as camerasClose, l as damp, m as dampCamera, n as fitTarget, o as interpolateCamera, s as scrubCamera } from '../mount-Dvv8NYIv.js';
|
|
3
3
|
import { DataRow, ChartSpec, LayerSpec, GraphSpec, MapSpec, ChartLayout } from '@opendata-ai/openchart-core';
|
|
4
|
+
import '../export-gif.js';
|
|
4
5
|
|
|
5
6
|
/** A spec a story can drive: same union `ChartInstance.update()` accepts. */
|
|
6
7
|
type StorySpec<TData extends DataRow = DataRow> = ChartSpec<TData> | LayerSpec<TData> | GraphSpec | MapSpec;
|
package/dist/story/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
clamp01,
|
|
7
7
|
createChart,
|
|
8
8
|
createMap,
|
|
9
|
-
createTween,
|
|
9
|
+
createTween2 as createTween,
|
|
10
10
|
damp,
|
|
11
11
|
dampCamera,
|
|
12
12
|
easingFns,
|
|
@@ -15,8 +15,10 @@ import {
|
|
|
15
15
|
lerp,
|
|
16
16
|
scrubCamera,
|
|
17
17
|
storyMotion
|
|
18
|
-
} from "../chunk-
|
|
19
|
-
import "../chunk-
|
|
18
|
+
} from "../chunk-KNT5AZ2O.js";
|
|
19
|
+
import "../chunk-T32UHMZA.js";
|
|
20
|
+
import "../chunk-EKAUQAAX.js";
|
|
21
|
+
import "../chunk-RM5XS6NR.js";
|
|
20
22
|
|
|
21
23
|
// src/story/create-chart-story.ts
|
|
22
24
|
import { deepMergeSpec, isMapSpec } from "@opendata-ai/openchart-core";
|