@opendata-ai/openchart-vanilla 8.8.0 → 8.9.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.
@@ -3,7 +3,7 @@ import {
3
3
  renderChromeElement,
4
4
  renderLegend,
5
5
  stampAnimationVars
6
- } from "./chunk-SXTHPXG6.js";
6
+ } from "./chunk-TP3GCWCO.js";
7
7
 
8
8
  // src/tilemap-renderer.ts
9
9
  var SVG_NS = "http://www.w3.org/2000/svg";
@@ -304,6 +304,7 @@ function renderTileMapSVG(layout, opts) {
304
304
  const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));
305
305
  stampAnimationVars(svg, {
306
306
  duration: animation.enter.duration,
307
+ spring: animation.enter.spring,
307
308
  stagger,
308
309
  annotationDelay: animation.annotationDelay,
309
310
  ease: animation.enter.ease
@@ -327,4 +328,4 @@ function renderTileMapSVG(layout, opts) {
327
328
  export {
328
329
  renderTileMapSVG
329
330
  };
330
- //# sourceMappingURL=chunk-EZQDCLZN.js.map
331
+ //# sourceMappingURL=chunk-YHZAH6EN.js.map
@@ -1 +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 // A hair of tracking is what keeps a two-letter code legible at 8px now\n // that it is set at 600 instead of 700.\n 'letter-spacing': '0.02em',\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: square-cornered, so it reads as a color scale rather than a\n // pill-shaped UI control.\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 fill: `url(#${gradientId})`,\n 'shape-rendering': 'crispEdges',\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 // Detached \"no data\" swatch, keyed beside the ramp rather than at its low end.\n if (gradientLegend.noData) {\n const nd = gradientLegend.noData;\n const swatch = createSVGElement('rect');\n swatch.setAttribute('class', 'oc-legend-nodata');\n setAttrs(swatch, {\n x: nd.x,\n y: nd.y,\n width: nd.size,\n height: nd.size,\n fill: nd.fill,\n 'shape-rendering': 'crispEdges',\n });\n g.appendChild(swatch);\n\n const ndLabel = createSVGElement('text');\n setAttrs(ndLabel, {\n x: nd.labelX,\n y: nd.labelY,\n 'text-anchor': 'start',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (ndLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n ndLabel.textContent = nd.label;\n g.appendChild(ndLabel);\n }\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":[]}
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 // A hair of tracking is what keeps a two-letter code legible at 8px now\n // that it is set at 600 instead of 700.\n 'letter-spacing': '0.02em',\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: square-cornered, so it reads as a color scale rather than a\n // pill-shaped UI control.\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 fill: `url(#${gradientId})`,\n 'shape-rendering': 'crispEdges',\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 // Detached \"no data\" swatch, keyed beside the ramp rather than at its low end.\n if (gradientLegend.noData) {\n const nd = gradientLegend.noData;\n const swatch = createSVGElement('rect');\n swatch.setAttribute('class', 'oc-legend-nodata');\n setAttrs(swatch, {\n x: nd.x,\n y: nd.y,\n width: nd.size,\n height: nd.size,\n fill: nd.fill,\n 'shape-rendering': 'crispEdges',\n });\n g.appendChild(swatch);\n\n const ndLabel = createSVGElement('text');\n setAttrs(ndLabel, {\n x: nd.labelX,\n y: nd.labelY,\n 'text-anchor': 'start',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (ndLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n ndLabel.textContent = nd.label;\n g.appendChild(ndLabel);\n }\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 spring: animation.enter.spring,\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":[]}
@@ -1,4 +1,4 @@
1
- import { ResolvedTheme, AnimationEase, ResolvedAnimation } from '@opendata-ai/openchart-core';
1
+ import { ResolvedTheme, AnimationEase, MotionPhase, ResolvedAnimation } from '@opendata-ai/openchart-core';
2
2
 
3
3
  /**
4
4
  * Animated GIF export.
@@ -98,7 +98,7 @@ declare function resolveRepeat(loop: boolean | number | undefined): number;
98
98
  * Apply the interpolated entrance state for a target at absolute time `nowMs`,
99
99
  * writing inline `clip-path`/`opacity` so it survives serialization.
100
100
  */
101
- declare function applyFrameState(target: AnimatedTarget, nowMs: number, easeKind: AnimationEase): void;
101
+ declare function applyFrameState(target: AnimatedTarget, nowMs: number, easeKind: AnimationEase, phase?: MotionPhase): void;
102
102
  /**
103
103
  * Render a chart's entrance animation to an animated GIF Blob.
104
104
  *
@@ -6,8 +6,8 @@ import {
6
6
  exportGIF,
7
7
  isModuleNotFound,
8
8
  resolveRepeat
9
- } from "./chunk-DFKLAB67.js";
10
- import "./chunk-I73B3DJI.js";
9
+ } from "./chunk-MGFXAROR.js";
10
+ import "./chunk-R5TZHNTG.js";
11
11
  import "./chunk-SDJHUELU.js";
12
12
  export {
13
13
  EASE_SMOOTH_SAMPLES,
@@ -14,7 +14,7 @@ import {
14
14
  reheatAlpha,
15
15
  resolveHighlightTarget,
16
16
  seedNodePositions
17
- } from "../chunk-5IMVUHVC.js";
17
+ } from "../chunk-YBVDBY6P.js";
18
18
  import {
19
19
  AnimationScheduler,
20
20
  createTween,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { ChartLayout, ChartSpec, CompileOptions, TableLayout, TableSpec, VizSpec } from '@opendata-ai/openchart-engine';
2
- import { BarListSpec, BarListLayout, ThemeConfig, DarkMode, ChartLayout, ChartSpec, LayerSpec, GeoMapSpec, GeoMapLayout, RectMark, Mark, BarTableCell, CategoryTableCell, TableCell, FlagTableCell, HeatmapTableCell, ImageTableCell, SparklineTableCell, TextTableCell, SankeySpec, SankeyLayout, TableSpec, SortState, TableLayout, TileMapSpec, TileMapLayout, ResolvedYouDrawIt } from '@opendata-ai/openchart-core';
3
- import { J as JPGExportOptions, S as SVGExportOptions, C as Camera } from './mount-WiY9BIr9.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-WiY9BIr9.js';
2
+ import { BarListSpec, BarListLayout, ThemeConfig, DarkMode, ChartLayout, ChartSpec, LayerSpec, GeoMapSpec, GeoMapLayout, ReaderSelection, DataRow, RectMark, Mark, BarTableCell, CategoryTableCell, TableCell, FlagTableCell, HeatmapTableCell, ImageTableCell, SparklineTableCell, TextTableCell, SankeySpec, SankeyLayout, TableSpec, SortState, TableLayout, TileMapSpec, TileMapLayout, ResolvedYouDrawIt } from '@opendata-ai/openchart-core';
3
+ import { J as JPGExportOptions, S as SVGExportOptions, C as Camera, a as ChartInstance } from './mount-FmUdKlCq.js';
4
+ export { 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-FmUdKlCq.js';
5
5
  export { C as CameraFlightOptions, G as GRAPH_3D_NOT_REGISTERED_ERROR, a as GraphCamera, b as GraphFlyTarget, c as GraphHighlightTarget, d as GraphInstance, e as GraphLegendData, f as GraphMountOptions, g as GraphRendererContext, h as GraphRendererFactory, i as GraphShell, j as GraphTooltipFormatter, k as GraphTooltipItem, T as TooltipManager, l as createGraph, m as createTooltipManager, r as registerGraphRenderer } from './renderer-registry-Cng76n8F.js';
6
6
  import './export-gif.js';
7
7
  import '@floating-ui/dom';
@@ -269,6 +269,21 @@ declare const DEFAULT_UPDATE_MAX_MARKS = 500;
269
269
  */
270
270
  declare const CANVAS_DEFAULT_UPDATE_MAX_MARKS = 20000;
271
271
 
272
+ interface ReaderController {
273
+ readonly selection: ReaderSelection;
274
+ /** Call after chart.update(). Without an override, visibility follows compiled marks. */
275
+ update(spec: ChartSpec | LayerSpec, visibleKeys?: ReadonlySet<string>): void;
276
+ pin(datum: DataRow): void;
277
+ clear(): void;
278
+ destroy(): void;
279
+ }
280
+ /**
281
+ * Persistent reader interaction, separate from editor ElementRef selection.
282
+ * Layered specs clear/disable inspection and announce the unsupported binding;
283
+ * updating back to a single chart resumes inspection without choosing a leaf.
284
+ */
285
+ declare function createReader(container: HTMLElement, instance: ChartInstance, initial: ChartSpec | LayerSpec, onChange: (selection: ReaderSelection) => void): ReaderController;
286
+
272
287
  /**
273
288
  * Mark rendering: dispatches to per-mark-type sub-renderers (line, area, rect,
274
289
  * arc, point, text, rule, tick).
@@ -372,7 +387,7 @@ interface SankeyInstance {
372
387
  /** Re-compile at current container dimensions. */
373
388
  resize(): void;
374
389
  /** Export the sankey diagram. */
375
- export(format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg', options?: JPGExportOptions): string | Promise<Blob> | Promise<string>;
390
+ export(format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg', options?: JPGExportOptions | SVGExportOptions): string | Promise<Blob> | Promise<string>;
376
391
  /** Remove all DOM elements and disconnect observers. */
377
392
  destroy(): void;
378
393
  /** The current compiled layout. */
@@ -417,8 +432,15 @@ declare function renderChartSVG(layout: ChartLayout, container: HTMLElement, opt
417
432
  interface KeyboardNavOptions {
418
433
  /** The wrapper element containing the whole table UI. */
419
434
  wrapper: HTMLElement;
435
+ /** Logical cell to restore after a resize, sort, or data update. */
436
+ initialCell?: {
437
+ rowId: string;
438
+ columnKey: string;
439
+ };
420
440
  /** Callback to trigger sort on a column. */
421
441
  onSort: (columnKey: string) => void;
442
+ /** Activate the focused row through the host's existing row-click callback. */
443
+ onRowActivate?: (rowId: string) => void;
422
444
  /** Callback to clear search and return focus to the table body. */
423
445
  onClearSearch: () => void;
424
446
  /** Callback to announce text to screen readers via the live region. */
@@ -632,4 +654,4 @@ interface YouDrawItController {
632
654
  */
633
655
  declare function createYouDrawIt(options: YouDrawItOptions): YouDrawItController;
634
656
 
635
- export { type BarListInstance, type BarListMountOptions, CANVAS_DEFAULT_UPDATE_MAX_MARKS, Camera, DEFAULT_UPDATE_MAX_MARKS, type GeoMapCameraOptions, type GeoMapInstance, type GeoMapMarkEvent, type GeoMapMountOptions, JPGExportOptions, type KeyboardNavOptions, SVGExportOptions, type SankeyInstance, type SankeyMountOptions, type SpecSequenceOptions, type TableInstance, type TableMountOptions, type TableState, type TextEditOverlayConfig, type TileMapInstance, type TileMapMountOptions, VECTOR_EXPORT_MAX_POINTS, type YouDrawItController, type YouDrawItOptions, attachKeyboardNav, createBarList, createGeoMap, createSankey, createSimulationWorker, createTable, createTextEditOverlay, createTileMap, createYouDrawIt, exportSpecSequence, materializeCanvasModeSVG, observeResize, rectPathWithCorners, registerMarkRenderer, renderBarCell, renderCategoryCell, renderCell, renderChartSVG, renderFlagCell, renderHeatmapCell, renderImageCell, renderSparklineCell, renderTable, renderTextCell };
657
+ export { type BarListInstance, type BarListMountOptions, CANVAS_DEFAULT_UPDATE_MAX_MARKS, Camera, ChartInstance, DEFAULT_UPDATE_MAX_MARKS, type GeoMapCameraOptions, type GeoMapInstance, type GeoMapMarkEvent, type GeoMapMountOptions, JPGExportOptions, type KeyboardNavOptions, type ReaderController, SVGExportOptions, type SankeyInstance, type SankeyMountOptions, type SpecSequenceOptions, type TableInstance, type TableMountOptions, type TableState, type TextEditOverlayConfig, type TileMapInstance, type TileMapMountOptions, VECTOR_EXPORT_MAX_POINTS, type YouDrawItController, type YouDrawItOptions, attachKeyboardNav, createBarList, createGeoMap, createReader, createSankey, createSimulationWorker, createTable, createTextEditOverlay, createTileMap, createYouDrawIt, exportSpecSequence, materializeCanvasModeSVG, observeResize, rectPathWithCorners, registerMarkRenderer, renderBarCell, renderCategoryCell, renderCell, renderChartSVG, renderFlagCell, renderHeatmapCell, renderImageCell, renderSparklineCell, renderTable, renderTextCell };