@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.
Files changed (79) hide show
  1. package/dist/{chunk-EQ6DYFO7.js → chunk-5EOV5JQZ.js} +431 -538
  2. package/dist/chunk-5EOV5JQZ.js.map +1 -0
  3. package/dist/chunk-ENXSUXC5.js +6513 -0
  4. package/dist/chunk-ENXSUXC5.js.map +1 -0
  5. package/dist/chunk-W34KN7FD.js +298 -0
  6. package/dist/chunk-W34KN7FD.js.map +1 -0
  7. package/dist/index.d.ts +110 -116
  8. package/dist/index.js +189 -4408
  9. package/dist/index.js.map +1 -1
  10. package/dist/mount-CUKQLeqD.d.ts +207 -0
  11. package/dist/static.js +16 -11
  12. package/dist/static.js.map +1 -1
  13. package/dist/story/index.d.ts +277 -0
  14. package/dist/story/index.js +537 -0
  15. package/dist/story/index.js.map +1 -0
  16. package/dist/styles.css +2 -1
  17. package/dist/styles.css.map +1 -0
  18. package/package.json +9 -3
  19. package/src/__test-fixtures__/specs.ts +1 -1
  20. package/src/__tests__/annotations-render.test.ts +201 -0
  21. package/src/__tests__/continuous-legend.test.ts +91 -0
  22. package/src/__tests__/edit-events.test.ts +155 -0
  23. package/src/__tests__/map.test.ts +293 -0
  24. package/src/__tests__/mount.test.ts +26 -0
  25. package/src/__tests__/pattern-fills.test.ts +105 -0
  26. package/src/__tests__/series-search.test.ts +311 -0
  27. package/src/__tests__/table-mount.test.ts +34 -0
  28. package/src/__tests__/tooltip.test.ts +26 -0
  29. package/src/__tests__/transition.test.ts +196 -11
  30. package/src/__tests__/you-draw-it.test.ts +273 -0
  31. package/src/animation-vars.ts +42 -0
  32. package/src/barlist-renderer.ts +2 -2
  33. package/src/dom-helpers.ts +17 -0
  34. package/src/export.ts +33 -16
  35. package/src/index.ts +7 -0
  36. package/src/interactions/editing-drags.ts +9 -6
  37. package/src/interactions/legend-interaction.ts +9 -1
  38. package/src/interactions/selection.ts +12 -3
  39. package/src/map-camera.ts +88 -0
  40. package/src/map-mount.ts +701 -0
  41. package/src/map-renderer.ts +338 -0
  42. package/src/map-transition.ts +83 -0
  43. package/src/mount.ts +212 -26
  44. package/src/pattern-utils.ts +110 -0
  45. package/src/renderers/annotations.ts +162 -117
  46. package/src/renderers/axes.ts +13 -6
  47. package/src/renderers/brand.ts +7 -2
  48. package/src/renderers/chrome.ts +32 -10
  49. package/src/renderers/legend.ts +171 -2
  50. package/src/renderers/marks.ts +33 -5
  51. package/src/sankey-renderer.ts +7 -11
  52. package/src/series-search.ts +392 -0
  53. package/src/static.ts +13 -8
  54. package/src/story/__tests__/camera-math.test.ts +145 -0
  55. package/src/story/__tests__/create-chart-story.test.ts +317 -0
  56. package/src/story/__tests__/progress-math.test.ts +95 -0
  57. package/src/story/__tests__/scroll-driver.test.ts +130 -0
  58. package/src/story/__tests__/tween.test.ts +159 -0
  59. package/src/story/camera-math.ts +125 -0
  60. package/src/story/create-chart-story.ts +353 -0
  61. package/src/story/crossfade.ts +70 -0
  62. package/src/story/index.ts +39 -0
  63. package/src/story/progress-math.ts +85 -0
  64. package/src/story/resolve-camera-target.ts +119 -0
  65. package/src/story/scroll-driver.ts +206 -0
  66. package/src/story/story-camera.ts +49 -0
  67. package/src/story/tween.ts +112 -0
  68. package/src/story/types.ts +76 -0
  69. package/src/svg-renderer.ts +60 -29
  70. package/src/table-mount.ts +12 -3
  71. package/src/table-renderer.ts +9 -28
  72. package/src/text-edit-overlay.ts +1 -1
  73. package/src/theme-tokens.ts +4 -4
  74. package/src/tilemap-mount.ts +27 -6
  75. package/src/tilemap-renderer.ts +30 -88
  76. package/src/tooltip.ts +11 -0
  77. package/src/transition.ts +376 -33
  78. package/src/you-draw-it.ts +480 -0
  79. 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, ChartSpec, LayerSpec, ElementRef, ChartLayout, ChartEventHandlers, DataRow, RectMark, Mark, BarTableCell, CategoryTableCell, TableCell, FlagTableCell, HeatmapTableCell, ImageTableCell, SparklineTableCell, TextTableCell, SankeySpec, SankeyLayout, TableSpec, SortState, TableLayout, TileMapSpec, TileMapLayout, TooltipContent } from '@opendata-ai/openchart-core';
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
- * Mount API: the main entry point for vanilla JS usage.
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
- * createChart() takes a container, spec, and options, compiles the chart,
177
- * renders it as SVG, sets up responsive resizing, tooltip interaction
178
- * (mouse/touch/keyboard), keyboard navigation between data points,
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 MountOptions extends ChartEventHandlers {
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
- /** Initial selected element. */
194
- selectedElement?: ElementRef;
195
- }
196
- interface UpdateOptions {
197
- /** Override the selected element after update. When omitted, preserves current selection. */
198
- selectedElement?: ElementRef;
199
- }
200
- interface ExportOptions extends JPGExportOptions {
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 ChartInstance {
147
+ interface MapInstance {
203
148
  /** Re-compile and re-render with a new spec. */
204
- update(spec: ChartSpec | LayerSpec | GraphSpec, options?: UpdateOptions): void;
149
+ update(spec: MapSpec): void;
205
150
  /** Re-compile at current container dimensions. */
206
151
  resize(): void;
207
- /** Export the chart. */
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 (for hooks / debugging). */
217
- readonly layout: ChartLayout;
218
- /** Get the currently selected element, or null if none. */
219
- getSelectedElement(): ElementRef | null;
220
- /** Programmatically select an element. Silent no-op if element not found. */
221
- select(ref: ElementRef): void;
222
- /** Deselect the current element. */
223
- deselect(): void;
224
- /** Whether inline text editing is active. */
225
- readonly isEditing: boolean;
226
- /** Set highlight values on the color encoding and re-render. Pass null to clear. */
227
- setHighlight(values: string[] | null): void;
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 chart instance from a spec and mount it into a container.
178
+ * Create a map instance from a spec and mount it into a container.
231
179
  */
232
- declare function createChart<TData extends DataRow = DataRow>(container: HTMLElement, spec: ChartSpec<TData> | LayerSpec<TData> | GraphSpec, options?: MountOptions): ChartInstance;
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
- export { type BarListInstance, type BarListMountOptions, type ChartInstance, type ExportOptions, type GraphInstance, type GraphMountOptions, type JPGExportOptions, type KeyboardNavOptions, type MountOptions, type PNGExportOptions, type SVGExportOptions, type SankeyInstance, type SankeyMountOptions, type TableInstance, type TableMountOptions, type TableState, type TextEditOverlayConfig, type TileMapInstance, type TileMapMountOptions, type TooltipManager, type UpdateOptions, attachKeyboardNav, createBarList, createChart, createGraph, createSankey, createSimulationWorker, createTable, createTextEditOverlay, createTileMap, createTooltipManager, exportCSV, exportJPG, exportPNG, exportSVG, exportSVGWithFonts, observeResize, rectPathWithCorners, registerMarkRenderer, renderBarCell, renderCategoryCell, renderCell, renderChartSVG, renderFlagCell, renderHeatmapCell, renderImageCell, renderSparklineCell, renderTable, renderTextCell };
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 };