@opendata-ai/openchart-vanilla 7.11.0 → 8.0.0-rc.2
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-EQ6DYFO7.js → chunk-5EOV5JQZ.js} +431 -538
- package/dist/chunk-5EOV5JQZ.js.map +1 -0
- package/dist/chunk-ENXSUXC5.js +6513 -0
- package/dist/chunk-ENXSUXC5.js.map +1 -0
- package/dist/chunk-W34KN7FD.js +298 -0
- package/dist/chunk-W34KN7FD.js.map +1 -0
- package/dist/index.d.ts +110 -116
- package/dist/index.js +189 -4408
- package/dist/index.js.map +1 -1
- package/dist/mount-CUKQLeqD.d.ts +207 -0
- package/dist/static.js +16 -11
- package/dist/static.js.map +1 -1
- package/dist/story/index.d.ts +277 -0
- package/dist/story/index.js +537 -0
- package/dist/story/index.js.map +1 -0
- package/dist/styles.css +2 -1
- package/dist/styles.css.map +1 -0
- package/package.json +9 -3
- package/src/__test-fixtures__/specs.ts +1 -1
- package/src/__tests__/annotations-render.test.ts +201 -0
- package/src/__tests__/continuous-legend.test.ts +91 -0
- package/src/__tests__/edit-events.test.ts +155 -0
- package/src/__tests__/map.test.ts +293 -0
- package/src/__tests__/mount.test.ts +26 -0
- package/src/__tests__/pattern-fills.test.ts +105 -0
- package/src/__tests__/series-search.test.ts +311 -0
- package/src/__tests__/table-mount.test.ts +34 -0
- package/src/__tests__/tooltip.test.ts +26 -0
- package/src/__tests__/transition.test.ts +196 -11
- package/src/__tests__/you-draw-it.test.ts +273 -0
- package/src/animation-vars.ts +42 -0
- package/src/barlist-renderer.ts +2 -2
- package/src/dom-helpers.ts +17 -0
- package/src/export.ts +33 -16
- package/src/index.ts +7 -0
- package/src/interactions/editing-drags.ts +9 -6
- package/src/interactions/legend-interaction.ts +9 -1
- package/src/interactions/selection.ts +12 -3
- package/src/map-camera.ts +88 -0
- package/src/map-mount.ts +701 -0
- package/src/map-renderer.ts +338 -0
- package/src/map-transition.ts +83 -0
- package/src/mount.ts +212 -26
- package/src/pattern-utils.ts +110 -0
- package/src/renderers/annotations.ts +162 -117
- package/src/renderers/axes.ts +13 -6
- package/src/renderers/brand.ts +7 -2
- package/src/renderers/chrome.ts +32 -10
- package/src/renderers/legend.ts +171 -2
- package/src/renderers/marks.ts +33 -5
- package/src/sankey-renderer.ts +7 -11
- package/src/series-search.ts +392 -0
- package/src/static.ts +13 -8
- package/src/story/__tests__/camera-math.test.ts +145 -0
- package/src/story/__tests__/create-chart-story.test.ts +317 -0
- package/src/story/__tests__/progress-math.test.ts +95 -0
- package/src/story/__tests__/scroll-driver.test.ts +130 -0
- package/src/story/__tests__/tween.test.ts +159 -0
- package/src/story/camera-math.ts +125 -0
- package/src/story/create-chart-story.ts +353 -0
- package/src/story/crossfade.ts +70 -0
- package/src/story/index.ts +39 -0
- package/src/story/progress-math.ts +85 -0
- package/src/story/resolve-camera-target.ts +119 -0
- package/src/story/scroll-driver.ts +206 -0
- package/src/story/story-camera.ts +49 -0
- package/src/story/tween.ts +112 -0
- package/src/story/types.ts +76 -0
- package/src/svg-renderer.ts +60 -29
- package/src/table-mount.ts +12 -3
- package/src/table-renderer.ts +9 -28
- package/src/text-edit-overlay.ts +1 -1
- package/src/theme-tokens.ts +4 -4
- package/src/tilemap-mount.ts +27 -6
- package/src/tilemap-renderer.ts +30 -88
- package/src/tooltip.ts +11 -0
- package/src/transition.ts +376 -33
- package/src/you-draw-it.ts +480 -0
- package/dist/chunk-EQ6DYFO7.js.map +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import {
|
|
2
|
+
nextSvgId,
|
|
3
|
+
renderChromeElement,
|
|
4
|
+
renderLegend,
|
|
5
|
+
stampAnimationVars
|
|
6
|
+
} from "./chunk-5EOV5JQZ.js";
|
|
7
|
+
|
|
8
|
+
// src/tilemap-renderer.ts
|
|
9
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
10
|
+
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
11
|
+
var BRAND_URL = "https://tryopendata.ai";
|
|
12
|
+
function createSVGElement(tag) {
|
|
13
|
+
return document.createElementNS(SVG_NS, tag);
|
|
14
|
+
}
|
|
15
|
+
function setAttrs(el, attrs) {
|
|
16
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
17
|
+
el.setAttribute(key, String(value));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function renderChrome(parent, layout) {
|
|
21
|
+
const g = createSVGElement("g");
|
|
22
|
+
g.setAttribute("class", "oc-chrome");
|
|
23
|
+
const { chrome, measureText } = layout;
|
|
24
|
+
const bottomOffset = layout.area.y + layout.area.height;
|
|
25
|
+
if (chrome.title) {
|
|
26
|
+
renderChromeElement(g, chrome.title, "oc-title", "title", measureText);
|
|
27
|
+
}
|
|
28
|
+
if (chrome.subtitle) {
|
|
29
|
+
renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
|
|
30
|
+
}
|
|
31
|
+
if (chrome.source) {
|
|
32
|
+
renderChromeElement(
|
|
33
|
+
g,
|
|
34
|
+
{ ...chrome.source, y: bottomOffset + chrome.source.y },
|
|
35
|
+
"oc-source",
|
|
36
|
+
"source",
|
|
37
|
+
measureText
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (chrome.byline) {
|
|
41
|
+
renderChromeElement(
|
|
42
|
+
g,
|
|
43
|
+
{ ...chrome.byline, y: bottomOffset + chrome.byline.y },
|
|
44
|
+
"oc-byline",
|
|
45
|
+
"byline",
|
|
46
|
+
measureText
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (chrome.footer) {
|
|
50
|
+
renderChromeElement(
|
|
51
|
+
g,
|
|
52
|
+
{ ...chrome.footer, y: bottomOffset + chrome.footer.y },
|
|
53
|
+
"oc-footer",
|
|
54
|
+
"footer",
|
|
55
|
+
measureText
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
parent.appendChild(g);
|
|
59
|
+
}
|
|
60
|
+
function renderWatermark(parent, layout) {
|
|
61
|
+
if (layout.width < 480) return;
|
|
62
|
+
const { width, height } = layout;
|
|
63
|
+
const { theme } = layout;
|
|
64
|
+
const padding = theme.spacing.padding;
|
|
65
|
+
const rightEdge = width - padding;
|
|
66
|
+
const bottomEdge = height - padding;
|
|
67
|
+
const fill = theme.colors.axis;
|
|
68
|
+
const a = createSVGElement("a");
|
|
69
|
+
a.setAttribute("href", BRAND_URL);
|
|
70
|
+
a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
|
|
71
|
+
a.setAttribute("target", "_blank");
|
|
72
|
+
a.setAttribute("rel", "noopener");
|
|
73
|
+
a.setAttribute("class", "oc-chrome-ref");
|
|
74
|
+
const text = createSVGElement("text");
|
|
75
|
+
setAttrs(text, {
|
|
76
|
+
x: rightEdge,
|
|
77
|
+
y: bottomEdge,
|
|
78
|
+
"dominant-baseline": "alphabetic",
|
|
79
|
+
"text-anchor": "end",
|
|
80
|
+
"font-family": theme.fonts.family,
|
|
81
|
+
"font-size": 12,
|
|
82
|
+
"fill-opacity": 0.55
|
|
83
|
+
});
|
|
84
|
+
text.style.setProperty("fill", fill);
|
|
85
|
+
const trySpan = createSVGElement("tspan");
|
|
86
|
+
setAttrs(trySpan, { "font-weight": 500 });
|
|
87
|
+
trySpan.textContent = "try";
|
|
88
|
+
const openDataSpan = createSVGElement("tspan");
|
|
89
|
+
setAttrs(openDataSpan, { "font-weight": 600, "font-size": 16 });
|
|
90
|
+
openDataSpan.textContent = "OpenData";
|
|
91
|
+
const aiSpan = createSVGElement("tspan");
|
|
92
|
+
setAttrs(aiSpan, { "font-weight": 500 });
|
|
93
|
+
aiSpan.textContent = ".ai";
|
|
94
|
+
text.appendChild(trySpan);
|
|
95
|
+
text.appendChild(openDataSpan);
|
|
96
|
+
text.appendChild(aiSpan);
|
|
97
|
+
a.appendChild(text);
|
|
98
|
+
parent.appendChild(a);
|
|
99
|
+
}
|
|
100
|
+
function renderTiles(parent, tiles, animation) {
|
|
101
|
+
const g = createSVGElement("g");
|
|
102
|
+
g.setAttribute("class", "oc-tilemap-tiles");
|
|
103
|
+
g.setAttribute("role", "list");
|
|
104
|
+
const tileDelays = [];
|
|
105
|
+
if (animation?.enter) {
|
|
106
|
+
const baseStagger = 800 / Math.max(tiles.length, 1);
|
|
107
|
+
let seed = 17;
|
|
108
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
109
|
+
const idx = tiles[i].animationIndex ?? i;
|
|
110
|
+
seed = seed * 1103515245 + 12345 & 2147483647;
|
|
111
|
+
const jitter = (seed % 1e3 / 1e3 - 0.5) * 0.8;
|
|
112
|
+
tileDelays.push(Math.max(0, Math.round(idx * baseStagger * (1 + jitter))));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
116
|
+
const tile = tiles[i];
|
|
117
|
+
const tileGroup = createSVGElement("g");
|
|
118
|
+
tileGroup.setAttribute("class", "oc-tilemap-tile");
|
|
119
|
+
tileGroup.setAttribute("data-state", tile.stateCode);
|
|
120
|
+
tileGroup.setAttribute("role", "listitem");
|
|
121
|
+
if (tile.aria?.label) {
|
|
122
|
+
tileGroup.setAttribute("aria-label", tile.aria.label);
|
|
123
|
+
}
|
|
124
|
+
if (animation?.enter) {
|
|
125
|
+
const idx = tile.animationIndex ?? i;
|
|
126
|
+
tileGroup.setAttribute("data-animation-index", String(idx));
|
|
127
|
+
tileGroup.style.setProperty(
|
|
128
|
+
"--oc-mark-index",
|
|
129
|
+
String(idx)
|
|
130
|
+
);
|
|
131
|
+
tileGroup.style.setProperty(
|
|
132
|
+
"--oc-tile-delay",
|
|
133
|
+
`${tileDelays[i]}ms`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const rect = createSVGElement("rect");
|
|
137
|
+
setAttrs(rect, {
|
|
138
|
+
x: tile.x,
|
|
139
|
+
y: tile.y,
|
|
140
|
+
width: tile.size,
|
|
141
|
+
height: tile.size,
|
|
142
|
+
rx: tile.cornerRadius,
|
|
143
|
+
fill: tile.fill,
|
|
144
|
+
"fill-opacity": tile.fillOpacity ?? 1,
|
|
145
|
+
stroke: tile.stroke,
|
|
146
|
+
"stroke-width": tile.strokeWidth
|
|
147
|
+
});
|
|
148
|
+
tileGroup.appendChild(rect);
|
|
149
|
+
const codeLabel = createSVGElement("text");
|
|
150
|
+
setAttrs(codeLabel, {
|
|
151
|
+
x: tile.label.x,
|
|
152
|
+
y: tile.label.y,
|
|
153
|
+
"text-anchor": "middle",
|
|
154
|
+
"dominant-baseline": "central",
|
|
155
|
+
"font-family": tile.label.style.fontFamily,
|
|
156
|
+
"font-size": tile.label.style.fontSize,
|
|
157
|
+
"font-weight": tile.label.style.fontWeight
|
|
158
|
+
});
|
|
159
|
+
codeLabel.style.setProperty(
|
|
160
|
+
"fill",
|
|
161
|
+
tile.label.style.fill
|
|
162
|
+
);
|
|
163
|
+
codeLabel.textContent = tile.label.text;
|
|
164
|
+
tileGroup.appendChild(codeLabel);
|
|
165
|
+
if (tile.valueLabel.visible && tile.valueLabel.text) {
|
|
166
|
+
const valueLabel = createSVGElement("text");
|
|
167
|
+
setAttrs(valueLabel, {
|
|
168
|
+
x: tile.valueLabel.x,
|
|
169
|
+
y: tile.valueLabel.y,
|
|
170
|
+
"text-anchor": "middle",
|
|
171
|
+
"dominant-baseline": "central",
|
|
172
|
+
"font-family": tile.valueLabel.style.fontFamily,
|
|
173
|
+
"font-size": tile.valueLabel.style.fontSize,
|
|
174
|
+
"font-weight": tile.valueLabel.style.fontWeight
|
|
175
|
+
});
|
|
176
|
+
valueLabel.style.setProperty(
|
|
177
|
+
"fill",
|
|
178
|
+
tile.valueLabel.style.fill
|
|
179
|
+
);
|
|
180
|
+
valueLabel.textContent = tile.valueLabel.text;
|
|
181
|
+
tileGroup.appendChild(valueLabel);
|
|
182
|
+
}
|
|
183
|
+
g.appendChild(tileGroup);
|
|
184
|
+
}
|
|
185
|
+
parent.appendChild(g);
|
|
186
|
+
}
|
|
187
|
+
function renderGradientLegend(parent, layout) {
|
|
188
|
+
if (!layout.gradientLegend) return;
|
|
189
|
+
const { gradientLegend } = layout;
|
|
190
|
+
const g = createSVGElement("g");
|
|
191
|
+
g.setAttribute("class", "oc-tilemap-legend");
|
|
192
|
+
const defs = parent.querySelector("defs") || createSVGElement("defs");
|
|
193
|
+
const exists = parent.querySelector("defs");
|
|
194
|
+
if (!exists) {
|
|
195
|
+
parent.insertBefore(defs, parent.firstChild);
|
|
196
|
+
}
|
|
197
|
+
const gradientId = nextSvgId("oc-tilemap-legend-gradient");
|
|
198
|
+
const grad = createSVGElement("linearGradient");
|
|
199
|
+
grad.id = gradientId;
|
|
200
|
+
grad.setAttribute("x1", "0%");
|
|
201
|
+
grad.setAttribute("y1", "0%");
|
|
202
|
+
grad.setAttribute("x2", "100%");
|
|
203
|
+
grad.setAttribute("y2", "0%");
|
|
204
|
+
for (const stop of gradientLegend.colorStops) {
|
|
205
|
+
const s = createSVGElement("stop");
|
|
206
|
+
const attrs = {
|
|
207
|
+
offset: `${stop.offset * 100}%`,
|
|
208
|
+
"stop-color": stop.color
|
|
209
|
+
};
|
|
210
|
+
if (stop.opacity !== void 0) {
|
|
211
|
+
attrs["stop-opacity"] = stop.opacity;
|
|
212
|
+
}
|
|
213
|
+
setAttrs(s, attrs);
|
|
214
|
+
grad.appendChild(s);
|
|
215
|
+
}
|
|
216
|
+
defs.appendChild(grad);
|
|
217
|
+
const barHeight = gradientLegend.bounds.height;
|
|
218
|
+
const bar = createSVGElement("rect");
|
|
219
|
+
setAttrs(bar, {
|
|
220
|
+
x: gradientLegend.bounds.x,
|
|
221
|
+
y: gradientLegend.bounds.y,
|
|
222
|
+
width: gradientLegend.bounds.width,
|
|
223
|
+
height: barHeight,
|
|
224
|
+
rx: barHeight / 2,
|
|
225
|
+
fill: `url(#${gradientId})`
|
|
226
|
+
});
|
|
227
|
+
g.appendChild(bar);
|
|
228
|
+
const minText = createSVGElement("text");
|
|
229
|
+
setAttrs(minText, {
|
|
230
|
+
x: gradientLegend.bounds.x,
|
|
231
|
+
y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
|
|
232
|
+
"text-anchor": "start",
|
|
233
|
+
"font-family": gradientLegend.labelStyle.fontFamily,
|
|
234
|
+
"font-size": gradientLegend.labelStyle.fontSize,
|
|
235
|
+
"font-weight": gradientLegend.labelStyle.fontWeight
|
|
236
|
+
});
|
|
237
|
+
minText.style.setProperty(
|
|
238
|
+
"fill",
|
|
239
|
+
gradientLegend.labelStyle.fill
|
|
240
|
+
);
|
|
241
|
+
minText.textContent = gradientLegend.minLabel;
|
|
242
|
+
g.appendChild(minText);
|
|
243
|
+
const maxText = createSVGElement("text");
|
|
244
|
+
setAttrs(maxText, {
|
|
245
|
+
x: gradientLegend.bounds.x + gradientLegend.bounds.width,
|
|
246
|
+
y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
|
|
247
|
+
"text-anchor": "end",
|
|
248
|
+
"font-family": gradientLegend.labelStyle.fontFamily,
|
|
249
|
+
"font-size": gradientLegend.labelStyle.fontSize,
|
|
250
|
+
"font-weight": gradientLegend.labelStyle.fontWeight
|
|
251
|
+
});
|
|
252
|
+
maxText.style.setProperty(
|
|
253
|
+
"fill",
|
|
254
|
+
gradientLegend.labelStyle.fill
|
|
255
|
+
);
|
|
256
|
+
maxText.textContent = gradientLegend.maxLabel;
|
|
257
|
+
g.appendChild(maxText);
|
|
258
|
+
parent.appendChild(g);
|
|
259
|
+
}
|
|
260
|
+
function renderTileMapSVG(layout, opts) {
|
|
261
|
+
const { width, height, tiles, a11y, watermark, animation } = layout;
|
|
262
|
+
const animate = opts?.animate && !!animation?.enter;
|
|
263
|
+
const svg = createSVGElement("svg");
|
|
264
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
265
|
+
svg.setAttribute("role", "img");
|
|
266
|
+
if (a11y.altText) {
|
|
267
|
+
svg.setAttribute("aria-label", a11y.altText);
|
|
268
|
+
}
|
|
269
|
+
const classes = animate ? "oc-tilemap oc-animate" : "oc-tilemap";
|
|
270
|
+
svg.setAttribute("class", classes);
|
|
271
|
+
if (animate && animation?.enter) {
|
|
272
|
+
const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));
|
|
273
|
+
stampAnimationVars(svg, {
|
|
274
|
+
duration: animation.enter.duration,
|
|
275
|
+
stagger,
|
|
276
|
+
annotationDelay: animation.annotationDelay,
|
|
277
|
+
ease: animation.enter.ease
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const defs = createSVGElement("defs");
|
|
281
|
+
svg.appendChild(defs);
|
|
282
|
+
renderChrome(svg, layout);
|
|
283
|
+
renderTiles(svg, tiles, animate ? animation : void 0);
|
|
284
|
+
if (layout.categoricalLegend) {
|
|
285
|
+
renderLegend(svg, layout.categoricalLegend);
|
|
286
|
+
} else {
|
|
287
|
+
renderGradientLegend(svg, layout);
|
|
288
|
+
}
|
|
289
|
+
if (watermark) {
|
|
290
|
+
renderWatermark(svg, layout);
|
|
291
|
+
}
|
|
292
|
+
return svg;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export {
|
|
296
|
+
renderTileMapSVG
|
|
297
|
+
};
|
|
298
|
+
//# sourceMappingURL=chunk-W34KN7FD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tilemap-renderer.ts"],"sourcesContent":["/**\n * TileMap SVG renderer: converts a TileMapLayout into SVG DOM elements.\n *\n * Creates an <svg> with tile rectangles, state code labels, value labels,\n * gradient legend, and chrome. All styling via inline SVG attributes from\n * layout data. Animation is pure CSS, driven by data attributes.\n */\n\nimport type {\n ResolvedAnimation,\n TileMapLayout,\n TileMapTileMark,\n} from '@opendata-ai/openchart-core';\nimport { stampAnimationVars } from './animation-vars';\nimport { renderChromeElement } from './renderers/chrome';\nimport { renderLegend } from './renderers/legend';\nimport { nextSvgId } from './svg-ids';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nfunction 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\n// ---------------------------------------------------------------------------\n// Chrome rendering (delegates to the shared helper for text wrapping)\n// ---------------------------------------------------------------------------\n\nfunction renderChrome(parent: SVGElement, layout: TileMapLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome, measureText } = layout;\n const bottomOffset = layout.area.y + layout.area.height;\n\n if (chrome.title) {\n renderChromeElement(g, chrome.title, 'oc-title', 'title', measureText);\n }\n if (chrome.subtitle) {\n renderChromeElement(g, chrome.subtitle, 'oc-subtitle', 'subtitle', measureText);\n }\n if (chrome.source) {\n renderChromeElement(\n g,\n { ...chrome.source, y: bottomOffset + chrome.source.y },\n 'oc-source',\n 'source',\n measureText,\n );\n }\n if (chrome.byline) {\n renderChromeElement(\n g,\n { ...chrome.byline, y: bottomOffset + chrome.byline.y },\n 'oc-byline',\n 'byline',\n measureText,\n );\n }\n if (chrome.footer) {\n renderChromeElement(\n g,\n { ...chrome.footer, y: bottomOffset + chrome.footer.y },\n 'oc-footer',\n 'footer',\n measureText,\n );\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Watermark rendering\n// ---------------------------------------------------------------------------\n\nfunction renderWatermark(parent: SVGElement, layout: TileMapLayout): void {\n if (layout.width < 480) return; // Don't render if too narrow\n\n const { width, height } = layout;\n const { theme } = layout;\n const padding = theme.spacing.padding;\n const rightEdge = width - padding;\n const bottomEdge = height - padding;\n const fill = theme.colors.axis;\n\n const a = createSVGElement('a');\n a.setAttribute('href', BRAND_URL);\n a.setAttributeNS(XLINK_NS, 'xlink:href', BRAND_URL);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener');\n a.setAttribute('class', 'oc-chrome-ref');\n\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: bottomEdge,\n 'dominant-baseline': 'alphabetic',\n 'text-anchor': 'end',\n 'font-family': theme.fonts.family,\n 'font-size': 12,\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n const trySpan = createSVGElement('tspan');\n setAttrs(trySpan, { 'font-weight': 500 });\n trySpan.textContent = 'try';\n\n const openDataSpan = createSVGElement('tspan');\n setAttrs(openDataSpan, { 'font-weight': 600, 'font-size': 16 });\n openDataSpan.textContent = 'OpenData';\n\n const aiSpan = createSVGElement('tspan');\n setAttrs(aiSpan, { 'font-weight': 500 });\n aiSpan.textContent = '.ai';\n\n text.appendChild(trySpan);\n text.appendChild(openDataSpan);\n text.appendChild(aiSpan);\n a.appendChild(text);\n parent.appendChild(a);\n}\n\n// ---------------------------------------------------------------------------\n// Tiles rendering\n// ---------------------------------------------------------------------------\n\nfunction renderTiles(\n parent: SVGElement,\n tiles: TileMapTileMark[],\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-tilemap-tiles');\n g.setAttribute('role', 'list');\n\n // Compute per-tile delays with jitter for organic feel.\n // Base delay spreads tiles across ~800ms window, jitter adds +-40% variation\n // so some tiles pop in clusters while others have longer gaps.\n const tileDelays: number[] = [];\n if (animation?.enter) {\n const baseStagger = 800 / Math.max(tiles.length, 1);\n let seed = 17;\n for (let i = 0; i < tiles.length; i++) {\n const idx = tiles[i].animationIndex ?? i;\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n const jitter = ((seed % 1000) / 1000 - 0.5) * 0.8;\n tileDelays.push(Math.max(0, Math.round(idx * baseStagger * (1 + jitter))));\n }\n }\n\n for (let i = 0; i < tiles.length; i++) {\n const tile = tiles[i];\n const tileGroup = createSVGElement('g');\n tileGroup.setAttribute('class', 'oc-tilemap-tile');\n tileGroup.setAttribute('data-state', tile.stateCode);\n tileGroup.setAttribute('role', 'listitem');\n if (tile.aria?.label) {\n tileGroup.setAttribute('aria-label', tile.aria.label);\n }\n\n if (animation?.enter) {\n const idx = tile.animationIndex ?? i;\n tileGroup.setAttribute('data-animation-index', String(idx));\n (tileGroup as SVGElement & ElementCSSInlineStyle).style.setProperty(\n '--oc-mark-index',\n String(idx),\n );\n (tileGroup as SVGElement & ElementCSSInlineStyle).style.setProperty(\n '--oc-tile-delay',\n `${tileDelays[i]}ms`,\n );\n }\n\n // Tile background rect\n const rect = createSVGElement('rect');\n setAttrs(rect, {\n x: tile.x,\n y: tile.y,\n width: tile.size,\n height: tile.size,\n rx: tile.cornerRadius,\n fill: tile.fill,\n 'fill-opacity': tile.fillOpacity ?? 1,\n stroke: tile.stroke,\n 'stroke-width': tile.strokeWidth,\n });\n tileGroup.appendChild(rect);\n\n // State code label\n const codeLabel = createSVGElement('text');\n setAttrs(codeLabel, {\n x: tile.label.x,\n y: tile.label.y,\n 'text-anchor': 'middle',\n 'dominant-baseline': 'central',\n 'font-family': tile.label.style.fontFamily,\n 'font-size': tile.label.style.fontSize,\n 'font-weight': tile.label.style.fontWeight,\n });\n (codeLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n tile.label.style.fill,\n );\n codeLabel.textContent = tile.label.text;\n tileGroup.appendChild(codeLabel);\n\n // Value label (if visible)\n if (tile.valueLabel.visible && tile.valueLabel.text) {\n const valueLabel = createSVGElement('text');\n setAttrs(valueLabel, {\n x: tile.valueLabel.x,\n y: tile.valueLabel.y,\n 'text-anchor': 'middle',\n 'dominant-baseline': 'central',\n 'font-family': tile.valueLabel.style.fontFamily,\n 'font-size': tile.valueLabel.style.fontSize,\n 'font-weight': tile.valueLabel.style.fontWeight,\n });\n (valueLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n tile.valueLabel.style.fill,\n );\n valueLabel.textContent = tile.valueLabel.text;\n tileGroup.appendChild(valueLabel);\n }\n\n g.appendChild(tileGroup);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Gradient legend rendering\n// ---------------------------------------------------------------------------\n\nfunction renderGradientLegend(parent: SVGElement, layout: TileMapLayout): void {\n if (!layout.gradientLegend) return;\n\n const { gradientLegend } = layout;\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-tilemap-legend');\n\n // Build linear gradient in defs\n const defs = parent.querySelector('defs') || createSVGElement('defs');\n const exists = parent.querySelector('defs');\n if (!exists) {\n parent.insertBefore(defs, parent.firstChild);\n }\n\n const gradientId = nextSvgId('oc-tilemap-legend-gradient');\n const grad = createSVGElement('linearGradient');\n grad.id = gradientId;\n grad.setAttribute('x1', '0%');\n grad.setAttribute('y1', '0%');\n grad.setAttribute('x2', '100%');\n grad.setAttribute('y2', '0%');\n\n for (const stop of gradientLegend.colorStops) {\n const s = createSVGElement('stop');\n const attrs: Record<string, string | number> = {\n offset: `${stop.offset * 100}%`,\n 'stop-color': stop.color,\n };\n if (stop.opacity !== undefined) {\n attrs['stop-opacity'] = stop.opacity;\n }\n setAttrs(s, attrs);\n grad.appendChild(s);\n }\n\n (defs as SVGElement).appendChild(grad);\n\n // Gradient bar (pill-shaped)\n const barHeight = gradientLegend.bounds.height;\n const bar = createSVGElement('rect');\n setAttrs(bar, {\n x: gradientLegend.bounds.x,\n y: gradientLegend.bounds.y,\n width: gradientLegend.bounds.width,\n height: barHeight,\n rx: barHeight / 2,\n fill: `url(#${gradientId})`,\n });\n g.appendChild(bar);\n\n // Min label\n const minText = createSVGElement('text');\n setAttrs(minText, {\n x: gradientLegend.bounds.x,\n y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,\n 'text-anchor': 'start',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (minText as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n minText.textContent = gradientLegend.minLabel;\n g.appendChild(minText);\n\n // Max label\n const maxText = createSVGElement('text');\n setAttrs(maxText, {\n x: gradientLegend.bounds.x + gradientLegend.bounds.width,\n y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,\n 'text-anchor': 'end',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (maxText as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n maxText.textContent = gradientLegend.maxLabel;\n g.appendChild(maxText);\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Render a TileMapLayout to an SVG element.\n */\nexport function renderTileMapSVG(\n layout: TileMapLayout,\n opts?: { animate?: boolean },\n): SVGSVGElement {\n const { width, height, tiles, a11y, watermark, animation } = layout;\n const animate = opts?.animate && !!animation?.enter;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('role', 'img');\n if (a11y.altText) {\n svg.setAttribute('aria-label', a11y.altText);\n }\n\n const classes = animate ? 'oc-tilemap oc-animate' : 'oc-tilemap';\n svg.setAttribute('class', classes);\n\n if (animate && animation?.enter) {\n // Target ~1s total: stagger window ~800ms + per-tile pop ~200ms\n const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));\n stampAnimationVars(svg, {\n duration: animation.enter.duration,\n stagger,\n annotationDelay: animation.annotationDelay,\n ease: animation.enter.ease,\n });\n }\n\n // Empty defs element (will be filled by gradient legend)\n const defs = createSVGElement('defs');\n svg.appendChild(defs);\n\n // Render chrome\n renderChrome(svg, layout);\n\n // Render tiles\n renderTiles(svg, tiles, animate ? animation : undefined);\n\n // Render legend (gradient for quantitative, categorical for nominal)\n if (layout.categoricalLegend) {\n renderLegend(svg, layout.categoricalLegend);\n } else {\n renderGradientLegend(svg, layout);\n }\n\n // Render watermark\n if (watermark) {\n renderWatermark(svg, layout);\n }\n\n return svg;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,82 +1,9 @@
|
|
|
1
1
|
export { ChartLayout, ChartSpec, CompileOptions, TableLayout, TableSpec, VizSpec } from '@opendata-ai/openchart-engine';
|
|
2
|
-
import { BarListSpec, BarListLayout, ThemeConfig, DarkMode, GraphSpec,
|
|
2
|
+
import { BarListSpec, BarListLayout, ThemeConfig, DarkMode, GraphSpec, MapSpec, MapLayout, RectMark, Mark, BarTableCell, CategoryTableCell, TableCell, FlagTableCell, HeatmapTableCell, ImageTableCell, SparklineTableCell, TextTableCell, SankeySpec, SankeyLayout, ChartLayout, TableSpec, SortState, TableLayout, TileMapSpec, TileMapLayout, TooltipContent, ResolvedYouDrawIt } from '@opendata-ai/openchart-core';
|
|
3
|
+
import { J as JPGExportOptions, S as SVGExportOptions, C as Camera } from './mount-CUKQLeqD.js';
|
|
4
|
+
export { a as ChartInstance, E as ExportOptions, M as MountOptions, P as PNGExportOptions, U as UpdateOptions, c as createChart, e as exportCSV, b as exportJPG, d as exportPNG, f as exportSVG, g as exportSVGWithFonts } from './mount-CUKQLeqD.js';
|
|
3
5
|
import { Placement } from '@floating-ui/dom';
|
|
4
6
|
|
|
5
|
-
/**
|
|
6
|
-
* Export utilities: serialize charts to SVG, PNG, JPG, or CSV.
|
|
7
|
-
*
|
|
8
|
-
* - SVG: serializes the rendered DOM element via XMLSerializer
|
|
9
|
-
* - SVG with fonts: async version that embeds @font-face data URIs
|
|
10
|
-
* - PNG: renders SVG to canvas, then extracts as Blob
|
|
11
|
-
* - JPG: same as PNG but with JPEG compression and background fill
|
|
12
|
-
* - CSV: converts a data array to comma-separated text
|
|
13
|
-
*/
|
|
14
|
-
interface SVGExportOptions {
|
|
15
|
-
/** Embed fonts as base64 data URIs in the SVG. Defaults to true. */
|
|
16
|
-
embedFonts?: boolean;
|
|
17
|
-
}
|
|
18
|
-
interface PNGExportOptions extends SVGExportOptions {
|
|
19
|
-
/** DPI scaling factor. Defaults to 2 for retina-quality output. */
|
|
20
|
-
dpi?: number;
|
|
21
|
-
}
|
|
22
|
-
interface JPGExportOptions extends PNGExportOptions {
|
|
23
|
-
/** JPEG quality from 0 to 1. Defaults to 0.92. */
|
|
24
|
-
quality?: number;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Serialize an SVG element to an XML string.
|
|
28
|
-
*
|
|
29
|
-
* @param svgElement - The rendered SVG element to serialize.
|
|
30
|
-
* @returns The SVG markup as a string.
|
|
31
|
-
*/
|
|
32
|
-
declare function exportSVG(svgElement: SVGElement): string;
|
|
33
|
-
/**
|
|
34
|
-
* Serialize an SVG element with embedded fonts to an XML string.
|
|
35
|
-
*
|
|
36
|
-
* Collects font-family declarations from the SVG's text elements,
|
|
37
|
-
* finds matching @font-face rules in the page's stylesheets, fetches
|
|
38
|
-
* the font files, and embeds them as base64 data URIs. The resulting
|
|
39
|
-
* SVG is self-contained and renders correctly without external fonts.
|
|
40
|
-
*
|
|
41
|
-
* @param svgElement - The rendered SVG element to serialize.
|
|
42
|
-
* @param options - Export options.
|
|
43
|
-
* @returns A Promise resolving to the SVG markup as a string.
|
|
44
|
-
*/
|
|
45
|
-
declare function exportSVGWithFonts(svgElement: SVGElement, options?: SVGExportOptions): Promise<string>;
|
|
46
|
-
/**
|
|
47
|
-
* Render an SVG element to a PNG Blob via a canvas.
|
|
48
|
-
*
|
|
49
|
-
* Embeds fonts by default so the exported image matches on-screen rendering.
|
|
50
|
-
* Set `embedFonts: false` to skip font embedding for faster exports.
|
|
51
|
-
*
|
|
52
|
-
* @param svgElement - The rendered SVG element.
|
|
53
|
-
* @param options - Optional DPI scaling and font embedding.
|
|
54
|
-
* @returns A Promise resolving to the PNG Blob.
|
|
55
|
-
*/
|
|
56
|
-
declare function exportPNG(svgElement: SVGElement, options?: PNGExportOptions): Promise<Blob>;
|
|
57
|
-
/**
|
|
58
|
-
* Render an SVG element to a JPEG Blob via a canvas.
|
|
59
|
-
*
|
|
60
|
-
* Same pipeline as exportPNG but outputs JPEG with configurable quality.
|
|
61
|
-
* The canvas is filled with the chart's background color before drawing
|
|
62
|
-
* to avoid transparent backgrounds rendering as black in JPEG format.
|
|
63
|
-
*
|
|
64
|
-
* @param svgElement - The rendered SVG element.
|
|
65
|
-
* @param options - Optional DPI scaling, JPEG quality, and font embedding.
|
|
66
|
-
* @returns A Promise resolving to the JPEG Blob.
|
|
67
|
-
*/
|
|
68
|
-
declare function exportJPG(svgElement: SVGElement, options?: JPGExportOptions): Promise<Blob>;
|
|
69
|
-
/**
|
|
70
|
-
* Convert an array of data objects to a CSV string.
|
|
71
|
-
*
|
|
72
|
-
* Uses the keys from the first row as column headers.
|
|
73
|
-
* Values are quoted if they contain commas, quotes, or newlines.
|
|
74
|
-
*
|
|
75
|
-
* @param data - Array of row objects.
|
|
76
|
-
* @returns CSV-formatted string.
|
|
77
|
-
*/
|
|
78
|
-
declare function exportCSV(data: Record<string, unknown>[]): string;
|
|
79
|
-
|
|
80
7
|
/**
|
|
81
8
|
* BarList mount API: the main entry point for vanilla JS barlist usage.
|
|
82
9
|
*
|
|
@@ -171,65 +98,86 @@ interface GraphInstance {
|
|
|
171
98
|
declare function createGraph(container: HTMLElement, spec: GraphSpec, options?: GraphMountOptions): GraphInstance;
|
|
172
99
|
|
|
173
100
|
/**
|
|
174
|
-
*
|
|
101
|
+
* Map camera utilities: zoom, pan, and focus for map visualizations.
|
|
102
|
+
*
|
|
103
|
+
* Reuses the existing camera-math infrastructure from the scrollytelling
|
|
104
|
+
* system. The camera transform is applied as an SVG `transform` attribute
|
|
105
|
+
* on the `[data-oc-map-camera]` group, never as a CSS transform.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
interface MapCameraOptions {
|
|
109
|
+
/** Transition duration in ms. 0 snaps instantly. Default 600. */
|
|
110
|
+
duration?: number;
|
|
111
|
+
/** Padding in map-local units around the target. Default 16. */
|
|
112
|
+
padding?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Map mount API: the main entry point for vanilla JS map usage.
|
|
175
117
|
*
|
|
176
|
-
*
|
|
177
|
-
* renders it as SVG, sets up responsive resizing, tooltip interaction
|
|
178
|
-
*
|
|
179
|
-
* and returns a ChartInstance with update/resize/export/destroy methods.
|
|
118
|
+
* createMap() takes a container, MapSpec, and options, compiles the
|
|
119
|
+
* map, renders it as SVG, sets up responsive resizing, tooltip interaction,
|
|
120
|
+
* and returns a MapInstance with update/resize/export/destroy.
|
|
180
121
|
*/
|
|
181
122
|
|
|
182
|
-
interface
|
|
123
|
+
interface MapMountOptions {
|
|
183
124
|
/** Theme overrides. */
|
|
184
125
|
theme?: ThemeConfig;
|
|
185
126
|
/** Dark mode setting: "auto" (system pref), "force", or "off". */
|
|
186
127
|
darkMode?: DarkMode;
|
|
187
|
-
/** Callback when a data point is clicked. @deprecated Use onMarkClick instead. */
|
|
188
|
-
onDataPointClick?: (data: Record<string, unknown>) => void;
|
|
189
128
|
/** Enable responsive resizing. Defaults to true. */
|
|
190
129
|
responsive?: boolean;
|
|
191
130
|
/** Show the tryOpenData.ai watermark. Defaults to true. */
|
|
192
131
|
watermark?: boolean;
|
|
193
|
-
/**
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
132
|
+
/** Show tooltips on hover. Defaults to true. */
|
|
133
|
+
tooltip?: boolean;
|
|
134
|
+
/** Callback when a feature is clicked. */
|
|
135
|
+
onMarkClick?: (feature: {
|
|
136
|
+
id: string | number;
|
|
137
|
+
name?: string;
|
|
138
|
+
data: Record<string, unknown> | null;
|
|
139
|
+
}) => void;
|
|
140
|
+
/** Callback when a feature is hovered (null on mouse leave). */
|
|
141
|
+
onMarkHover?: (feature: {
|
|
142
|
+
id: string | number;
|
|
143
|
+
name?: string;
|
|
144
|
+
data: Record<string, unknown> | null;
|
|
145
|
+
} | null) => void;
|
|
201
146
|
}
|
|
202
|
-
interface
|
|
147
|
+
interface MapInstance {
|
|
203
148
|
/** Re-compile and re-render with a new spec. */
|
|
204
|
-
update(spec:
|
|
149
|
+
update(spec: MapSpec): void;
|
|
205
150
|
/** Re-compile at current container dimensions. */
|
|
206
151
|
resize(): void;
|
|
207
|
-
/** Export the
|
|
208
|
-
export(format: 'svg'): string
|
|
209
|
-
export(format: 'svg-with-fonts', options?: SVGExportOptions): Promise<string>;
|
|
210
|
-
export(format: 'png', options?: ExportOptions): Promise<Blob>;
|
|
211
|
-
export(format: 'jpg', options?: ExportOptions): Promise<Blob>;
|
|
212
|
-
export(format: 'csv'): string;
|
|
213
|
-
export(format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg' | 'csv', options?: ExportOptions): string | Promise<Blob> | Promise<string>;
|
|
152
|
+
/** Export the map. */
|
|
153
|
+
export(format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg', options?: JPGExportOptions | SVGExportOptions): string | Promise<Blob> | Promise<string>;
|
|
214
154
|
/** Remove all DOM elements and disconnect observers. */
|
|
215
155
|
destroy(): void;
|
|
216
|
-
/** The current compiled layout
|
|
217
|
-
readonly layout:
|
|
218
|
-
/**
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
156
|
+
/** The current compiled layout. */
|
|
157
|
+
readonly layout: MapLayout;
|
|
158
|
+
/** Zoom to one or more features by ID. */
|
|
159
|
+
zoomTo(featureId: string | number | Array<string | number>, opts?: MapCameraOptions): void;
|
|
160
|
+
/** Pan to a feature keeping the current zoom level. */
|
|
161
|
+
panTo(featureId: string | number, opts?: MapCameraOptions): void;
|
|
162
|
+
/** Fit the camera to an arbitrary bounding box, or reset to full view if no target. */
|
|
163
|
+
fitBounds(target?: {
|
|
164
|
+
x: number;
|
|
165
|
+
y: number;
|
|
166
|
+
width: number;
|
|
167
|
+
height: number;
|
|
168
|
+
padding?: number;
|
|
169
|
+
}, opts?: MapCameraOptions): void;
|
|
170
|
+
/** Reset the camera to the full map view. */
|
|
171
|
+
resetView(opts?: MapCameraOptions): void;
|
|
172
|
+
/** Get a snapshot of the current camera state. */
|
|
173
|
+
getCamera(): Camera;
|
|
174
|
+
/** Set the camera to an exact state (no animation). */
|
|
175
|
+
setCamera(camera: Camera): void;
|
|
228
176
|
}
|
|
229
177
|
/**
|
|
230
|
-
* Create a
|
|
178
|
+
* Create a map instance from a spec and mount it into a container.
|
|
231
179
|
*/
|
|
232
|
-
declare function
|
|
180
|
+
declare function createMap(container: HTMLElement, spec: MapSpec, options?: MapMountOptions): MapInstance;
|
|
233
181
|
|
|
234
182
|
/**
|
|
235
183
|
* Mark rendering: dispatches to per-mark-type sub-renderers (line, area, rect,
|
|
@@ -573,4 +521,50 @@ interface TooltipManager {
|
|
|
573
521
|
*/
|
|
574
522
|
declare function createTooltipManager(container: HTMLElement): TooltipManager;
|
|
575
523
|
|
|
576
|
-
|
|
524
|
+
/**
|
|
525
|
+
* "You draw it" interaction layer: the DOM/SVG overlay for the `youDrawIt`
|
|
526
|
+
* spec option (NYT-style draw-then-reveal engagement format).
|
|
527
|
+
*
|
|
528
|
+
* SVG-space elements (hatched drawing region, reader's guess path, reveal
|
|
529
|
+
* clip rect, pointer-capture overlay) render inside the chart's own SVG so
|
|
530
|
+
* they clip and scale with it. The prompt text and skip-to-reveal button are
|
|
531
|
+
* a small HTML overlay positioned over the drawing region, following the
|
|
532
|
+
* series-search precedent (`series-search.ts`): a native `<button>` gets
|
|
533
|
+
* keyboard semantics for free instead of hand-rolling SVG focus handling.
|
|
534
|
+
*
|
|
535
|
+
* State (the reader's in-progress guess) lives in this module's closure,
|
|
536
|
+
* matching mount.ts's per-instance-closure convention (StrictMode double-
|
|
537
|
+
* mount safe): a fresh `createYouDrawIt()` call gets a fresh closure.
|
|
538
|
+
*/
|
|
539
|
+
|
|
540
|
+
interface YouDrawItOptions {
|
|
541
|
+
/** The chart container (`.oc-root`). Must be position: relative. */
|
|
542
|
+
container: HTMLElement;
|
|
543
|
+
/** Fired once the drawing is revealed, with the guess in data coordinates ordered by x. */
|
|
544
|
+
onReveal?: (guess: Array<{
|
|
545
|
+
x: string | number;
|
|
546
|
+
y: number;
|
|
547
|
+
}>) => void;
|
|
548
|
+
}
|
|
549
|
+
interface YouDrawItController {
|
|
550
|
+
/** Reposition, resync geometry, and (re)wire pointer capture against a freshly rendered layout + SVG. */
|
|
551
|
+
update(config: ResolvedYouDrawIt, svg: SVGSVGElement): void;
|
|
552
|
+
/** Hide (spec no longer wants youDrawIt on this render, e.g. mark type changed). */
|
|
553
|
+
hide(): void;
|
|
554
|
+
/** Reveal the real line and fire onReveal, without requiring reader interaction (skip-to-reveal). */
|
|
555
|
+
reveal(): void;
|
|
556
|
+
/** Clear the reader's guess and return to the drawing state. */
|
|
557
|
+
reset(): void;
|
|
558
|
+
/** Whether the drawing has been revealed. */
|
|
559
|
+
readonly isRevealed: boolean;
|
|
560
|
+
/** Remove all DOM elements. */
|
|
561
|
+
destroy(): void;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Create the you-draw-it control. Mounted once per chart instance and kept
|
|
565
|
+
* across re-renders so the in-progress drawing survives; `update()` re-syncs
|
|
566
|
+
* geometry and re-wires pointer capture after each render.
|
|
567
|
+
*/
|
|
568
|
+
declare function createYouDrawIt(options: YouDrawItOptions): YouDrawItController;
|
|
569
|
+
|
|
570
|
+
export { type BarListInstance, type BarListMountOptions, Camera, type GraphInstance, type GraphMountOptions, JPGExportOptions, type KeyboardNavOptions, type MapCameraOptions, type MapInstance, type MapMountOptions, SVGExportOptions, type SankeyInstance, type SankeyMountOptions, type TableInstance, type TableMountOptions, type TableState, type TextEditOverlayConfig, type TileMapInstance, type TileMapMountOptions, type TooltipManager, type YouDrawItController, type YouDrawItOptions, attachKeyboardNav, createBarList, createGraph, createMap, createSankey, createSimulationWorker, createTable, createTextEditOverlay, createTileMap, createTooltipManager, createYouDrawIt, observeResize, rectPathWithCorners, registerMarkRenderer, renderBarCell, renderCategoryCell, renderCell, renderChartSVG, renderFlagCell, renderHeatmapCell, renderImageCell, renderSparklineCell, renderTable, renderTextCell };
|