@opendata-ai/openchart-vanilla 8.0.0-rc.9 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-SPILUE5S.js → chunk-BGJT3GTB.js} +2 -2
- package/dist/chunk-DCODCLWI.js +234 -0
- package/dist/chunk-DCODCLWI.js.map +1 -0
- package/dist/chunk-RM5XS6NR.js +113 -0
- package/dist/chunk-RM5XS6NR.js.map +1 -0
- package/dist/{chunk-IZQBT6QM.js → chunk-RQNAZZJM.js} +7297 -5895
- package/dist/chunk-RQNAZZJM.js.map +1 -0
- package/dist/chunk-T32UHMZA.js +282 -0
- package/dist/chunk-T32UHMZA.js.map +1 -0
- package/dist/{chunk-QW7ZB6NE.js → chunk-XOZ6AM7C.js} +51 -47
- package/dist/chunk-XOZ6AM7C.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 +324 -27
- package/dist/index.js +2071 -848
- package/dist/index.js.map +1 -1
- package/dist/{mount-CUKQLeqD.d.ts → mount-CL-nA0f-.d.ts} +85 -2
- package/dist/simulation-worker.js +83 -2
- package/dist/static.d.ts +2 -0
- package/dist/static.js +8 -69
- package/dist/static.js.map +1 -1
- package/dist/story/index.d.ts +25 -9
- package/dist/story/index.js +28 -8
- 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-IZQBT6QM.js.map +0 -1
- package/dist/chunk-QW7ZB6NE.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 -1477
- package/src/__tests__/editable-prop.test.ts +0 -174
- 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__/invert.test.ts +0 -187
- package/src/__tests__/known-bugs-render.test.ts +0 -183
- package/src/__tests__/map.test.ts +0 -636
- 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 -1062
- 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 -1792
- package/src/__tests__/update-scrolly-matrix.test.ts +0 -563
- 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 -649
- package/src/interactions/index.ts +0 -27
- package/src/interactions/invert.ts +0 -54
- package/src/interactions/keyboard-nav.ts +0 -111
- package/src/interactions/legend-interaction.ts +0 -46
- package/src/interactions/selection.ts +0 -286
- package/src/interactions/tooltip-events.ts +0 -72
- package/src/map-camera.ts +0 -111
- package/src/map-mount.ts +0 -797
- package/src/map-renderer.ts +0 -476
- package/src/map-transition.ts +0 -99
- package/src/measure-text.ts +0 -134
- package/src/mount.ts +0 -1434
- 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 -312
- 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 -389
- 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-SPILUE5S.js.map → chunk-BGJT3GTB.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
renderChromeElement,
|
|
4
4
|
renderLegend,
|
|
5
5
|
stampAnimationVars
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XOZ6AM7C.js";
|
|
7
7
|
|
|
8
8
|
// src/tilemap-renderer.ts
|
|
9
9
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
@@ -295,4 +295,4 @@ function renderTileMapSVG(layout, opts) {
|
|
|
295
295
|
export {
|
|
296
296
|
renderTileMapSVG
|
|
297
297
|
};
|
|
298
|
-
//# sourceMappingURL=chunk-
|
|
298
|
+
//# sourceMappingURL=chunk-BGJT3GTB.js.map
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import {
|
|
2
|
+
embedFonts,
|
|
3
|
+
ensureSVGDimensions,
|
|
4
|
+
exportSVG,
|
|
5
|
+
getSVGBackgroundColor,
|
|
6
|
+
getSVGDimensions,
|
|
7
|
+
rasterizeSVGToCanvas
|
|
8
|
+
} from "./chunk-T32UHMZA.js";
|
|
9
|
+
import {
|
|
10
|
+
injectThemeStyleBlock
|
|
11
|
+
} from "./chunk-RM5XS6NR.js";
|
|
12
|
+
|
|
13
|
+
// src/gif-encode.ts
|
|
14
|
+
function readCanvasSRGB(canvas) {
|
|
15
|
+
const ctx = canvas.getContext("2d");
|
|
16
|
+
if (!ctx) throw new Error("Canvas 2D context not available");
|
|
17
|
+
return ctx.getImageData(0, 0, canvas.width, canvas.height, {
|
|
18
|
+
colorSpace: "srgb"
|
|
19
|
+
}).data;
|
|
20
|
+
}
|
|
21
|
+
function paletteFromCanvas(canvas, quantize) {
|
|
22
|
+
return quantize(readCanvasSRGB(canvas), 256);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/export-gif.ts
|
|
26
|
+
var EASE_SMOOTH_SAMPLES = [
|
|
27
|
+
0,
|
|
28
|
+
0.157,
|
|
29
|
+
0.438,
|
|
30
|
+
0.64,
|
|
31
|
+
0.766,
|
|
32
|
+
0.85,
|
|
33
|
+
0.906,
|
|
34
|
+
0.941,
|
|
35
|
+
0.964,
|
|
36
|
+
0.978,
|
|
37
|
+
0.988,
|
|
38
|
+
0.994,
|
|
39
|
+
0.998,
|
|
40
|
+
1
|
|
41
|
+
];
|
|
42
|
+
function evalLinearSamples(samples, t) {
|
|
43
|
+
if (t <= 0) return samples[0];
|
|
44
|
+
if (t >= 1) return samples[samples.length - 1];
|
|
45
|
+
const scaled = t * (samples.length - 1);
|
|
46
|
+
const i = Math.floor(scaled);
|
|
47
|
+
const frac = scaled - i;
|
|
48
|
+
return samples[i] + (samples[i + 1] - samples[i]) * frac;
|
|
49
|
+
}
|
|
50
|
+
function ease(p, _kind) {
|
|
51
|
+
return evalLinearSamples(EASE_SMOOTH_SAMPLES, p);
|
|
52
|
+
}
|
|
53
|
+
function classifyTarget(groupOrEl) {
|
|
54
|
+
const cls = groupOrEl.getAttribute("class") ?? "";
|
|
55
|
+
if (cls.includes("oc-mark-rect") || cls.includes("oc-mark-bar")) {
|
|
56
|
+
const rect = groupOrEl.querySelector("rect") ?? groupOrEl.querySelector("path");
|
|
57
|
+
if (!rect) return null;
|
|
58
|
+
const horizontal = groupOrEl.getAttribute("data-orient") === "horizontal";
|
|
59
|
+
return { el: rect, kind: horizontal ? "bar-horizontal" : "bar-vertical" };
|
|
60
|
+
}
|
|
61
|
+
if (cls.includes("oc-mark-line") || cls.includes("oc-mark-area")) {
|
|
62
|
+
return { el: groupOrEl, kind: "line-area" };
|
|
63
|
+
}
|
|
64
|
+
if (cls.includes("oc-mark-arc") || cls.includes("oc-mark-point") || cls.includes("oc-mark-circle")) {
|
|
65
|
+
return { el: groupOrEl, kind: "fade" };
|
|
66
|
+
}
|
|
67
|
+
return { el: groupOrEl, kind: "fade" };
|
|
68
|
+
}
|
|
69
|
+
function collectTargets(clone, enterDurationMs, staggerMs) {
|
|
70
|
+
const els = clone.querySelectorAll("[data-animation-index]");
|
|
71
|
+
const targets = [];
|
|
72
|
+
for (const el of els) {
|
|
73
|
+
const classified = classifyTarget(el);
|
|
74
|
+
if (!classified) continue;
|
|
75
|
+
const idx = parseInt(el.getAttribute("data-animation-index") ?? "0", 10) || 0;
|
|
76
|
+
const isPoint = classified.kind === "fade" && /oc-mark-(point|circle)/.test(el.getAttribute("class") ?? "");
|
|
77
|
+
const lineOffset = isPoint && hasLineAreaSibling(el) ? enterDurationMs * 0.35 : 0;
|
|
78
|
+
targets.push({
|
|
79
|
+
el: classified.el,
|
|
80
|
+
kind: classified.kind,
|
|
81
|
+
startMs: staggerMs * idx + lineOffset,
|
|
82
|
+
durationMs: isPoint ? enterDurationMs * 0.4 : enterDurationMs
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return targets;
|
|
86
|
+
}
|
|
87
|
+
function hasLineAreaSibling(point) {
|
|
88
|
+
let sib = point.previousElementSibling;
|
|
89
|
+
while (sib) {
|
|
90
|
+
const cls = sib.getAttribute("class") ?? "";
|
|
91
|
+
if (cls.includes("oc-mark-line") || cls.includes("oc-mark-area")) return true;
|
|
92
|
+
sib = sib.previousElementSibling;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
97
|
+
function isModuleNotFound(err) {
|
|
98
|
+
const code = err?.code;
|
|
99
|
+
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") return true;
|
|
100
|
+
const message = err?.message ?? "";
|
|
101
|
+
return /cannot find (module|package)|failed to (resolve|fetch)/i.test(message);
|
|
102
|
+
}
|
|
103
|
+
function resolveRepeat(loop) {
|
|
104
|
+
if (loop === true) return 0;
|
|
105
|
+
if (typeof loop === "number") return loop;
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
function applyFrameState(target, nowMs, easeKind) {
|
|
109
|
+
const p = clamp01((nowMs - target.startMs) / target.durationMs);
|
|
110
|
+
const eased = ease(p, easeKind);
|
|
111
|
+
const style = target.el.style;
|
|
112
|
+
switch (target.kind) {
|
|
113
|
+
case "bar-vertical": {
|
|
114
|
+
const inset = (1 - eased) * 100;
|
|
115
|
+
style.clipPath = `inset(${inset}% 0 0 0)`;
|
|
116
|
+
style.opacity = String(clamp01(p / 0.75));
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "bar-horizontal": {
|
|
120
|
+
const inset = (1 - eased) * 100;
|
|
121
|
+
style.clipPath = `inset(0 ${inset}% 0 0)`;
|
|
122
|
+
style.opacity = String(clamp01(p / 0.75));
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "line-area": {
|
|
126
|
+
const inset = (1 - eased) * 100;
|
|
127
|
+
style.clipPath = `inset(0 ${inset}% 0 0)`;
|
|
128
|
+
style.opacity = String(clamp01(p / 0.15));
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case "fade": {
|
|
132
|
+
style.opacity = String(eased);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function clearFrameState(target) {
|
|
138
|
+
const style = target.el.style;
|
|
139
|
+
style.clipPath = "";
|
|
140
|
+
style.opacity = "";
|
|
141
|
+
}
|
|
142
|
+
async function exportGIF(svgElement, animation, options) {
|
|
143
|
+
let gifenc;
|
|
144
|
+
try {
|
|
145
|
+
gifenc = await import("gifenc");
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (isModuleNotFound(err)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"GIF export requires the optional 'gifenc' peer dependency. Install it: npm install gifenc"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
throw new Error("GIF export failed to load the gifenc encoder", { cause: err });
|
|
153
|
+
}
|
|
154
|
+
const { GIFEncoder, quantize, applyPalette } = gifenc;
|
|
155
|
+
const fps = options?.fps ?? 25;
|
|
156
|
+
const dpi = options?.dpi ?? 2;
|
|
157
|
+
const shouldEmbed = options?.embedFonts ?? true;
|
|
158
|
+
const { width, height } = getSVGDimensions(svgElement);
|
|
159
|
+
if (!width || !height) {
|
|
160
|
+
throw new Error(`SVG has zero dimensions (width=${width}, height=${height})`);
|
|
161
|
+
}
|
|
162
|
+
const enter = animation?.enter;
|
|
163
|
+
const enterDurationMs = enter?.duration ?? 600;
|
|
164
|
+
const staggerMs = enter?.staggerDelay ?? 0;
|
|
165
|
+
const easeKind = enter?.ease ?? "smooth";
|
|
166
|
+
const clone = svgElement.cloneNode(true);
|
|
167
|
+
injectThemeStyleBlock(clone, options?.theme);
|
|
168
|
+
if (shouldEmbed) {
|
|
169
|
+
await embedFonts(clone);
|
|
170
|
+
}
|
|
171
|
+
const targets = collectTargets(clone, enterDurationMs, staggerMs);
|
|
172
|
+
const annotationDelay = animation?.annotationDelay ?? 0;
|
|
173
|
+
const lastMarkEnd = targets.reduce(
|
|
174
|
+
(m, t) => Math.max(m, t.startMs + t.durationMs),
|
|
175
|
+
enterDurationMs
|
|
176
|
+
);
|
|
177
|
+
const lastEnd = Math.max(lastMarkEnd, enterDurationMs + annotationDelay);
|
|
178
|
+
const totalMs = options?.durationMs ?? lastEnd + 300;
|
|
179
|
+
const frameDelay = Math.round(1e3 / fps);
|
|
180
|
+
const totalFrames = Math.max(1, Math.round(totalMs / 1e3 * fps));
|
|
181
|
+
const repeat = resolveRepeat(options?.loop);
|
|
182
|
+
const bgColor = options?.backgroundColor ?? getSVGBackgroundColor(svgElement, options?.theme?.colors.background);
|
|
183
|
+
const fillBackground = (ctx, cv) => {
|
|
184
|
+
ctx.fillStyle = bgColor;
|
|
185
|
+
ctx.fillRect(0, 0, cv.width, cv.height);
|
|
186
|
+
};
|
|
187
|
+
const gif = GIFEncoder();
|
|
188
|
+
let globalPalette = [];
|
|
189
|
+
await seedPalette(clone, targets, width, height, dpi, fillBackground, (data) => {
|
|
190
|
+
globalPalette = quantize(data, 256);
|
|
191
|
+
});
|
|
192
|
+
let frameWritten = 0;
|
|
193
|
+
const writeFrame = async (nowMs) => {
|
|
194
|
+
if (nowMs === null) {
|
|
195
|
+
for (const t of targets) clearFrameState(t);
|
|
196
|
+
} else {
|
|
197
|
+
for (const t of targets) applyFrameState(t, nowMs, easeKind);
|
|
198
|
+
}
|
|
199
|
+
const svgString = ensureSVGDimensions(exportSVG(clone), width, height);
|
|
200
|
+
const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, fillBackground);
|
|
201
|
+
const index = applyPalette(readCanvasSRGB(canvas), globalPalette);
|
|
202
|
+
gif.writeFrame(index, canvas.width, canvas.height, {
|
|
203
|
+
palette: globalPalette,
|
|
204
|
+
delay: frameDelay,
|
|
205
|
+
...frameWritten === 0 ? { repeat } : {}
|
|
206
|
+
});
|
|
207
|
+
frameWritten++;
|
|
208
|
+
};
|
|
209
|
+
for (let i = 0; i < totalFrames; i++) {
|
|
210
|
+
await writeFrame(i / fps * 1e3);
|
|
211
|
+
}
|
|
212
|
+
await writeFrame(null);
|
|
213
|
+
gif.finish();
|
|
214
|
+
return new Blob([gif.bytes()], { type: "image/gif" });
|
|
215
|
+
}
|
|
216
|
+
async function seedPalette(clone, targets, width, height, dpi, prepare, use) {
|
|
217
|
+
for (const t of targets) clearFrameState(t);
|
|
218
|
+
const svgString = ensureSVGDimensions(exportSVG(clone), width, height);
|
|
219
|
+
const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, prepare);
|
|
220
|
+
use(readCanvasSRGB(canvas));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
readCanvasSRGB,
|
|
225
|
+
paletteFromCanvas,
|
|
226
|
+
EASE_SMOOTH_SAMPLES,
|
|
227
|
+
evalLinearSamples,
|
|
228
|
+
classifyTarget,
|
|
229
|
+
isModuleNotFound,
|
|
230
|
+
resolveRepeat,
|
|
231
|
+
applyFrameState,
|
|
232
|
+
exportGIF
|
|
233
|
+
};
|
|
234
|
+
//# sourceMappingURL=chunk-DCODCLWI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gif-encode.ts","../src/export-gif.ts"],"sourcesContent":["/**\n * Shared gifenc frame-encoding primitives.\n *\n * Both GIF exporters (`export-gif.ts`, entrance animation; `export-sequence.ts`,\n * spec-swap keyframes) need the same low-level steps: read a canvas back in\n * sRGB (the export canvas may be display-p3, but gifenc's quantizer assumes sRGB\n * bytes), quantize a shared palette, and write indexed frames. They differ only\n * in how they PRODUCE canvases — synthesized per time-step vs. one per spec — so\n * that stays in each caller; only these primitives are shared, which keeps the\n * sRGB-readback subtlety in exactly one place.\n */\n\n/**\n * Read a canvas's pixels back in sRGB. The export canvas may be display-p3 (so\n * PNG/JPG can embed a wide-gamut profile), but GIF is sRGB-only and gifenc's\n * quantizer assumes sRGB bytes — feeding it P3-encoded data would shift and\n * desaturate the GIF. The browser converts P3→sRGB on readback.\n */\nexport function readCanvasSRGB(canvas: HTMLCanvasElement): Uint8ClampedArray {\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('Canvas 2D context not available');\n return ctx.getImageData(0, 0, canvas.width, canvas.height, {\n colorSpace: 'srgb',\n }).data;\n}\n\n/** Quantize a 256-color palette from a canvas's sRGB pixels. */\nexport function paletteFromCanvas(\n canvas: HTMLCanvasElement,\n quantize: (data: Uint8ClampedArray, maxColors: number) => number[][],\n): number[][] {\n return quantize(readCanvasSRGB(canvas), 256);\n}\n","/**\n * Animated GIF export.\n *\n * Unlike PNG/JPG (single static frame), GIF export must reproduce the chart's\n * entrance animation across many frames. OpenChart entrance animations are pure\n * CSS keyframes that mutate `clip-path`/`opacity`/`transform` in *computed*\n * style, which `XMLSerializer` does not capture — so serializing the live,\n * mid-animation SVG yields the base markup on every frame (a static GIF).\n *\n * This module sidesteps that by *synthesizing* frames deterministically: it\n * takes the settled, final-state SVG plus the resolved animation config,\n * computes each animated element's value at time `t` in JS, stamps\n * `clip-path`/`opacity` as INLINE attributes on a clone (inline values DO\n * serialize), rasterizes that clone, and encodes each frame with gifenc.\n *\n * This mirrors the entrance keyframes in `packages/core/src/styles/*.css`:\n * bars clip bottom-up / left-right, line/area clip left-right, arcs and points\n * fade. Only the entrance is reproduced; the on-screen CSS animation is\n * untouched. `gifenc` is an optional peer dependency, loaded dynamically.\n */\n\nimport type { AnimationEase, ResolvedAnimation, ResolvedTheme } from '@opendata-ai/openchart-core';\nimport {\n embedFonts,\n ensureSVGDimensions,\n exportSVG,\n getSVGBackgroundColor,\n getSVGDimensions,\n rasterizeSVGToCanvas,\n} from './export';\nimport { readCanvasSRGB } from './gif-encode';\nimport { injectThemeStyleBlock } from './theme-style-block';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GIFExportOptions {\n /** Frames per second to capture. Defaults to 25. */\n fps?: number;\n /** DPI scaling factor, matching PNG export. Defaults to 2. */\n dpi?: number;\n /**\n * Loop behavior. Defaults to play-once (hold the final frame):\n * - `false` / omitted: play once, then hold the final frame\n * - `true`: loop forever\n * - a number: loop that many times, then hold\n */\n loop?: boolean | number;\n /**\n * Explicit capture duration in ms. Defaults to the resolved animation total\n * (enter duration + last element's stagger delay + a small tail).\n */\n durationMs?: number;\n /** Embed fonts as base64 data URIs so text matches on-screen. Defaults to true. */\n embedFonts?: boolean;\n /**\n * Opaque background fill. GIF can't carry partial alpha, so a transparent\n * chart would composite onto black. Defaults to the chart's own background\n * color (or white if the chart is transparent).\n */\n backgroundColor?: string;\n /**\n * Resolved theme to inline as a `<style>` block so class-based fills (metric\n * cells, brand watermark dot, legend text) survive serialization. Omit to skip.\n */\n theme?: ResolvedTheme;\n}\n\n/** Which visual property an element's entrance keyframe animates. */\nexport type AnimationKind = 'bar-vertical' | 'bar-horizontal' | 'line-area' | 'fade';\n\nexport interface AnimatedTarget {\n /** The element in the CLONE that receives per-frame inline styles. */\n el: SVGElement;\n kind: AnimationKind;\n /** Start offset in ms (stagger * mark index). */\n startMs: number;\n /** Duration of this element's own animation in ms. */\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Easing — replicate --oc-ease-smooth (CSS linear() sample points)\n// ---------------------------------------------------------------------------\n\n/**\n * Sample points of the `--oc-ease-smooth` token (see tokens.css). CSS\n * `linear()` interpolates piecewise-linearly between evenly-spaced samples, so\n * we reproduce it exactly rather than approximating with a cubic-bezier.\n */\nexport const EASE_SMOOTH_SAMPLES = [\n 0, 0.157, 0.438, 0.64, 0.766, 0.85, 0.906, 0.941, 0.964, 0.978, 0.988, 0.994, 0.998, 1,\n];\n\n/** Piecewise-linear evaluation of a CSS linear() sample array at t in [0,1]. */\nexport function evalLinearSamples(samples: number[], t: number): number {\n if (t <= 0) return samples[0];\n if (t >= 1) return samples[samples.length - 1];\n const scaled = t * (samples.length - 1);\n const i = Math.floor(scaled);\n const frac = scaled - i;\n return samples[i] + (samples[i + 1] - samples[i]) * frac;\n}\n\n/** Ease a normalized progress value. Only 'smooth' is sample-accurate today. */\nfunction ease(p: number, _kind: AnimationEase): number {\n // The entrance rules use --oc-ease-smooth (or fall back to it) in all cases\n // GIF export handles today. Snappy/others could add their own samples later.\n return evalLinearSamples(EASE_SMOOTH_SAMPLES, p);\n}\n\n// ---------------------------------------------------------------------------\n// Target discovery\n// ---------------------------------------------------------------------------\n\n/**\n * Classify an animated element (identified by `data-animation-index`) into the\n * keyframe it plays, and return the element that should receive inline styles.\n * Returns null only when a bar group has no shape child to clip. Unrecognized\n * mark types (map/tilemap/table/text) fall back to a plain opacity fade rather\n * than a hard pop-in — see the generic case at the end.\n */\nexport function classifyTarget(\n groupOrEl: SVGElement,\n): { el: SVGElement; kind: AnimationKind } | null {\n const cls = groupOrEl.getAttribute('class') ?? '';\n\n // Bars: CSS animates the child <rect>; clip + fade. Orient on the group.\n if (cls.includes('oc-mark-rect') || cls.includes('oc-mark-bar')) {\n const rect = groupOrEl.querySelector('rect') ?? groupOrEl.querySelector('path');\n if (!rect) return null;\n const horizontal = groupOrEl.getAttribute('data-orient') === 'horizontal';\n return { el: rect as SVGElement, kind: horizontal ? 'bar-horizontal' : 'bar-vertical' };\n }\n\n // Line/area: the whole group clips left-to-right + fades.\n if (cls.includes('oc-mark-line') || cls.includes('oc-mark-area')) {\n return { el: groupOrEl, kind: 'line-area' };\n }\n\n // Arcs and points: fade only (scale breaks arc/point positioning, so the CSS\n // uses a plain opacity fade too).\n if (\n cls.includes('oc-mark-arc') ||\n cls.includes('oc-mark-point') ||\n cls.includes('oc-mark-circle')\n ) {\n return { el: groupOrEl, kind: 'fade' };\n }\n\n // Everything else (text labels, rules, ticks, map/tilemap/table marks) is a\n // plain fade; treat generically so the frame isn't a hard pop-in.\n return { el: groupOrEl, kind: 'fade' };\n}\n\n/**\n * Build the list of animated targets from a cloned SVG, pairing each element\n * with its timing. Elements carry `data-animation-index` (the mark index that\n * drives stagger) stamped by the renderer.\n */\nfunction collectTargets(\n clone: SVGElement,\n enterDurationMs: number,\n staggerMs: number,\n): AnimatedTarget[] {\n const els = clone.querySelectorAll<SVGElement>('[data-animation-index]');\n const targets: AnimatedTarget[] = [];\n\n for (const el of els) {\n const classified = classifyTarget(el);\n if (!classified) continue;\n const idx = parseInt(el.getAttribute('data-animation-index') ?? '0', 10) || 0;\n // Points animate at 40% of the base duration (matches animation.css).\n const isPoint =\n classified.kind === 'fade' && /oc-mark-(point|circle)/.test(el.getAttribute('class') ?? '');\n // Points on line/area charts start 35% of a duration in, so they pop as the\n // line draws through them (the `.oc-mark-line ~ circle.oc-mark-point` rule).\n const lineOffset = isPoint && hasLineAreaSibling(el) ? enterDurationMs * 0.35 : 0;\n targets.push({\n el: classified.el,\n kind: classified.kind,\n startMs: staggerMs * idx + lineOffset,\n durationMs: isPoint ? enterDurationMs * 0.4 : enterDurationMs,\n });\n }\n\n return targets;\n}\n\n/**\n * Replicate the CSS sibling combinator `.oc-mark-line ~ circle.oc-mark-point`:\n * true when a line or area mark precedes this point among its siblings.\n */\nfunction hasLineAreaSibling(point: SVGElement): boolean {\n let sib = point.previousElementSibling;\n while (sib) {\n const cls = sib.getAttribute('class') ?? '';\n if (cls.includes('oc-mark-line') || cls.includes('oc-mark-area')) return true;\n sib = sib.previousElementSibling;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Per-frame interpolation\n// ---------------------------------------------------------------------------\n\nconst clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);\n\n/**\n * Distinguish \"the optional gifenc peer isn't installed\" from a real error\n * thrown while loading it. Node/bundlers use ERR_MODULE_NOT_FOUND /\n * MODULE_NOT_FOUND / \"Cannot find module\" for the former.\n */\nexport function isModuleNotFound(err: unknown): boolean {\n const code = (err as { code?: string })?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const message = (err as { message?: string })?.message ?? '';\n return /cannot find (module|package)|failed to (resolve|fetch)/i.test(message);\n}\n\n/**\n * Map the `loop` option to gifenc's `repeat` value:\n * `true` → 0 (loop forever), a number → that count, otherwise → -1 (play once,\n * hold the final frame — the default and the common editorial case).\n */\nexport function resolveRepeat(loop: boolean | number | undefined): number {\n if (loop === true) return 0;\n if (typeof loop === 'number') return loop;\n return -1;\n}\n\n/**\n * Apply the interpolated entrance state for a target at absolute time `nowMs`,\n * writing inline `clip-path`/`opacity` so it survives serialization.\n */\nexport function applyFrameState(\n target: AnimatedTarget,\n nowMs: number,\n easeKind: AnimationEase,\n): void {\n const p = clamp01((nowMs - target.startMs) / target.durationMs);\n const eased = ease(p, easeKind);\n const style = target.el.style;\n\n switch (target.kind) {\n case 'bar-vertical': {\n // oc-enter-bar: clip-path inset(100%→0 from top) + opacity (full by 75%).\n const inset = (1 - eased) * 100;\n style.clipPath = `inset(${inset}% 0 0 0)`;\n style.opacity = String(clamp01(p / 0.75));\n break;\n }\n case 'bar-horizontal': {\n // oc-enter-bar-h: clip-path inset(0 100%→0 from right) + opacity.\n const inset = (1 - eased) * 100;\n style.clipPath = `inset(0 ${inset}% 0 0)`;\n style.opacity = String(clamp01(p / 0.75));\n break;\n }\n case 'line-area': {\n // oc-enter-line: clip-path inset(0 100%→0 from right); opacity full by 15%.\n const inset = (1 - eased) * 100;\n style.clipPath = `inset(0 ${inset}% 0 0)`;\n style.opacity = String(clamp01(p / 0.15));\n break;\n }\n case 'fade': {\n // oc-enter-fade-only: opacity 0→1.\n style.opacity = String(eased);\n break;\n }\n }\n}\n\n/** Clear any inline entrance styles so the settled frame renders at rest. */\nfunction clearFrameState(target: AnimatedTarget): void {\n const style = target.el.style;\n style.clipPath = '';\n style.opacity = '';\n}\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\n/**\n * Render a chart's entrance animation to an animated GIF Blob.\n *\n * @param svgElement - The settled, fully-rendered chart SVG (animation complete).\n * @param animation - The chart's resolved animation config (for exact timing).\n * When undefined, timing falls back to sensible defaults.\n * @param options - fps, dpi, loop, duration, font-embedding overrides.\n * @returns A Promise resolving to the GIF Blob.\n */\nexport async function exportGIF(\n svgElement: SVGElement,\n animation: ResolvedAnimation | undefined,\n options?: GIFExportOptions,\n): Promise<Blob> {\n let gifenc: typeof import('gifenc');\n try {\n gifenc = await import('gifenc');\n } catch (err) {\n // Only a genuine \"module not found\" means the optional peer is absent. Any\n // other failure (a real bug in gifenc's module eval, a corrupt install) must\n // surface with its context, not the misleading install hint.\n if (isModuleNotFound(err)) {\n throw new Error(\n \"GIF export requires the optional 'gifenc' peer dependency. Install it: npm install gifenc\",\n );\n }\n throw new Error('GIF export failed to load the gifenc encoder', { cause: err });\n }\n const { GIFEncoder, quantize, applyPalette } = gifenc;\n\n const fps = options?.fps ?? 25;\n const dpi = options?.dpi ?? 2;\n const shouldEmbed = options?.embedFonts ?? true;\n const { width, height } = getSVGDimensions(svgElement);\n if (!width || !height) {\n throw new Error(`SVG has zero dimensions (width=${width}, height=${height})`);\n }\n\n // Resolve timing from the config (exact) with defaults as a fallback.\n const enter = animation?.enter;\n const enterDurationMs = enter?.duration ?? 600;\n const staggerMs = enter?.staggerDelay ?? 0;\n const easeKind: AnimationEase = enter?.ease ?? 'smooth';\n\n // Build one font-embedded clone that represents the FINAL frame; every frame\n // derives from it, so fonts are embedded exactly once.\n const clone = svgElement.cloneNode(true) as SVGElement;\n // Inline the theme style block so class-based chrome fills survive the detach\n // from the page stylesheet (metrics, brand dot, legend). No-op when undefined.\n injectThemeStyleBlock(clone, options?.theme);\n if (shouldEmbed) {\n await embedFonts(clone);\n }\n const targets = collectTargets(clone, enterDurationMs, staggerMs);\n\n // Total capture window: explicit override, else the animation total. Mirrors\n // computeAnimationDuration (animation.ts): the window must cover the last\n // mark's stagger + its own duration AND the annotation reveal, which animates\n // at enterDuration + annotationDelay. Without the annotationDelay term, charts\n // with annotations get cut off before the annotations finish fading in.\n const annotationDelay = animation?.annotationDelay ?? 0;\n const lastMarkEnd = targets.reduce(\n (m, t) => Math.max(m, t.startMs + t.durationMs),\n enterDurationMs,\n );\n const lastEnd = Math.max(lastMarkEnd, enterDurationMs + annotationDelay);\n const totalMs = options?.durationMs ?? lastEnd + 300;\n const frameDelay = Math.round(1000 / fps);\n const totalFrames = Math.max(1, Math.round((totalMs / 1000) * fps));\n\n // Loop → gifenc repeat: play-once (-1), forever (0), or explicit count.\n const repeat = resolveRepeat(options?.loop);\n\n // GIF can't carry partial alpha, so fill an opaque background before drawing\n // each frame (else a transparent chart composites onto black). Runs before\n // ctx.scale, so paint the raw device-pixel canvas size.\n // The theme background is the fallback: canvas mark mode suppresses the SVG\n // background rect, so there's nothing in the markup to read it from.\n const bgColor =\n options?.backgroundColor ??\n getSVGBackgroundColor(svgElement, options?.theme?.colors.background);\n const fillBackground = (ctx: CanvasRenderingContext2D, cv: HTMLCanvasElement): void => {\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, cv.width, cv.height);\n };\n\n const gif = GIFEncoder();\n\n // Derive ONE global palette from the settled frame (most complete colors)\n // and reuse it for every frame, so colors stay stable and the file stays\n // small. gifenc writes the global color table from the palette on the first\n // frame; later frames reuse it.\n let globalPalette: number[][] = [];\n await seedPalette(clone, targets, width, height, dpi, fillBackground, (data) => {\n globalPalette = quantize(data, 256);\n });\n\n let frameWritten = 0;\n const writeFrame = async (nowMs: number | null): Promise<void> => {\n if (nowMs === null) {\n for (const t of targets) clearFrameState(t);\n } else {\n for (const t of targets) applyFrameState(t, nowMs, easeKind);\n }\n const svgString = ensureSVGDimensions(exportSVG(clone), width, height);\n const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, fillBackground);\n const index = applyPalette(readCanvasSRGB(canvas), globalPalette);\n // `repeat` is only meaningful on the first frame (it writes the loop\n // extension); passing it later is ignored by gifenc.\n gif.writeFrame(index, canvas.width, canvas.height, {\n palette: globalPalette,\n delay: frameDelay,\n ...(frameWritten === 0 ? { repeat } : {}),\n });\n frameWritten++;\n };\n\n for (let i = 0; i < totalFrames; i++) {\n await writeFrame((i / fps) * 1000);\n }\n // Final settled frame the GIF holds on.\n await writeFrame(null);\n\n gif.finish();\n // gifenc types bytes() as Uint8Array<ArrayBufferLike>; BlobPart wants a view\n // over a plain ArrayBuffer. The runtime value is a valid BufferSource, so the\n // cast is safe — it only bridges the ArrayBufferLike/ArrayBuffer lib mismatch.\n return new Blob([gif.bytes() as BlobPart], { type: 'image/gif' });\n}\n\n/**\n * Render the settled clone once (no per-frame state) to derive the palette\n * source, without writing a frame. Restores frame state afterward.\n */\nasync function seedPalette(\n clone: SVGElement,\n targets: AnimatedTarget[],\n width: number,\n height: number,\n dpi: number,\n prepare: (ctx: CanvasRenderingContext2D, cv: HTMLCanvasElement) => void,\n use: (data: Uint8ClampedArray) => void,\n): Promise<void> {\n for (const t of targets) clearFrameState(t);\n const svgString = ensureSVGDimensions(exportSVG(clone), width, height);\n const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, prepare);\n use(readCanvasSRGB(canvas));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/renderers/svg-dom.ts
|
|
2
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3
|
+
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
4
|
+
function createSVGElement(tag) {
|
|
5
|
+
return document.createElementNS(SVG_NS, tag);
|
|
6
|
+
}
|
|
7
|
+
function setAttrs(el, attrs) {
|
|
8
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
9
|
+
el.setAttribute(key, String(value));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function applyTextStyle(el, style) {
|
|
13
|
+
const inline = el.style;
|
|
14
|
+
inline.setProperty("fill", style.fill);
|
|
15
|
+
inline.setProperty("font-size", `${style.fontSize}px`);
|
|
16
|
+
inline.setProperty("font-weight", String(style.fontWeight));
|
|
17
|
+
inline.setProperty("font-family", style.fontFamily);
|
|
18
|
+
if (style.textAnchor) {
|
|
19
|
+
el.setAttribute("text-anchor", style.textAnchor);
|
|
20
|
+
}
|
|
21
|
+
if (style.dominantBaseline) {
|
|
22
|
+
el.setAttribute("dominant-baseline", style.dominantBaseline);
|
|
23
|
+
}
|
|
24
|
+
if (style.fontVariant) {
|
|
25
|
+
el.setAttribute("font-variant", style.fontVariant);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/theme-style-block.ts
|
|
30
|
+
import { adaptForLightLineStroke, cssTokenDefault } from "@opendata-ai/openchart-core";
|
|
31
|
+
function buildThemeStyleBlock(theme) {
|
|
32
|
+
const accent = theme.colors.categorical[0] ?? cssTokenDefault("--oc-accent", "light");
|
|
33
|
+
const bg = theme.colors.background === "transparent" ? theme.isDark ? cssTokenDefault("--oc-bg", "dark") : cssTokenDefault("--oc-bg", "light") : theme.colors.background;
|
|
34
|
+
const props = [
|
|
35
|
+
`--oc-font-family: ${theme.fonts.family}`,
|
|
36
|
+
`--oc-font-mono: ${theme.fonts.mono}`,
|
|
37
|
+
`--oc-title-size: ${theme.chrome.title.fontSize}px`,
|
|
38
|
+
`--oc-title-weight: ${theme.chrome.title.fontWeight}`,
|
|
39
|
+
`--oc-title-tracking: ${cssTokenDefault("--oc-title-tracking", "light")}`,
|
|
40
|
+
`--oc-subtitle-size: ${theme.chrome.subtitle.fontSize}px`,
|
|
41
|
+
`--oc-subtitle-weight: ${theme.chrome.subtitle.fontWeight}`,
|
|
42
|
+
`--oc-source-size: ${theme.chrome.source.fontSize}px`,
|
|
43
|
+
`--oc-source-weight: ${theme.chrome.source.fontWeight}`,
|
|
44
|
+
`--oc-body-size: ${theme.fonts.sizes.body}px`,
|
|
45
|
+
`--oc-eyebrow-size: ${theme.chrome.eyebrow.fontSize}px`,
|
|
46
|
+
`--oc-eyebrow-weight: ${theme.chrome.eyebrow.fontWeight}`,
|
|
47
|
+
`--oc-eyebrow-tracking: ${cssTokenDefault("--oc-eyebrow-tracking", "light")}`,
|
|
48
|
+
`--oc-bg: ${bg}`,
|
|
49
|
+
`--oc-text: ${theme.colors.text}`,
|
|
50
|
+
`--oc-text-muted: ${theme.colors.axis}`,
|
|
51
|
+
`--oc-text-faint: ${theme.isDark ? cssTokenDefault("--oc-text-faint", "dark") : cssTokenDefault("--oc-text-faint", "light")}`,
|
|
52
|
+
`--oc-gridline: ${theme.colors.gridline}`,
|
|
53
|
+
`--oc-axis: ${theme.colors.axis}`,
|
|
54
|
+
`--oc-border-radius: ${theme.borderRadius}px`,
|
|
55
|
+
`--oc-accent: ${accent}`,
|
|
56
|
+
`--oc-accent-strong: ${adaptForLightLineStroke(accent)}`,
|
|
57
|
+
`--oc-positive: ${theme.colors.positive}`,
|
|
58
|
+
`--oc-negative: ${theme.colors.negative}`,
|
|
59
|
+
`--oc-legend-text: ${theme.isDark ? cssTokenDefault("--oc-legend-text", "dark") : cssTokenDefault("--oc-legend-text", "light")}`,
|
|
60
|
+
`--oc-space-2: ${theme.spacing.chromeGap * 2}px`,
|
|
61
|
+
`--oc-space-4: ${theme.spacing.padding}px`
|
|
62
|
+
];
|
|
63
|
+
const rules = [
|
|
64
|
+
`svg.oc-chart { ${props.join("; ")}; }`,
|
|
65
|
+
`.oc-chrome { font-family: var(--oc-font-family); }`,
|
|
66
|
+
`.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); }`,
|
|
67
|
+
`.oc-title { font-size: var(--oc-title-size); font-weight: var(--oc-title-weight); letter-spacing: var(--oc-title-tracking); fill: var(--oc-text); }`,
|
|
68
|
+
`.oc-subtitle { font-size: var(--oc-subtitle-size); font-weight: var(--oc-subtitle-weight); fill: var(--oc-text-muted); }`,
|
|
69
|
+
`.oc-source, .oc-byline, .oc-footer { font-size: var(--oc-source-size); font-weight: var(--oc-source-weight); fill: var(--oc-text-muted); }`,
|
|
70
|
+
`.oc-brand { font-size: 11px; font-weight: 510; letter-spacing: 0.02em; fill: var(--oc-text-faint); }`,
|
|
71
|
+
`.oc-brand-dot { fill: var(--oc-accent); }`,
|
|
72
|
+
`.oc-eyebrow-dot { fill: var(--oc-accent); }`,
|
|
73
|
+
`.oc-metrics { font-family: var(--oc-font-family); }`,
|
|
74
|
+
`.oc-metric-label { font-size: 10px; font-weight: 510; letter-spacing: 0.08em; text-transform: uppercase; fill: var(--oc-text-muted); }`,
|
|
75
|
+
`.oc-metric-value { font-size: 22px; font-weight: 510; letter-spacing: -0.01em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,
|
|
76
|
+
`.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 510; }`,
|
|
77
|
+
`.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 510; }`,
|
|
78
|
+
`.oc-axis-tick-inline { font-size: 11px; font-weight: 400; fill: var(--oc-text-muted); }`,
|
|
79
|
+
`.oc-endpoint-labels { font-family: var(--oc-font-family); }`,
|
|
80
|
+
`.oc-endpoint-label { fill: var(--oc-endpoint-label-color, var(--oc-text)); }`,
|
|
81
|
+
`.oc-endpoint-value { fill: var(--oc-endpoint-value-color, var(--oc-text-muted)); }`,
|
|
82
|
+
`.oc-endpoint-leader { stroke: var(--oc-endpoint-leader-color, currentColor); }`,
|
|
83
|
+
`.oc-annotation-subtitle { fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted)); }`,
|
|
84
|
+
`.oc-metric-secondary { fill: var(--oc-positive); font-size: 12px; font-weight: 400; }`,
|
|
85
|
+
`.oc-legend { font-family: var(--oc-font-family); font-size: var(--oc-body-size); }`,
|
|
86
|
+
`.oc-legend-entry { cursor: default; }`,
|
|
87
|
+
`.oc-legend text { fill: var(--oc-legend-text); }`
|
|
88
|
+
];
|
|
89
|
+
return rules.join("\n");
|
|
90
|
+
}
|
|
91
|
+
function injectThemeStyleBlock(svg, theme) {
|
|
92
|
+
if (!theme) return;
|
|
93
|
+
let defs = svg.querySelector("defs");
|
|
94
|
+
if (!defs) {
|
|
95
|
+
defs = document.createElementNS(SVG_NS, "defs");
|
|
96
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
97
|
+
}
|
|
98
|
+
const style = document.createElementNS(SVG_NS, "style");
|
|
99
|
+
style.setAttribute("data-oc-theme", "");
|
|
100
|
+
style.textContent = buildThemeStyleBlock(theme);
|
|
101
|
+
defs.insertBefore(style, defs.firstChild);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
SVG_NS,
|
|
106
|
+
XLINK_NS,
|
|
107
|
+
createSVGElement,
|
|
108
|
+
setAttrs,
|
|
109
|
+
applyTextStyle,
|
|
110
|
+
buildThemeStyleBlock,
|
|
111
|
+
injectThemeStyleBlock
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=chunk-RM5XS6NR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderers/svg-dom.ts","../src/theme-style-block.ts"],"sourcesContent":["/**\n * Shared SVG DOM helpers used across the per-concern renderers.\n *\n * Pure, stateless utilities. No layout/theme knowledge.\n */\n\nimport type { TextStyle } from '@opendata-ai/openchart-core';\n\nexport const SVG_NS = 'http://www.w3.org/2000/svg';\nexport const XLINK_NS = 'http://www.w3.org/1999/xlink';\n\nexport function createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nexport function setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\nexport function applyTextStyle(el: SVGElement, style: TextStyle): void {\n // Use inline styles so engine-computed values take priority over CSS class\n // defaults (e.g. .oc-title { font-size: var(--oc-title-size) } would otherwise\n // override the responsive scaling applied by the chrome layout).\n const inline = (el as SVGElement & ElementCSSInlineStyle).style;\n inline.setProperty('fill', style.fill);\n inline.setProperty('font-size', `${style.fontSize}px`);\n inline.setProperty('font-weight', String(style.fontWeight));\n inline.setProperty('font-family', style.fontFamily);\n if (style.textAnchor) {\n el.setAttribute('text-anchor', style.textAnchor);\n }\n if (style.dominantBaseline) {\n el.setAttribute('dominant-baseline', style.dominantBaseline);\n }\n if (style.fontVariant) {\n el.setAttribute('font-variant', style.fontVariant);\n }\n}\n","/**\n * Theme `<style>` block for self-contained SVG export.\n *\n * Charts render most fills as inline SVG attributes, but a handful of chrome\n * elements (metric cells, the brand watermark dot, legend text, endpoint labels)\n * take their fill from CSS classes in `chrome.css` via `--oc-*` variables. That\n * works on-screen because the live SVG inherits the page stylesheet — but a\n * serialized/rasterized export (PNG/JPG/GIF) is detached from the page, so those\n * class-based fills vanish. This module builds a `<style>` block that resolves\n * every such rule against a concrete `ResolvedTheme`, so injecting it into an\n * export clone makes the SVG fully self-contained.\n *\n * Both the headless renderer (`static.ts`) and the browser export path use this,\n * so class-based styling round-trips identically no matter which path produced\n * the SVG. Browser-safe (no Node imports) so the browser exporters can import it.\n */\n\nimport type { ResolvedTheme } from '@opendata-ai/openchart-core';\nimport { adaptForLightLineStroke, cssTokenDefault } from '@opendata-ai/openchart-core';\nimport { SVG_NS } from './renderers/svg-dom';\n\n/**\n * Build the theme `<style>` block CSS text for a resolved theme. Sets the\n * `--oc-*` custom properties on `svg.oc-chart` and defines every class-based\n * chrome rule against them.\n */\nexport function 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\n/**\n * Inject the theme style block into an SVG clone's `<defs>` so class-based fills\n * survive serialization. Idempotent-ish: adds one `<style data-oc-theme>` at the\n * front of `<defs>`; call once per clone before serializing. No-op if `theme` is\n * undefined (nothing to resolve against).\n */\nexport function injectThemeStyleBlock(svg: SVGElement, theme: ResolvedTheme | undefined): void {\n if (!theme) return;\n let defs = svg.querySelector('defs');\n if (!defs) {\n defs = document.createElementNS(SVG_NS, 'defs');\n svg.insertBefore(defs, svg.firstChild);\n }\n const style = document.createElementNS(SVG_NS, 'style');\n style.setAttribute('data-oc-theme', '');\n style.textContent = buildThemeStyleBlock(theme);\n defs.insertBefore(style, defs.firstChild);\n}\n"],"mappings":"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","names":[]}
|