@opendata-ai/openchart-vanilla 8.7.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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/gif-encode.ts","../src/export-gif.ts"],"sourcesContent":["/**\n * Shared gifenc frame-encoding primitives.\n *\n * Both GIF exporters (`export-gif.ts`, entrance animation; `export-sequence.ts`,\n * spec-swap keyframes) need the same low-level steps: read a canvas back in\n * sRGB (the export canvas may be display-p3, but gifenc's quantizer assumes sRGB\n * bytes), quantize a shared palette, and write indexed frames. They differ only\n * in how they PRODUCE canvases — synthesized per time-step vs. one per spec — so\n * that stays in each caller; only these primitives are shared, which keeps the\n * sRGB-readback subtlety in exactly one place.\n */\n\n/**\n * Read a canvas's pixels back in sRGB. The export canvas may be display-p3 (so\n * PNG/JPG can embed a wide-gamut profile), but GIF is sRGB-only and gifenc's\n * quantizer assumes sRGB bytes — feeding it P3-encoded data would shift and\n * desaturate the GIF. The browser converts P3→sRGB on readback.\n */\nexport function readCanvasSRGB(canvas: HTMLCanvasElement): Uint8ClampedArray {\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('Canvas 2D context not available');\n return ctx.getImageData(0, 0, canvas.width, canvas.height, {\n colorSpace: 'srgb',\n }).data;\n}\n\n/** Quantize a 256-color palette from a canvas's sRGB pixels. */\nexport function paletteFromCanvas(\n canvas: HTMLCanvasElement,\n quantize: (data: Uint8ClampedArray, maxColors: number) => number[][],\n): number[][] {\n return quantize(readCanvasSRGB(canvas), 256);\n}\n","/**\n * Animated GIF export.\n *\n * Unlike PNG/JPG (single static frame), GIF export must reproduce the chart's\n * entrance animation across many frames. OpenChart entrance animations are pure\n * CSS keyframes that mutate `clip-path`/`opacity`/`transform` in *computed*\n * style, which `XMLSerializer` does not capture — so serializing the live,\n * mid-animation SVG yields the base markup on every frame (a static GIF).\n *\n * This module sidesteps that by *synthesizing* frames deterministically: it\n * takes the settled, final-state SVG plus the resolved animation config,\n * computes each animated element's value at time `t` in JS, stamps\n * `clip-path`/`opacity` as INLINE attributes on a clone (inline values DO\n * serialize), rasterizes that clone, and encodes each frame with gifenc.\n *\n * This mirrors the entrance keyframes in `packages/core/src/styles/*.css`:\n * bars clip bottom-up / left-right, line/area clip left-right, arcs and points\n * fade. Only the entrance is reproduced; the on-screen CSS animation is\n * untouched. `gifenc` is an optional peer dependency, loaded dynamically.\n */\n\nimport type { AnimationEase, ResolvedAnimation, ResolvedTheme } from '@opendata-ai/openchart-core';\nimport {\n embedFonts,\n ensureSVGDimensions,\n exportSVG,\n getSVGBackgroundColor,\n getSVGDimensions,\n rasterizeSVGToCanvas,\n} from './export';\nimport { readCanvasSRGB } from './gif-encode';\nimport { injectThemeStyleBlock } from './theme-style-block';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GIFExportOptions {\n /** Frames per second to capture. Defaults to 25. */\n fps?: number;\n /** DPI scaling factor, matching PNG export. Defaults to 2. */\n dpi?: number;\n /**\n * Loop behavior. Defaults to play-once (hold the final frame):\n * - `false` / omitted: play once, then hold the final frame\n * - `true`: loop forever\n * - a number: loop that many times, then hold\n */\n loop?: boolean | number;\n /**\n * Explicit capture duration in ms. Defaults to the resolved animation total\n * (enter duration + last element's stagger delay + a small tail).\n */\n durationMs?: number;\n /** Embed fonts as base64 data URIs so text matches on-screen. Defaults to true. */\n embedFonts?: boolean;\n /**\n * Opaque background fill. GIF can't carry partial alpha, so a transparent\n * chart would composite onto black. Defaults to the chart's own background\n * color (or white if the chart is transparent).\n */\n backgroundColor?: string;\n /**\n * Resolved theme to inline as a `<style>` block so class-based fills (metric\n * cells, eyebrow dot, legend text) survive serialization. Omit to skip.\n */\n theme?: ResolvedTheme;\n}\n\n/** Which visual property an element's entrance keyframe animates. */\nexport type AnimationKind = 'bar-vertical' | 'bar-horizontal' | 'line-area' | 'fade';\n\nexport interface AnimatedTarget {\n /** The element in the CLONE that receives per-frame inline styles. */\n el: SVGElement;\n kind: AnimationKind;\n /** Start offset in ms (stagger * mark index). */\n startMs: number;\n /** Duration of this element's own animation in ms. */\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Easing — replicate --oc-ease-smooth (CSS linear() sample points)\n// ---------------------------------------------------------------------------\n\n/**\n * Sample points of the `--oc-ease-smooth` token (see tokens.css). CSS\n * `linear()` interpolates piecewise-linearly between evenly-spaced samples, so\n * we reproduce it exactly rather than approximating with a cubic-bezier.\n */\nexport const EASE_SMOOTH_SAMPLES = [\n 0, 0.157, 0.438, 0.64, 0.766, 0.85, 0.906, 0.941, 0.964, 0.978, 0.988, 0.994, 0.998, 1,\n];\n\n/** Piecewise-linear evaluation of a CSS linear() sample array at t in [0,1]. */\nexport function evalLinearSamples(samples: number[], t: number): number {\n if (t <= 0) return samples[0];\n if (t >= 1) return samples[samples.length - 1];\n const scaled = t * (samples.length - 1);\n const i = Math.floor(scaled);\n const frac = scaled - i;\n return samples[i] + (samples[i + 1] - samples[i]) * frac;\n}\n\n/** Ease a normalized progress value. Only 'smooth' is sample-accurate today. */\nfunction ease(p: number, _kind: AnimationEase): number {\n // The entrance rules use --oc-ease-smooth (or fall back to it) in all cases\n // GIF export handles today. Snappy/others could add their own samples later.\n return evalLinearSamples(EASE_SMOOTH_SAMPLES, p);\n}\n\n// ---------------------------------------------------------------------------\n// Target discovery\n// ---------------------------------------------------------------------------\n\n/**\n * Classify an animated element (identified by `data-animation-index`) into the\n * keyframe it plays, and return the element that should receive inline styles.\n * Returns null only when a bar group has no shape child to clip. Unrecognized\n * mark types (map/tilemap/table/text) fall back to a plain opacity fade rather\n * than a hard pop-in — see the generic case at the end.\n */\nexport function classifyTarget(\n groupOrEl: SVGElement,\n): { el: SVGElement; kind: AnimationKind } | null {\n const cls = groupOrEl.getAttribute('class') ?? '';\n\n // Bars: CSS animates the child <rect>; clip + fade. Orient on the group.\n if (cls.includes('oc-mark-rect') || cls.includes('oc-mark-bar')) {\n const rect = groupOrEl.querySelector('rect') ?? groupOrEl.querySelector('path');\n if (!rect) return null;\n const horizontal = groupOrEl.getAttribute('data-orient') === 'horizontal';\n return { el: rect as SVGElement, kind: horizontal ? 'bar-horizontal' : 'bar-vertical' };\n }\n\n // Line/area: the whole group clips left-to-right + fades.\n if (cls.includes('oc-mark-line') || cls.includes('oc-mark-area')) {\n return { el: groupOrEl, kind: 'line-area' };\n }\n\n // Arcs and points: fade only (scale breaks arc/point positioning, so the CSS\n // uses a plain opacity fade too).\n if (\n cls.includes('oc-mark-arc') ||\n cls.includes('oc-mark-point') ||\n cls.includes('oc-mark-circle')\n ) {\n return { el: groupOrEl, kind: 'fade' };\n }\n\n // Everything else (text labels, rules, ticks, map/tilemap/table marks) is a\n // plain fade; treat generically so the frame isn't a hard pop-in.\n return { el: groupOrEl, kind: 'fade' };\n}\n\n/**\n * Build the list of animated targets from a cloned SVG, pairing each element\n * with its timing. Elements carry `data-animation-index` (the mark index that\n * drives stagger) stamped by the renderer.\n */\nfunction collectTargets(\n clone: SVGElement,\n enterDurationMs: number,\n staggerMs: number,\n): AnimatedTarget[] {\n const els = clone.querySelectorAll<SVGElement>('[data-animation-index]');\n const targets: AnimatedTarget[] = [];\n\n for (const el of els) {\n const classified = classifyTarget(el);\n if (!classified) continue;\n const idx = parseInt(el.getAttribute('data-animation-index') ?? '0', 10) || 0;\n // Points animate at 40% of the base duration (matches animation.css).\n const isPoint =\n classified.kind === 'fade' && /oc-mark-(point|circle)/.test(el.getAttribute('class') ?? '');\n // Points on line/area charts start 35% of a duration in, so they pop as the\n // line draws through them (the `.oc-mark-line ~ circle.oc-mark-point` rule).\n const lineOffset = isPoint && hasLineAreaSibling(el) ? enterDurationMs * 0.35 : 0;\n targets.push({\n el: classified.el,\n kind: classified.kind,\n startMs: staggerMs * idx + lineOffset,\n durationMs: isPoint ? enterDurationMs * 0.4 : enterDurationMs,\n });\n }\n\n return targets;\n}\n\n/**\n * Replicate the CSS sibling combinator `.oc-mark-line ~ circle.oc-mark-point`:\n * true when a line or area mark precedes this point among its siblings.\n */\nfunction hasLineAreaSibling(point: SVGElement): boolean {\n let sib = point.previousElementSibling;\n while (sib) {\n const cls = sib.getAttribute('class') ?? '';\n if (cls.includes('oc-mark-line') || cls.includes('oc-mark-area')) return true;\n sib = sib.previousElementSibling;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Per-frame interpolation\n// ---------------------------------------------------------------------------\n\nconst clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);\n\n/**\n * Distinguish \"the optional gifenc peer isn't installed\" from a real error\n * thrown while loading it. Node/bundlers use ERR_MODULE_NOT_FOUND /\n * MODULE_NOT_FOUND / \"Cannot find module\" for the former.\n */\nexport function isModuleNotFound(err: unknown): boolean {\n const code = (err as { code?: string })?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const message = (err as { message?: string })?.message ?? '';\n return /cannot find (module|package)|failed to (resolve|fetch)/i.test(message);\n}\n\n/**\n * Map the `loop` option to gifenc's `repeat` value:\n * `true` → 0 (loop forever), a number → that count, otherwise → -1 (play once,\n * hold the final frame — the default and the common editorial case).\n */\nexport function resolveRepeat(loop: boolean | number | undefined): number {\n if (loop === true) return 0;\n if (typeof loop === 'number') return loop;\n return -1;\n}\n\n/**\n * Apply the interpolated entrance state for a target at absolute time `nowMs`,\n * writing inline `clip-path`/`opacity` so it survives serialization.\n */\nexport function applyFrameState(\n target: AnimatedTarget,\n nowMs: number,\n easeKind: AnimationEase,\n): void {\n const p = clamp01((nowMs - target.startMs) / target.durationMs);\n const eased = ease(p, easeKind);\n const style = target.el.style;\n\n switch (target.kind) {\n case 'bar-vertical': {\n // oc-enter-bar: clip-path inset(100%→0 from top) + opacity (full by 75%).\n const inset = (1 - eased) * 100;\n style.clipPath = `inset(${inset}% 0 0 0)`;\n style.opacity = String(clamp01(p / 0.75));\n break;\n }\n case 'bar-horizontal': {\n // oc-enter-bar-h: clip-path inset(0 100%→0 from right) + opacity.\n const inset = (1 - eased) * 100;\n style.clipPath = `inset(0 ${inset}% 0 0)`;\n style.opacity = String(clamp01(p / 0.75));\n break;\n }\n case 'line-area': {\n // oc-enter-line: clip-path inset(0 100%→0 from right); opacity full by 15%.\n const inset = (1 - eased) * 100;\n style.clipPath = `inset(0 ${inset}% 0 0)`;\n style.opacity = String(clamp01(p / 0.15));\n break;\n }\n case 'fade': {\n // oc-enter-fade-only: opacity 0→1.\n style.opacity = String(eased);\n break;\n }\n }\n}\n\n/** Clear any inline entrance styles so the settled frame renders at rest. */\nfunction clearFrameState(target: AnimatedTarget): void {\n const style = target.el.style;\n style.clipPath = '';\n style.opacity = '';\n}\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\n/**\n * Render a chart's entrance animation to an animated GIF Blob.\n *\n * @param svgElement - The settled, fully-rendered chart SVG (animation complete).\n * @param animation - The chart's resolved animation config (for exact timing).\n * When undefined, timing falls back to sensible defaults.\n * @param options - fps, dpi, loop, duration, font-embedding overrides.\n * @returns A Promise resolving to the GIF Blob.\n */\nexport async function exportGIF(\n svgElement: SVGElement,\n animation: ResolvedAnimation | undefined,\n options?: GIFExportOptions,\n): Promise<Blob> {\n let gifenc: typeof import('gifenc');\n try {\n gifenc = await import('gifenc');\n } catch (err) {\n // Only a genuine \"module not found\" means the optional peer is absent. Any\n // other failure (a real bug in gifenc's module eval, a corrupt install) must\n // surface with its context, not the misleading install hint.\n if (isModuleNotFound(err)) {\n throw new Error(\n \"GIF export requires the optional 'gifenc' peer dependency. Install it: npm install gifenc\",\n );\n }\n throw new Error('GIF export failed to load the gifenc encoder', { cause: err });\n }\n const { GIFEncoder, quantize, applyPalette } = gifenc;\n\n const fps = options?.fps ?? 25;\n const dpi = options?.dpi ?? 2;\n const shouldEmbed = options?.embedFonts ?? true;\n const { width, height } = getSVGDimensions(svgElement);\n if (!width || !height) {\n throw new Error(`SVG has zero dimensions (width=${width}, height=${height})`);\n }\n\n // Resolve timing from the config (exact) with defaults as a fallback.\n const enter = animation?.enter;\n const enterDurationMs = enter?.duration ?? 600;\n const staggerMs = enter?.staggerDelay ?? 0;\n const easeKind: AnimationEase = enter?.ease ?? 'smooth';\n\n // Build one font-embedded clone that represents the FINAL frame; every frame\n // derives from it, so fonts are embedded exactly once.\n const clone = svgElement.cloneNode(true) as SVGElement;\n // Inline the theme style block so class-based chrome fills survive the detach\n // from the page stylesheet (metrics, eyebrow dot, legend). No-op when undefined.\n injectThemeStyleBlock(clone, options?.theme);\n if (shouldEmbed) {\n await embedFonts(clone);\n }\n const targets = collectTargets(clone, enterDurationMs, staggerMs);\n\n // Total capture window: explicit override, else the animation total. Mirrors\n // computeAnimationDuration (animation.ts): the window must cover the last\n // mark's stagger + its own duration AND the annotation reveal, which animates\n // at enterDuration + annotationDelay. Without the annotationDelay term, charts\n // with annotations get cut off before the annotations finish fading in.\n const annotationDelay = animation?.annotationDelay ?? 0;\n const lastMarkEnd = targets.reduce(\n (m, t) => Math.max(m, t.startMs + t.durationMs),\n enterDurationMs,\n );\n const lastEnd = Math.max(lastMarkEnd, enterDurationMs + annotationDelay);\n const totalMs = options?.durationMs ?? lastEnd + 300;\n const frameDelay = Math.round(1000 / fps);\n const totalFrames = Math.max(1, Math.round((totalMs / 1000) * fps));\n\n // Loop → gifenc repeat: play-once (-1), forever (0), or explicit count.\n const repeat = resolveRepeat(options?.loop);\n\n // GIF can't carry partial alpha, so fill an opaque background before drawing\n // each frame (else a transparent chart composites onto black). Runs before\n // ctx.scale, so paint the raw device-pixel canvas size.\n // The theme background is the fallback: canvas mark mode suppresses the SVG\n // background rect, so there's nothing in the markup to read it from.\n const bgColor =\n options?.backgroundColor ??\n getSVGBackgroundColor(svgElement, options?.theme?.colors.background);\n const fillBackground = (ctx: CanvasRenderingContext2D, cv: HTMLCanvasElement): void => {\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, cv.width, cv.height);\n };\n\n const gif = GIFEncoder();\n\n // Derive ONE global palette from the settled frame (most complete colors)\n // and reuse it for every frame, so colors stay stable and the file stays\n // small. gifenc writes the global color table from the palette on the first\n // frame; later frames reuse it.\n let globalPalette: number[][] = [];\n await seedPalette(clone, targets, width, height, dpi, fillBackground, (data) => {\n globalPalette = quantize(data, 256);\n });\n\n let frameWritten = 0;\n const writeFrame = async (nowMs: number | null): Promise<void> => {\n if (nowMs === null) {\n for (const t of targets) clearFrameState(t);\n } else {\n for (const t of targets) applyFrameState(t, nowMs, easeKind);\n }\n const svgString = ensureSVGDimensions(exportSVG(clone), width, height);\n const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, fillBackground);\n const index = applyPalette(readCanvasSRGB(canvas), globalPalette);\n // `repeat` is only meaningful on the first frame (it writes the loop\n // extension); passing it later is ignored by gifenc.\n gif.writeFrame(index, canvas.width, canvas.height, {\n palette: globalPalette,\n delay: frameDelay,\n ...(frameWritten === 0 ? { repeat } : {}),\n });\n frameWritten++;\n };\n\n for (let i = 0; i < totalFrames; i++) {\n await writeFrame((i / fps) * 1000);\n }\n // Final settled frame the GIF holds on.\n await writeFrame(null);\n\n gif.finish();\n // gifenc types bytes() as Uint8Array<ArrayBufferLike>; BlobPart wants a view\n // over a plain ArrayBuffer. The runtime value is a valid BufferSource, so the\n // cast is safe — it only bridges the ArrayBufferLike/ArrayBuffer lib mismatch.\n return new Blob([gif.bytes() as BlobPart], { type: 'image/gif' });\n}\n\n/**\n * Render the settled clone once (no per-frame state) to derive the palette\n * source, without writing a frame. Restores frame state afterward.\n */\nasync function seedPalette(\n clone: SVGElement,\n targets: AnimatedTarget[],\n width: number,\n height: number,\n dpi: number,\n prepare: (ctx: CanvasRenderingContext2D, cv: HTMLCanvasElement) => void,\n use: (data: Uint8ClampedArray) => void,\n): Promise<void> {\n for (const t of targets) clearFrameState(t);\n const svgString = ensureSVGDimensions(exportSVG(clone), width, height);\n const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, prepare);\n use(readCanvasSRGB(canvas));\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/export.ts"],"sourcesContent":["/**\n * Export utilities: serialize charts to SVG, PNG, JPG, or CSV.\n *\n * - SVG: serializes the rendered DOM element via XMLSerializer\n * - SVG with fonts: async version that embeds @font-face data URIs\n * - PNG: renders SVG to canvas, then extracts as Blob\n * - JPG: same as PNG but with JPEG compression and background fill\n * - CSV: converts a data array to comma-separated text\n */\n\nimport type { ResolvedTheme } from '@opendata-ai/openchart-core';\nimport { injectThemeStyleBlock } from './theme-style-block';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SVGExportOptions {\n /** Embed fonts as base64 data URIs in the SVG. Defaults to true. */\n embedFonts?: boolean;\n /**\n * Resolved theme to inline as a `<style>` block, so class-based fills (metric\n * cells, eyebrow dot, legend text, endpoint labels) survive\n * serialization away from the page stylesheet. Omit to skip — a chart whose\n * chrome is all inline-filled exports fine without it.\n */\n theme?: ResolvedTheme;\n}\n\nexport interface PNGExportOptions extends SVGExportOptions {\n /** DPI scaling factor. Defaults to 2 for retina-quality output. */\n dpi?: number;\n}\n\nexport interface JPGExportOptions extends PNGExportOptions {\n /** JPEG quality from 0 to 1. Defaults to 0.92. */\n quality?: number;\n}\n\ninterface FontFaceData {\n family: string;\n weight: string;\n style: string;\n base64: string;\n format: string;\n}\n\n// ---------------------------------------------------------------------------\n// Dimension parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Extract dimensions from an SVG element, trying width/height attributes\n * first, then falling back to viewBox.\n */\nexport function getSVGDimensions(svg: SVGElement): { width: number; height: number } {\n const w = parseFloat(svg.getAttribute('width') || '');\n const h = parseFloat(svg.getAttribute('height') || '');\n if (w && h) return { width: w, height: h };\n\n const vb = svg.getAttribute('viewBox');\n if (vb) {\n const parts = vb.split(/[\\s,]+/).map(Number);\n if (parts.length >= 4 && parts[2] && parts[3]) {\n return { width: parts[2], height: parts[3] };\n }\n }\n\n return { width: 600, height: 400 };\n}\n\n/**\n * Ensure an SVG string has explicit width/height attributes.\n *\n * When an SVG only has a viewBox (no width/height), browsers loading it as\n * an Image blob may use 300x150 as the intrinsic size instead of the viewBox\n * dimensions. This causes clipping at non-1x DPI scaling. Injecting explicit\n * width/height into the root <svg> tag fixes the intrinsic size.\n */\nexport function ensureSVGDimensions(svgString: string, width: number, height: number): string {\n // If the <svg> already has a width attribute, leave it alone\n if (/^<svg[^>]*\\swidth\\s*=/.test(svgString)) return svgString;\n // Inject width and height right after <svg\n return svgString.replace(/^(<svg)/, `$1 width=\"${width}\" height=\"${height}\"`);\n}\n\n// ---------------------------------------------------------------------------\n// Font embedding\n// ---------------------------------------------------------------------------\n\n/**\n * Collect unique font-family + font-weight combos from all <text> elements.\n */\nfunction collectUsedFonts(svgElement: SVGElement): Map<string, Set<string>> {\n const fonts = new Map<string, Set<string>>();\n const textElements = svgElement.querySelectorAll('text');\n\n for (const el of textElements) {\n const family = el.getAttribute('font-family');\n const weight = el.getAttribute('font-weight') || '400';\n if (family) {\n // Take the first font in the stack (e.g., \"Inter, sans-serif\" → \"Inter\")\n const primary = family.split(',')[0].trim().replace(/[\"']/g, '');\n if (!fonts.has(primary)) fonts.set(primary, new Set());\n fonts.get(primary)!.add(String(weight));\n }\n }\n\n return fonts;\n}\n\n/**\n * Find @font-face rules in document stylesheets that match the requested fonts.\n * Returns the src URLs for .woff2 files.\n */\nfunction findFontFaceRules(\n usedFonts: Map<string, Set<string>>,\n): Array<{ family: string; weight: string; style: string; url: string; format: string }> {\n const results: Array<{\n family: string;\n weight: string;\n style: string;\n url: string;\n format: string;\n }> = [];\n\n try {\n for (const sheet of document.styleSheets) {\n let rules: CSSRuleList;\n try {\n rules = sheet.cssRules;\n } catch {\n // Cross-origin stylesheet, skip\n continue;\n }\n\n for (const rule of rules) {\n if (!(rule instanceof CSSFontFaceRule)) continue;\n\n const familyRaw = rule.style.getPropertyValue('font-family').replace(/[\"']/g, '').trim();\n const weight = rule.style.getPropertyValue('font-weight') || '400';\n const style = rule.style.getPropertyValue('font-style') || 'normal';\n const src = rule.style.getPropertyValue('src');\n\n const weights = usedFonts.get(familyRaw);\n if (!weights) continue;\n\n // Check if this weight is used (handle ranges like \"100 900\")\n const weightMatch = weights.has(weight) || weight.includes(' ');\n if (!weightMatch) continue;\n\n // Extract woff2 URL from src descriptor\n const woff2Match = src.match(/url\\([\"']?([^\"')]+\\.woff2[^\"')]*?)[\"']?\\)/);\n if (woff2Match) {\n results.push({\n family: familyRaw,\n weight,\n style,\n url: woff2Match[1],\n format: 'woff2',\n });\n }\n }\n }\n } catch {\n // Stylesheet access failed entirely, return empty\n }\n\n return results;\n}\n\n/**\n * Fetch font files and convert to base64 data URIs.\n */\nasync function fetchFontsAsBase64(\n fontRules: Array<{ family: string; weight: string; style: string; url: string; format: string }>,\n): Promise<FontFaceData[]> {\n const results: FontFaceData[] = [];\n\n const fetches = fontRules.map(async (rule) => {\n try {\n const response = await fetch(rule.url);\n if (!response.ok) return;\n const buffer = await response.arrayBuffer();\n const base64 = arrayBufferToBase64(buffer);\n results.push({\n family: rule.family,\n weight: rule.weight,\n style: rule.style,\n base64,\n format: rule.format,\n });\n } catch {\n // Font fetch failed (CORS, network, etc.) - skip this font\n }\n });\n\n await Promise.all(fetches);\n return results;\n}\n\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n let binary = '';\n const bytes = new Uint8Array(buffer);\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n\n/**\n * Inject @font-face rules with base64 data URIs into the SVG's <defs>.\n */\nfunction injectFontsIntoSVG(svgElement: SVGElement, fonts: FontFaceData[]): void {\n if (fonts.length === 0) return;\n\n const cssRules = fonts\n .map(\n (f) =>\n `@font-face { font-family: '${f.family}'; font-weight: ${f.weight}; font-style: ${f.style}; src: url(data:font/${f.format};base64,${f.base64}) format('${f.format}'); }`,\n )\n .join('\\n');\n\n const ns = 'http://www.w3.org/2000/svg';\n let defs = svgElement.querySelector('defs');\n if (!defs) {\n defs = document.createElementNS(ns, 'defs');\n svgElement.insertBefore(defs, svgElement.firstChild);\n }\n\n const styleEl = document.createElementNS(ns, 'style');\n styleEl.textContent = cssRules;\n defs.insertBefore(styleEl, defs.firstChild);\n}\n\n/**\n * Embed fonts into an SVG element by finding matching @font-face rules\n * in the page's stylesheets, fetching the font files, and injecting\n * them as base64 data URIs.\n *\n * Modifies the SVG element in place. Call this before serialization.\n * If font fetching fails for any font, that font is silently skipped\n * and the export proceeds with system font fallback for that face.\n */\nexport async function embedFonts(svgElement: SVGElement): Promise<void> {\n const usedFonts = collectUsedFonts(svgElement);\n if (usedFonts.size === 0) return;\n\n const fontRules = findFontFaceRules(usedFonts);\n if (fontRules.length === 0) return;\n\n const fontData = await fetchFontsAsBase64(fontRules);\n injectFontsIntoSVG(svgElement, fontData);\n}\n\n// ---------------------------------------------------------------------------\n// SVG background color\n// ---------------------------------------------------------------------------\n\n/**\n * Read the chart's background color from its first rect element. Falls back to\n * `fallback` (white when omitted) when the chart has no opaque background,\n * which matters for formats that can't carry alpha (JPEG, GIF).\n *\n * Pass the resolved theme's `colors.background` as `fallback` when the caller\n * has one: canvas mark mode suppresses the SVG background rect (the canvas\n * paints it), so without it a dark chart would flood-fill white.\n */\nexport function getSVGBackgroundColor(svgElement: SVGElement, fallback?: string): string {\n const firstRect = svgElement.querySelector('rect');\n const fill = firstRect?.getAttribute('fill');\n if (!fill || fill === 'none' || fill === 'transparent') return fallback ?? '#ffffff';\n return fill;\n}\n\n// ---------------------------------------------------------------------------\n// SVG export\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize an SVG element to an XML string.\n *\n * @param svgElement - The rendered SVG element to serialize.\n * @returns The SVG markup as a string.\n */\nexport function exportSVG(svgElement: SVGElement): string {\n if (!svgElement.getAttribute('xmlns')) {\n svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');\n }\n const serializer = new XMLSerializer();\n return serializer.serializeToString(svgElement);\n}\n\n/**\n * Serialize an SVG element with embedded fonts to an XML string.\n *\n * Collects font-family declarations from the SVG's text elements,\n * finds matching @font-face rules in the page's stylesheets, fetches\n * the font files, and embeds them as base64 data URIs. The resulting\n * SVG is self-contained and renders correctly without external fonts.\n *\n * @param svgElement - The rendered SVG element to serialize.\n * @param options - Export options.\n * @returns A Promise resolving to the SVG markup as a string.\n */\nexport async function exportSVGWithFonts(\n svgElement: SVGElement,\n options?: SVGExportOptions,\n): Promise<string> {\n const clone = svgElement.cloneNode(true) as SVGElement;\n const shouldEmbed = options?.embedFonts ?? true;\n injectThemeStyleBlock(clone, options?.theme);\n if (shouldEmbed) {\n await embedFonts(clone);\n }\n return exportSVG(clone);\n}\n\n// ---------------------------------------------------------------------------\n// Raster export (PNG / JPG)\n// ---------------------------------------------------------------------------\n\n/**\n * Cache the one-time probe for Display P3 canvas support. Rendering raster\n * exports into a display-p3 canvas makes toBlob embed a wide-gamut color profile\n * (verified: an sRGB PNG has no iCCP chunk; a display-p3 PNG does — and JPEG\n * upgrades from an sRGB to a P3 profile), so the export matches the on-screen\n * chart instead of reading as an untagged/sRGB image that looks washed out on\n * P3 displays.\n */\nlet p3Supported: boolean | undefined;\n\nfunction supportsDisplayP3(): boolean {\n if (p3Supported !== undefined) return p3Supported;\n try {\n const probe = document.createElement('canvas');\n const ctx = probe.getContext('2d', { colorSpace: 'display-p3' }) as\n | (CanvasRenderingContext2D & { getContextAttributes?: () => { colorSpace?: string } })\n | null;\n p3Supported = !!ctx && ctx.getContextAttributes?.().colorSpace === 'display-p3';\n } catch {\n p3Supported = false;\n }\n return p3Supported;\n}\n\n/**\n * Create a 2D context in the widest color space the runtime supports. A\n * display-p3 canvas is what makes toBlob embed the profile. Callers that read\n * pixels back for a non-color-managed sink (GIF quantization) must NOT rely on\n * this — they must read back in sRGB explicitly.\n */\nfunction getExportContext(canvas: HTMLCanvasElement): CanvasRenderingContext2D {\n const ctx = supportsDisplayP3()\n ? canvas.getContext('2d', { colorSpace: 'display-p3' })\n : canvas.getContext('2d');\n if (!ctx) {\n throw new Error('Canvas 2D context not available');\n }\n return ctx;\n}\n\n/**\n * Draw a serialized SVG string onto a canvas at DPI scaling.\n *\n * Shared by PNG/JPG export and per-frame GIF rendering: loads the SVG string\n * as an Image via an object URL, draws it into a `width*dpi × height*dpi`\n * canvas, and hands the canvas back once painted. The caller decides how to\n * read the result (toBlob for PNG/JPG, getImageData for GIF frames).\n *\n * An optional `prepare` hook runs before drawing the image, used by JPG export\n * to fill an opaque background so transparency doesn't render as black.\n *\n * @param svgString - Serialized SVG markup (should already carry width/height).\n * @param width - Logical width in CSS pixels.\n * @param height - Logical height in CSS pixels.\n * @param dpi - Device pixel ratio scaling factor.\n * @param prepare - Optional pre-draw hook receiving the scaled 2D context.\n * @returns A Promise resolving to the painted canvas.\n */\nexport function rasterizeSVGToCanvas(\n svgString: string,\n width: number,\n height: number,\n dpi: number,\n prepare?: (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => void,\n): Promise<HTMLCanvasElement> {\n if (!width || !height) {\n throw new Error(`SVG has zero dimensions (width=${width}, height=${height})`);\n }\n\n const canvas = document.createElement('canvas');\n canvas.width = width * dpi;\n canvas.height = height * dpi;\n\n const ctx = getExportContext(canvas);\n\n prepare?.(ctx, canvas);\n ctx.scale(dpi, dpi);\n\n const img = new Image();\n const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n\n return new Promise<HTMLCanvasElement>((resolve, reject) => {\n img.onload = () => {\n ctx.drawImage(img, 0, 0, width, height);\n URL.revokeObjectURL(url);\n resolve(canvas);\n };\n\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(\n new Error(\n `Failed to load SVG as image (width=${width}, height=${height}, svgLength=${svgString.length})`,\n ),\n );\n };\n\n img.src = url;\n });\n}\n\n/**\n * Serialize an SVG element (optionally embedding fonts) into a canvas-ready\n * string with explicit width/height. Shared by the raster exporters.\n */\nasync function prepareRasterSVG(\n svgElement: SVGElement,\n shouldEmbed: boolean,\n theme?: ResolvedTheme,\n): Promise<{ svgString: string; width: number; height: number }> {\n const { width, height } = getSVGDimensions(svgElement);\n const clone = svgElement.cloneNode(true) as SVGElement;\n // Inline the theme style block so class-based fills survive the detach from\n // the page stylesheet. No-op when theme is undefined.\n injectThemeStyleBlock(clone, theme);\n if (shouldEmbed) {\n await embedFonts(clone);\n }\n const svgString = ensureSVGDimensions(exportSVG(clone), width, height);\n return { svgString, width, height };\n}\n\n/**\n * Render an SVG element to a PNG Blob via a canvas.\n *\n * Embeds fonts by default so the exported image matches on-screen rendering.\n * Set `embedFonts: false` to skip font embedding for faster exports.\n *\n * @param svgElement - The rendered SVG element.\n * @param options - Optional DPI scaling and font embedding.\n * @returns A Promise resolving to the PNG Blob.\n */\nexport async function exportPNG(svgElement: SVGElement, options?: PNGExportOptions): Promise<Blob> {\n const dpi = options?.dpi ?? 2;\n const shouldEmbed = options?.embedFonts ?? true;\n const { svgString, width, height } = await prepareRasterSVG(\n svgElement,\n shouldEmbed,\n options?.theme,\n );\n const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi);\n\n return new Promise<Blob>((resolve, reject) => {\n canvas.toBlob((result) => {\n if (result) {\n resolve(result);\n } else {\n reject(new Error('Canvas toBlob returned null'));\n }\n }, 'image/png');\n });\n}\n\n/**\n * Render an SVG element to a JPEG Blob via a canvas.\n *\n * Same pipeline as exportPNG but outputs JPEG with configurable quality.\n * The canvas is filled with the chart's background color before drawing\n * to avoid transparent backgrounds rendering as black in JPEG format.\n *\n * @param svgElement - The rendered SVG element.\n * @param options - Optional DPI scaling, JPEG quality, and font embedding.\n * @returns A Promise resolving to the JPEG Blob.\n */\nexport async function exportJPG(svgElement: SVGElement, options?: JPGExportOptions): Promise<Blob> {\n const dpi = options?.dpi ?? 2;\n const quality = options?.quality ?? 0.92;\n const shouldEmbed = options?.embedFonts ?? true;\n const backgroundColor = getSVGBackgroundColor(svgElement);\n const { svgString, width, height } = await prepareRasterSVG(\n svgElement,\n shouldEmbed,\n options?.theme,\n );\n\n // Fill an opaque background before drawing so transparency doesn't render\n // as black in the JPEG. Runs before ctx.scale, so use the raw canvas size.\n const canvas = await rasterizeSVGToCanvas(svgString, width, height, dpi, (ctx, cv) => {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, cv.width, cv.height);\n });\n\n return new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (result) => {\n if (result) {\n resolve(result);\n } else {\n reject(new Error('Canvas toBlob returned null'));\n }\n },\n 'image/jpeg',\n quality,\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// CSV export\n// ---------------------------------------------------------------------------\n\n/**\n * Convert an array of data objects to a CSV string.\n *\n * Uses the keys from the first row as column headers.\n * Values are quoted if they contain commas, quotes, or newlines.\n *\n * @param data - Array of row objects.\n * @returns CSV-formatted string.\n */\nexport function exportCSV(data: Record<string, unknown>[]): string {\n if (data.length === 0) return '';\n\n const headers = Object.keys(data[0]);\n const rows = [headers.map(csvEscape).join(',')];\n\n for (const row of data) {\n const values = headers.map((h) => csvEscape(String(row[h] ?? '')));\n rows.push(values.join(','));\n }\n\n return rows.join('\\n');\n}\n\nfunction csvEscape(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n') || value.includes('\\r')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/svg-ids.ts","../src/gradient-utils.ts","../src/pattern-utils.ts","../src/renderers/marks.ts","../src/svg-renderer.ts","../src/animation-vars.ts","../src/renderers/annotations.ts","../src/renderers/axes.ts","../src/renderers/brand.ts","../src/renderers/chrome.ts","../src/renderers/endpoint-labels.ts","../src/renderers/legend.ts","../src/renderers/metrics.ts"],"sourcesContent":["/**\n * Monotonic SVG ID generator shared by gradient and clipPath defs.\n *\n * Why a single counter: SVG `url(#id)` references resolve against the full\n * document, so any ID collision (across charts on the same page) silently\n * cross-wires one chart's fill/clip into another. Random-hex suffixes have a\n * small-but-real collision probability that surfaced in production; a global\n * monotonic counter makes uniqueness unconditional.\n *\n * Gradient and clip-path IDs share one counter so we can't regress one half of\n * the system by accident. Consumers just pick a prefix.\n */\n\nlet counter = 0;\n\nexport function nextSvgId(prefix: string): string {\n return `${prefix}-${counter++}`;\n}\n\n/** @internal Used by static rendering only. Never call from browser-facing code. */\nexport function resetSvgIdCounter(): void {\n counter = 0;\n}\n","/**\n * SVG gradient utilities: creates <linearGradient> and <radialGradient>\n * elements from GradientDef specs and resolves mark fills to url(#id) refs.\n */\n\nimport type { GradientDef, LinearGradient, RadialGradient } from '@opendata-ai/openchart-core';\nimport { isGradientDef } from '@opendata-ai/openchart-core';\nimport { nextSvgId } from './svg-ids';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * Produce a stable, deterministic key for a GradientDef.\n * Used for deduplication so identical gradients share one SVG element.\n * Recursively sorts object keys for stability across property order variations.\n */\nfunction gradientKey(def: GradientDef): string {\n return sortedStringify(def);\n}\n\nfunction sortedStringify(value: unknown): string {\n if (value === null || value === undefined) return String(value);\n if (Array.isArray(value)) return `[${value.map(sortedStringify).join(',')}]`;\n if (typeof value === 'object') {\n const sorted = Object.keys(value)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${sortedStringify((value as Record<string, unknown>)[k])}`);\n return `{${sorted.join(',')}}`;\n }\n return JSON.stringify(value);\n}\n\n/**\n * Create a single SVG gradient element (<linearGradient> or <radialGradient>).\n * Uses gradientUnits=\"objectBoundingBox\" so coordinates are in [0,1] space.\n */\nfunction createGradientElement(def: GradientDef, id: string): SVGElement {\n if (def.gradient === 'linear') {\n return createLinearGradient(def, id);\n }\n return createRadialGradient(def, id);\n}\n\nfunction createLinearGradient(def: LinearGradient, id: string): SVGElement {\n const el = document.createElementNS(SVG_NS, 'linearGradient');\n el.setAttribute('id', id);\n el.setAttribute('gradientUnits', 'objectBoundingBox');\n el.setAttribute('x1', String(def.x1 ?? 0));\n el.setAttribute('y1', String(def.y1 ?? 0));\n el.setAttribute('x2', String(def.x2 ?? 0));\n el.setAttribute('y2', String(def.y2 ?? 1));\n\n for (const stop of def.stops) {\n appendStop(el, stop);\n }\n\n return el;\n}\n\nfunction createRadialGradient(def: RadialGradient, id: string): SVGElement {\n const el = document.createElementNS(SVG_NS, 'radialGradient');\n el.setAttribute('id', id);\n el.setAttribute('gradientUnits', 'objectBoundingBox');\n // SVG radialGradient: cx/cy/r = outer circle, fx/fy/fr = focal (inner) circle\n // Spec: x2/y2/r2 = outer, x1/y1/r1 = inner (focal)\n el.setAttribute('cx', String(def.x2 ?? 0.5));\n el.setAttribute('cy', String(def.y2 ?? 0.5));\n el.setAttribute('r', String(def.r2 ?? 0.5));\n el.setAttribute('fx', String(def.x1 ?? 0.5));\n el.setAttribute('fy', String(def.y1 ?? 0.5));\n el.setAttribute('fr', String(def.r1 ?? 0));\n\n for (const stop of def.stops) {\n appendStop(el, stop);\n }\n\n return el;\n}\n\nfunction appendStop(\n parent: SVGElement,\n stop: { offset: number; color: string; opacity?: number },\n): void {\n const stopEl = document.createElementNS(SVG_NS, 'stop');\n stopEl.setAttribute('offset', String(stop.offset));\n stopEl.setAttribute('stop-color', stop.color);\n if (stop.opacity !== undefined) {\n stopEl.setAttribute('stop-opacity', String(stop.opacity));\n }\n parent.appendChild(stopEl);\n}\n\n/**\n * Scan all marks for GradientDef fill values, create SVG gradient elements\n * in the provided <defs> node, and return a map from gradient key to element ID.\n *\n * Identical gradients (by key) share a single SVG element within one chart.\n * IDs come from `nextSvgId` so they're globally unique across charts and\n * share the counter with clip-path IDs (see svg-ids.ts). Gradient ID numbers\n * may skip values when clip-paths consume counter slots - still monotonic,\n * still unique.\n */\nexport function buildGradientDefs(\n marks: Array<{ fill?: unknown }>,\n defs: SVGElement,\n): Map<string, string> {\n const map = new Map<string, string>();\n\n for (const mark of marks) {\n const fill = mark.fill;\n if (fill && isGradientDef(fill)) {\n const key = gradientKey(fill);\n if (!map.has(key)) {\n const id = nextSvgId('oc-grad');\n const el = createGradientElement(fill, id);\n defs.appendChild(el);\n map.set(key, id);\n }\n }\n }\n\n return map;\n}\n\n/**\n * Resolve a mark's fill value to a CSS/SVG fill string.\n * Returns the color string directly for plain fills,\n * or \"url(#gradientId)\" for gradient fills.\n */\nexport function resolveMarkFill(\n fill: string | GradientDef,\n gradientMap: Map<string, string>,\n): string {\n if (typeof fill === 'string') return fill;\n const key = gradientKey(fill);\n const id = gradientMap.get(key);\n return id ? `url(#${id})` : '#000000';\n}\n","/**\n * SVG pattern utilities: creates <pattern> elements from ResolvedFillPattern\n * specs (engine output for `mark.fillPattern: 'auto'`) and resolves mark\n * fills to url(#id) refs. Mirrors the gradient defs lifecycle in\n * gradient-utils.ts, including nextSvgId-based deterministic IDs.\n */\n\nimport type { ResolvedFillPattern } from '@opendata-ai/openchart-core';\nimport { nextSvgId } from './svg-ids';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/** Pattern tile size in px (userSpaceOnUse, so zoom-stable within the SVG). */\nconst TILE = 6;\n\n/**\n * Opacity of the pattern geometry over the series base color. Full-contrast\n * lines read as stripes and fight the data; a settled overlay keeps the\n * series color dominant while the texture stays clearly legible.\n */\nconst LINE_OPACITY = 0.55;\n\n/** Stable dedup key: one SVG element per unique (type, base, line) combo. */\nfunction patternKey(p: ResolvedFillPattern): string {\n return `${p.type}|${p.base}|${p.line}`;\n}\n\n/** Create one <pattern> tile: base-color rect + line-colored geometry. */\nfunction createPatternElement(p: ResolvedFillPattern, id: string): SVGElement {\n const el = document.createElementNS(SVG_NS, 'pattern');\n el.setAttribute('id', id);\n el.setAttribute('patternUnits', 'userSpaceOnUse');\n el.setAttribute('width', String(TILE));\n el.setAttribute('height', String(TILE));\n\n const bg = document.createElementNS(SVG_NS, 'rect');\n bg.setAttribute('width', String(TILE));\n bg.setAttribute('height', String(TILE));\n bg.setAttribute('fill', p.base);\n el.appendChild(bg);\n\n if (p.type === 'dot') {\n const dot = document.createElementNS(SVG_NS, 'circle');\n dot.setAttribute('cx', String(TILE / 2));\n dot.setAttribute('cy', String(TILE / 2));\n dot.setAttribute('r', '1.2');\n dot.setAttribute('fill', p.line);\n dot.setAttribute('fill-opacity', String(LINE_OPACITY));\n el.appendChild(dot);\n return el;\n }\n\n // Stroked shapes. Diagonal paths include half-tile companions so the\n // hatch stays continuous across tile seams.\n const d =\n p.type === 'diagonal'\n ? `M0,${TILE} l${TILE},-${TILE} M-1.5,1.5 l3,-3 M${TILE - 1.5},${TILE + 1.5} l3,-3`\n : p.type === 'crosshatch'\n ? `M0,0 l${TILE},${TILE} M${TILE},0 l-${TILE},${TILE}`\n : `M${TILE / 2},0 V${TILE}`;\n\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('d', d);\n path.setAttribute('stroke', p.line);\n path.setAttribute('stroke-width', '1');\n path.setAttribute('stroke-opacity', String(LINE_OPACITY));\n el.appendChild(path);\n return el;\n}\n\n/**\n * Scan marks for resolved fill patterns, create <pattern> elements in the\n * provided <defs> node, and return a map from pattern key to element ID.\n *\n * Identical patterns share one SVG element within a chart. IDs come from\n * `nextSvgId` (shared counter with gradients and clip-paths) so they are\n * deterministic at generation time and globally unique across charts.\n */\nexport function buildPatternDefs(\n marks: Array<{ pattern?: ResolvedFillPattern }>,\n defs: SVGElement,\n): Map<string, string> {\n const map = new Map<string, string>();\n\n for (const mark of marks) {\n const pattern = mark.pattern;\n if (!pattern) continue;\n const key = patternKey(pattern);\n if (!map.has(key)) {\n const id = nextSvgId('oc-pattern');\n defs.appendChild(createPatternElement(pattern, id));\n map.set(key, id);\n }\n }\n\n return map;\n}\n\n/**\n * Resolve a mark's pattern to an SVG fill string (\"url(#id)\"), falling back\n * to the pattern's base color if the def is missing (defensive; buildPatternDefs\n * runs over the same mark list first).\n */\nexport function resolvePatternFill(\n pattern: ResolvedFillPattern,\n patternMap: Map<string, string>,\n): string {\n const id = patternMap.get(patternKey(pattern));\n return id ? `url(#${id})` : pattern.base;\n}\n","/**\n * Mark rendering: dispatches to per-mark-type sub-renderers (line, area, rect,\n * arc, point, text, rule, tick).\n *\n * Mark renderers read module-level animation and gradient state so their\n * signatures stay `(mark, index) => SVGElement`. Callers must invoke\n * `setMarkRenderState()` before `renderMarks()` and `resetMarkRenderState()`\n * after.\n */\n\nimport type {\n ArcMark,\n AreaMark,\n ChartLayout,\n LineMark,\n Mark,\n PointMark,\n RectMark,\n ResolvedAnimation,\n ResolvedFillPattern,\n ResolvedLabel,\n RuleMarkLayout,\n TextMarkLayout,\n TickMarkLayout,\n} from '@opendata-ai/openchart-core';\nimport { resolveMarkFill } from '../gradient-utils';\nimport { resolvePatternFill } from '../pattern-utils';\nimport { applyKnockoutHalo, applyTextStyle, createSVGElement, setAttrs } from './svg-dom';\n\n/**\n * Module-level animation state. Set by the orchestrator before rendering marks\n * so mark renderers can read it without changing their function signatures.\n */\nlet currentAnimation: ResolvedAnimation | undefined;\n\n/**\n * Module-level gradient map. Set by the orchestrator after building gradient defs\n * so mark renderers can resolve gradient fills without signature changes.\n */\nlet currentGradientMap: Map<string, string> = new Map();\n\n/**\n * Module-level pattern map. Same lifecycle as the gradient map, built from\n * pattern defs (see pattern-utils.ts).\n */\nlet currentPatternMap: Map<string, string> = new Map();\n\n/**\n * Module-level opaque surface color, used to cut the knockout halo behind mark\n * labels. Empty string means \"no halo\" (state was never primed).\n */\nlet currentSurface = '';\n\n/** Set animation + gradient + pattern + surface state before rendering marks. */\nexport function setMarkRenderState(state: {\n animation: ResolvedAnimation | undefined;\n gradientMap: Map<string, string>;\n patternMap?: Map<string, string>;\n surface?: string;\n}): void {\n currentAnimation = state.animation;\n currentGradientMap = state.gradientMap;\n currentPatternMap = state.patternMap ?? new Map();\n currentSurface = state.surface ?? '';\n}\n\n/** Reset animation + gradient + pattern + surface state after rendering. */\nexport function resetMarkRenderState(): void {\n currentAnimation = undefined;\n currentGradientMap = new Map();\n currentPatternMap = new Map();\n currentSurface = '';\n}\n\n/**\n * Cut a knockout halo behind a mark label so it stays legible where it crosses\n * gridlines, marks, or a neighbouring series. Skipped when the engine set\n * `halo: false` -- labels drawn *inside* a filled bar are already\n * contrast-picked against that fill, and a surface-colored stroke there would\n * ring every glyph.\n */\nfunction applyLabelHalo(text: SVGElement, label: ResolvedLabel): void {\n if (!currentSurface || label.halo === false) return;\n applyKnockoutHalo(text, currentSurface, label.style.fontSize);\n}\n\n/**\n * Resolve a filled mark's paint: the pattern url when the engine assigned a\n * fill pattern, the plain/gradient fill otherwise.\n */\nfunction resolveFillOrPattern(\n fill: Parameters<typeof resolveMarkFill>[0],\n pattern: ResolvedFillPattern | undefined,\n): string {\n if (pattern) return resolvePatternFill(pattern, currentPatternMap);\n return resolveMarkFill(fill, currentGradientMap);\n}\n\n/**\n * Stamp animation index attributes on a mark element when animation is enabled.\n * Sets `data-animation-index` (for querySelector) and `--oc-mark-index`\n * (for CSS calc-based stagger delay).\n */\nfunction stampAnimationAttrs(\n el: SVGElement,\n mark: { animationIndex?: number; key?: string },\n fallbackIndex: number,\n): void {\n // Stamp stable identity key for data-update transitions (always, not just enter)\n if (mark.key) {\n el.setAttribute('data-key', mark.key);\n }\n if (!currentAnimation?.enter) return;\n const idx = mark.animationIndex ?? fallbackIndex;\n el.setAttribute('data-animation-index', String(idx));\n (el as SVGElement & ElementCSSInlineStyle).style.setProperty('--oc-mark-index', String(idx));\n}\n\ntype MarkRenderer<T extends Mark> = (mark: T, index: number) => SVGElement;\n\nconst markRenderers: Record<string, MarkRenderer<Mark>> = {};\n\n/**\n * Register a mark renderer for a specific mark type.\n * Built-in renderers are registered below for all chart types.\n */\nexport function registerMarkRenderer<T extends Mark>(\n type: T['type'],\n renderer: MarkRenderer<T>,\n): void {\n markRenderers[type] = renderer as MarkRenderer<Mark>;\n}\n\nfunction renderLineMark(mark: LineMark, index: number): SVGElement {\n const g = createSVGElement('g');\n g.setAttribute('data-mark-id', `line-${mark.seriesKey ?? index}`);\n g.setAttribute('class', 'oc-mark oc-mark-line');\n stampAnimationAttrs(g, mark, index);\n\n if (mark.points.length > 1) {\n const path = createSVGElement('path');\n // Use the pre-computed D3 curve path when available (smooth monotone),\n // otherwise fall back to straight M/L segments.\n const d =\n mark.path ?? mark.points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ');\n setAttrs(path, {\n d,\n fill: 'none',\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth,\n // Round joins/caps: at 2px a mitre on a sharp data spike renders as a\n // spur past the vertex, and a butt cap leaves a squared-off line end.\n 'stroke-linejoin': 'round',\n 'stroke-linecap': 'round',\n });\n if (mark.strokeDasharray) {\n path.setAttribute('stroke-dasharray', mark.strokeDasharray);\n }\n if (mark.opacity != null) {\n path.setAttribute('opacity', String(mark.opacity));\n }\n // Note: line drawing animation is handled via CSS clip-path on the group,\n // no inline dasharray/dashoffset needed.\n g.appendChild(path);\n }\n\n // Render end-of-line label if present and visible\n if (mark.label?.visible) {\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-mark-label');\n if (mark.seriesKey) {\n label.setAttribute('data-series', mark.seriesKey);\n }\n setAttrs(label, { x: mark.label.x, y: mark.label.y });\n applyTextStyle(label, mark.label.style);\n applyLabelHalo(label, mark.label);\n label.textContent = mark.label.text;\n g.appendChild(label);\n\n // Render connector line if label was offset from anchor\n if (mark.label.connector) {\n const connector = createSVGElement('line');\n connector.setAttribute('class', 'oc-mark-connector');\n setAttrs(connector, {\n x1: mark.label.connector.from.x,\n y1: mark.label.connector.from.y,\n x2: mark.label.connector.to.x,\n y2: mark.label.connector.to.y,\n stroke: mark.label.connector.stroke,\n 'stroke-width': 1,\n 'stroke-opacity': 0.5,\n });\n g.appendChild(connector);\n }\n }\n\n return g;\n}\n\nfunction renderAreaMark(mark: AreaMark, index: number): SVGElement {\n const g = createSVGElement('g');\n g.setAttribute('data-mark-id', `area-${mark.seriesKey ?? index}`);\n g.setAttribute('class', 'oc-mark oc-mark-area');\n stampAnimationAttrs(g, mark, index);\n\n if (mark.path) {\n // Area fill: the full closed shape (top line + baseline), no stroke\n const fill = createSVGElement('path');\n setAttrs(fill, {\n d: mark.path,\n fill: resolveFillOrPattern(mark.fill, mark.pattern),\n 'fill-opacity': mark.fillOpacity,\n stroke: 'none',\n });\n g.appendChild(fill);\n\n // Top-line stroke: only along the data points, not the baseline\n if (mark.stroke && mark.topPath) {\n const strokePath = createSVGElement('path');\n strokePath.setAttribute('class', 'oc-area-top');\n setAttrs(strokePath, {\n d: mark.topPath,\n fill: 'none',\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth ?? 1,\n 'stroke-linejoin': 'round',\n 'stroke-linecap': 'round',\n });\n // Note: area drawing animation is handled via CSS clip-path on the group,\n // no inline dasharray/dashoffset needed.\n g.appendChild(strokePath);\n }\n }\n\n return g;\n}\n\n/**\n * Build an SVG path describing a rectangle with selectively rounded corners.\n * Used by stacked segments where only the leading edge (top of a vertical\n * stack, right of a horizontal stack) should round so the seams between\n * adjacent segments stay flush.\n */\nexport function rectPathWithCorners(\n mark: RectMark,\n sides: NonNullable<RectMark['cornerRadiusSides']>,\n): string {\n const { x, y, width: w, height: h } = mark;\n // Clamp the radius so it never exceeds half of the shorter side, otherwise\n // the arcs would overlap and the path would render as a degenerate shape.\n const r = Math.max(0, Math.min(mark.cornerRadius ?? 0, w / 2, h / 2));\n const tl = sides.tl ? r : 0;\n const tr = sides.tr ? r : 0;\n const br = sides.br ? r : 0;\n const bl = sides.bl ? r : 0;\n return [\n `M${x + tl},${y}`,\n `H${x + w - tr}`,\n tr ? `A${tr},${tr} 0 0 1 ${x + w},${y + tr}` : '',\n `V${y + h - br}`,\n br ? `A${br},${br} 0 0 1 ${x + w - br},${y + h}` : '',\n `H${x + bl}`,\n bl ? `A${bl},${bl} 0 0 1 ${x},${y + h - bl}` : '',\n `V${y + tl}`,\n tl ? `A${tl},${tl} 0 0 1 ${x + tl},${y}` : '',\n 'Z',\n ]\n .filter(Boolean)\n .join(' ');\n}\n\nfunction renderRectMark(mark: RectMark, index: number): SVGElement {\n const g = createSVGElement('g');\n g.setAttribute('data-mark-id', `rect-${index}`);\n g.setAttribute('class', 'oc-mark oc-mark-rect');\n stampAnimationAttrs(g, mark, index);\n // Use engine-provided orientation for animation direction\n if (currentAnimation?.enter && mark.orient === 'horizontal') {\n g.setAttribute('data-orient', 'horizontal');\n }\n\n // When `cornerRadiusSides` selects a subset of corners (e.g. top-only for\n // the topmost segment of a stacked column), SVG's `rx`/`ry` won't do —\n // it rounds all four corners or none. Emit a `<path>` with per-corner\n // arcs in that case so the stacked segments stay flush at the seam.\n const sides = mark.cornerRadiusSides;\n const partialCorners =\n !!sides && (!sides.tl || !sides.tr || !sides.br || !sides.bl) && !!mark.cornerRadius;\n const shapeEl = partialCorners ? createSVGElement('path') : createSVGElement('rect');\n if (partialCorners) {\n shapeEl.setAttribute('d', rectPathWithCorners(mark, sides));\n } else {\n setAttrs(shapeEl, {\n x: mark.x,\n y: mark.y,\n width: mark.width,\n height: mark.height,\n });\n if (mark.cornerRadius) {\n setAttrs(shapeEl, { rx: mark.cornerRadius, ry: mark.cornerRadius });\n }\n }\n shapeEl.setAttribute('fill', resolveFillOrPattern(mark.fill, mark.pattern));\n // Only when set: an unconditional fill-opacity=\"1\" would change the\n // serialized SVG of every existing bar and churn the visual baselines.\n // `fill-opacity` is a different property from the `opacity` that the\n // hover-emphasis CSS drives, so the two compose rather than fight.\n if (mark.fillOpacity !== undefined) {\n shapeEl.setAttribute('fill-opacity', String(mark.fillOpacity));\n }\n if (mark.stroke) {\n shapeEl.setAttribute('stroke', mark.stroke);\n }\n if (mark.strokeWidth) {\n shapeEl.setAttribute('stroke-width', String(mark.strokeWidth));\n }\n if (mark.shapeRendering) {\n shapeEl.setAttribute('shape-rendering', mark.shapeRendering);\n }\n g.appendChild(shapeEl);\n\n // Labels for rect marks are rendered in a dedicated overlay group by\n // renderMarks() so they always paint above all bars in SVG z-order.\n // See the two-pass logic in renderMarks() below.\n\n return g;\n}\n\nfunction renderArcMark(mark: ArcMark, index: number): SVGElement {\n const g = createSVGElement('g');\n g.setAttribute('data-mark-id', `arc-${index}`);\n g.setAttribute('class', 'oc-mark oc-mark-arc');\n g.setAttribute('transform', `translate(${mark.center.x},${mark.center.y})`);\n stampAnimationAttrs(g, mark, index);\n\n const path = createSVGElement('path');\n setAttrs(path, {\n d: mark.path,\n fill: resolveFillOrPattern(mark.fill, mark.pattern),\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth,\n });\n g.appendChild(path);\n\n // Render label if present and visible\n if (mark.label?.visible) {\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-mark-label');\n // Label position is in absolute coords, but we're in a translated group,\n // so subtract the center offset\n setAttrs(label, {\n x: mark.label.x - mark.center.x,\n y: mark.label.y - mark.center.y,\n });\n applyTextStyle(label, mark.label.style);\n applyLabelHalo(label, mark.label);\n label.textContent = mark.label.text;\n g.appendChild(label);\n }\n\n return g;\n}\n\nfunction renderPointMark(mark: PointMark, index: number): SVGElement {\n const circle = createSVGElement('circle');\n circle.setAttribute('data-mark-id', `point-${index}`);\n circle.setAttribute('class', 'oc-mark oc-mark-point');\n stampAnimationAttrs(circle, mark, index);\n\n setAttrs(circle, {\n cx: mark.cx,\n cy: mark.cy,\n r: mark.r,\n fill: resolveMarkFill(mark.fill, currentGradientMap),\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth,\n });\n if (mark.fillOpacity !== undefined) {\n circle.setAttribute('fill-opacity', String(mark.fillOpacity));\n }\n return circle;\n}\n\nfunction renderTextMark(mark: TextMarkLayout, index: number): SVGElement {\n const text = createSVGElement('text');\n text.setAttribute('data-mark-id', `textMark-${index}`);\n text.setAttribute('class', 'oc-mark oc-mark-text');\n stampAnimationAttrs(text, mark, index);\n\n setAttrs(text, {\n x: mark.x,\n y: mark.y,\n 'font-size': mark.fontSize,\n 'text-anchor': mark.textAnchor,\n });\n // Only when set: calendar and parliament text marks hand-compute their y\n // against the default alphabetic baseline, so stamping one would shift them.\n if (mark.dominantBaseline) {\n text.setAttribute('dominant-baseline', mark.dominantBaseline);\n }\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', mark.fill);\n if (mark.fontWeight) {\n text.setAttribute('font-weight', String(mark.fontWeight));\n }\n if (mark.fontFamily) {\n text.setAttribute('font-family', mark.fontFamily);\n }\n if (mark.angle) {\n text.setAttribute('transform', `rotate(${mark.angle}, ${mark.x}, ${mark.y})`);\n }\n text.textContent = mark.text;\n return text;\n}\n\nfunction renderRuleMark(mark: RuleMarkLayout, index: number): SVGElement {\n const line = createSVGElement('line');\n line.setAttribute('data-mark-id', `rule-${index}`);\n line.setAttribute('class', 'oc-mark oc-mark-rule');\n stampAnimationAttrs(line, mark, index);\n\n setAttrs(line, {\n x1: mark.x1,\n y1: mark.y1,\n x2: mark.x2,\n y2: mark.y2,\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth,\n });\n if (mark.strokeDasharray) {\n line.setAttribute('stroke-dasharray', mark.strokeDasharray);\n }\n if (mark.strokeOpacity != null) {\n line.setAttribute('stroke-opacity', String(mark.strokeOpacity));\n }\n if (mark.opacity != null) {\n line.setAttribute('opacity', String(mark.opacity));\n }\n return line;\n}\n\nfunction renderTickMark(mark: TickMarkLayout, index: number): SVGElement {\n const line = createSVGElement('line');\n line.setAttribute('data-mark-id', `tick-${index}`);\n line.setAttribute('class', 'oc-mark oc-mark-tick');\n stampAnimationAttrs(line, mark, index);\n\n // Tick is a short line segment centered at (x, y)\n const half = mark.length / 2;\n if (mark.orient === 'vertical') {\n setAttrs(line, {\n x1: mark.x,\n y1: mark.y - half,\n x2: mark.x,\n y2: mark.y + half,\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth,\n });\n } else {\n setAttrs(line, {\n x1: mark.x - half,\n y1: mark.y,\n x2: mark.x + half,\n y2: mark.y,\n stroke: mark.stroke,\n 'stroke-width': mark.strokeWidth,\n });\n }\n\n if (mark.opacity != null) {\n line.setAttribute('opacity', String(mark.opacity));\n }\n return line;\n}\n\n// Register built-in renderers\nregisterMarkRenderer('line', renderLineMark as MarkRenderer<Mark>);\nregisterMarkRenderer('area', renderAreaMark as MarkRenderer<Mark>);\nregisterMarkRenderer('rect', renderRectMark as MarkRenderer<Mark>);\nregisterMarkRenderer('arc', renderArcMark as MarkRenderer<Mark>);\nregisterMarkRenderer('point', renderPointMark as MarkRenderer<Mark>);\nregisterMarkRenderer('textMark', renderTextMark as MarkRenderer<Mark>);\nregisterMarkRenderer('rule', renderRuleMark as MarkRenderer<Mark>);\nregisterMarkRenderer('tick', renderTickMark as MarkRenderer<Mark>);\n\n/**\n * Render a single mark to an SVG element without appending it anywhere.\n * Used by the transition module to create ghost elements for exiting marks.\n */\nexport function renderSingleMark(mark: Mark, index: number): SVGElement | undefined {\n const renderer = markRenderers[mark.type];\n if (!renderer) return undefined;\n return renderer(mark, index);\n}\n\n/** Extract series name from a mark for legend toggle matching. */\nfunction getMarkSeries(mark: Mark): string | undefined {\n // Line and area marks have an explicit seriesKey\n if (mark.type === 'line' || mark.type === 'area') {\n return mark.seriesKey;\n }\n // Rect/arc/point marks carry an explicit seriesKey whenever a color field\n // groups them. It is the authoritative value: the aria fallback below yields\n // \"Jan, US\" for a grouped bar, which matches no legend entry.\n const explicitSeries = (mark as { seriesKey?: string }).seriesKey;\n if (explicitSeries) {\n return explicitSeries;\n }\n // For arc marks, the category name is the first part of the aria label (before ':')\n if (mark.type === 'arc') {\n const label = mark.aria.label;\n if (!label) return undefined;\n const i = label.indexOf(':');\n return (i === -1 ? label : label.slice(0, i)).trim();\n }\n // For rect/point, the aria label may be \"category: value\" or \"category, group: value\".\n // The series name is the category part (before the colon).\n if (mark.aria?.label) {\n const label = mark.aria.label;\n const i = label.indexOf(':');\n const beforeColon = (i === -1 ? label : label.slice(0, i)).trim();\n if (beforeColon) return beforeColon;\n }\n return undefined;\n}\n\n/**\n * Render chart marks into `parent`.\n *\n * Returns an optional overlay group containing rect mark value labels.\n * The caller should append this group to the outer SVG (outside any clip path)\n * so tall labels above near-full-height bars are not clipped by the chart-area\n * clip path that constrains the marks themselves.\n */\nexport function renderMarks(\n parent: SVGElement,\n layout: ChartLayout,\n opts?: { skipPoints?: boolean },\n): SVGElement | undefined {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-marks');\n\n for (let i = 0; i < layout.marks.length; i++) {\n const mark = layout.marks[i];\n // Canvas mark mode paints point marks on the canvas layer instead. The\n // point renderer stays registered: SVG mode, exit ghosts, and full-fidelity\n // exports all still go through it.\n if (opts?.skipPoints && mark.type === 'point') continue;\n const renderer = markRenderers[mark.type];\n if (!renderer) continue;\n\n const el = renderer(mark, i);\n // Decorative marks (e.g. sparkline endpoint dots) are hidden from\n // assistive tech because they duplicate an existing data point.\n if (mark.aria?.decorative) {\n el.setAttribute('aria-hidden', 'true');\n } else if (mark.aria?.label) {\n el.setAttribute('aria-label', mark.aria.label);\n }\n // Add data-series attribute for legend toggle matching\n const series = getMarkSeries(mark);\n if (series) {\n el.setAttribute('data-series', series);\n }\n\n // For stacked segments, set stack position for sequential animation chaining.\n // stackPos is computed by the engine on RectMark during compilation.\n if (currentAnimation?.enter && mark.type === 'rect') {\n const rect = mark as RectMark;\n if (rect.stackGroup && rect.stackPos !== undefined) {\n el.setAttribute('data-stack-pos', String(rect.stackPos));\n (el as SVGElement & ElementCSSInlineStyle).style.setProperty(\n '--oc-stack-pos',\n String(rect.stackPos),\n );\n }\n }\n\n g.appendChild(el);\n }\n\n parent.appendChild(g);\n\n // Second pass: render rect mark labels in a dedicated overlay group so they\n // always paint above all bars in SVG z-order. On grouped column charts, a\n // later-rendered bar can otherwise paint over a label from an adjacent column\n // when the label extends into a neighbor's airspace.\n //\n // Each overlay label gets --oc-mark-index stamped directly on it so the CSS\n // animation delay calc (which reads that property) works without inheriting\n // from a parent group.\n let labelsGroup: SVGElement | undefined;\n for (let i = 0; i < layout.marks.length; i++) {\n const mark = layout.marks[i];\n if (mark.type !== 'rect') continue;\n const rect = mark as RectMark;\n if (!rect.label?.visible) continue;\n\n if (!labelsGroup) {\n labelsGroup = createSVGElement('g');\n labelsGroup.setAttribute('class', 'oc-mark-labels');\n }\n\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-mark-label');\n setAttrs(label, { x: rect.label.x, y: rect.label.y });\n applyTextStyle(label, rect.label.style);\n applyLabelHalo(label, rect.label);\n label.textContent = rect.label.text;\n\n // Stamp animation index so the CSS stagger delay works for overlay labels\n // (they're not children of a .oc-mark-rect group that carries --oc-mark-index).\n if (currentAnimation?.enter) {\n const idx = rect.animationIndex ?? i;\n label.setAttribute('data-animation-index', String(idx));\n (label as SVGElement & ElementCSSInlineStyle).style.setProperty(\n '--oc-mark-index',\n String(idx),\n );\n }\n\n labelsGroup.appendChild(label);\n }\n\n return labelsGroup;\n}\n","/**\n * SVG renderer: converts a ChartLayout into SVG DOM elements.\n *\n * Creates an <svg> element with viewBox matching layout dimensions,\n * renders chrome (title/subtitle/source), axes, marks, annotations,\n * and legend. All styling via inline SVG attributes from layout data.\n *\n * This file is the orchestrator only. Each rendering concern lives in its\n * own module under `./renderers/`.\n */\n\nimport type {\n ChartLayout,\n FacetPanelLayout,\n RectMark,\n ResolvedFillPattern,\n} from '@opendata-ai/openchart-core';\nimport { clampStaggerDelay } from '@opendata-ai/openchart-engine';\nimport { stampAnimationVars } from './animation-vars';\nimport { buildGradientDefs } from './gradient-utils';\nimport { buildPatternDefs } from './pattern-utils';\nimport { renderAnnotations } from './renderers/annotations';\nimport { renderAxes } from './renderers/axes';\nimport { renderBrand } from './renderers/brand';\nimport { renderChrome } from './renderers/chrome';\nimport { renderEndpointLabels } from './renderers/endpoint-labels';\nimport { renderLegend } from './renderers/legend';\nimport { renderMarks, resetMarkRenderState, setMarkRenderState } from './renderers/marks';\nimport { renderMetrics } from './renderers/metrics';\nimport { createSVGElement, SVG_NS, setAttrs } from './renderers/svg-dom';\nimport { nextSvgId } from './svg-ids';\nimport { resolvedSurface } from './theme-tokens';\n\n// Re-export registerMarkRenderer so external consumers can still register\n// custom mark renderers via the vanilla package entry point.\nexport { registerMarkRenderer } from './renderers/marks';\n\n/**\n * Render a compiled ChartLayout into an SVG element and append it to a container.\n *\n * @param layout - Compiled ChartLayout from compileChart().\n * @param container - DOM element to mount the SVG into.\n * @returns The created SVG element.\n */\nexport function renderChartSVG(\n layout: ChartLayout,\n container: HTMLElement,\n opts?: { animate?: boolean; crosshair?: boolean; canvasMarks?: boolean },\n): SVGElement {\n const { width, height } = layout.dimensions;\n const animation = layout.animation;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n setAttrs(svg, {\n viewBox: `0 0 ${width} ${height}`,\n xmlns: SVG_NS,\n // The SVG spec default is overflow:\"hidden\", which clips anything a hair\n // outside the viewBox. We now position all text on the alphabetic/central\n // baseline (dominant-baseline:hanging was dropped because WebKit computed\n // it from different metrics), but WebKit/iOS still reports getBBox extents\n // with a few pixels of slack around tspans, so text touching an edge can\n // still get clipped. overflow:visible avoids that. Chart marks are already\n // constrained by a clipPath, so nothing bleeds out.\n overflow: 'visible',\n // Hint browsers to enable sub-pixel font hinting and kerning for chart text.\n 'text-rendering': 'optimizeLegibility',\n });\n // Set explicit pixel height via inline style. iOS Safari misresolves CSS\n // height:100% when the ancestor chain uses minHeight instead of height,\n // causing the top of the chart (title) to clip on real mobile devices.\n svg.style.height = `${height}px`;\n // Author opt-out (a11y.hidden) hides the chart from assistive technology;\n // mount.ts also skips the screen-reader table and keyboard nav wiring.\n if (layout.a11y.hidden) {\n svg.setAttribute('aria-hidden', 'true');\n } else {\n svg.setAttribute('role', layout.a11y.role);\n svg.setAttribute('aria-label', layout.a11y.altText);\n }\n\n // Sparkline display mode: stamp a data attribute so consumers can target\n // sparkline-specific styles. Only set the attribute in sparkline mode so\n // regular charts keep an unchanged DOM signature.\n if (layout.display === 'sparkline') {\n svg.setAttribute('data-display', 'sparkline');\n }\n\n // oc-animate must be set before the SVG enters the DOM to prevent a flash\n // of the final state. mount.ts passes animate: true only on genuine first render.\n const classes = opts?.animate ? 'oc-chart oc-animate' : 'oc-chart';\n svg.setAttribute('class', classes);\n\n // Set animation CSS custom properties when the enter phase is enabled\n const enterPhase = animation?.enter;\n if (enterPhase) {\n const markCount = layout.marks.length;\n const stagger = clampStaggerDelay(enterPhase.staggerDelay, markCount);\n\n // Compute per-segment duration for stacked bars so the total bar animation\n // time stays consistent regardless of segment count.\n // stackPos is set by the engine (0-indexed position within each stack group).\n let maxSegments = 0;\n for (const m of layout.marks) {\n if (m.type === 'rect') {\n const pos = (m as RectMark).stackPos;\n if (pos !== undefined && pos + 1 > maxSegments) {\n maxSegments = pos + 1;\n }\n }\n }\n const stackSegmentDuration =\n maxSegments > 0 ? Math.round(enterPhase.duration / maxSegments) : undefined;\n\n stampAnimationVars(svg, {\n duration: enterPhase.duration,\n stagger,\n annotationDelay: animation!.annotationDelay,\n ease: enterPhase.ease,\n stackSegmentDuration,\n });\n }\n\n // Background. Sparkline mode skips the background rect entirely so the\n // host page's surface color shows through — sparklines are drop-ins for\n // KPI cards, table cells, and inline contexts where the consumer owns\n // the background. Other display modes paint a fill so the chart is a\n // self-contained visual on any host surface.\n // Canvas mark mode paints the background full-bleed on the canvas layer\n // beneath this SVG, so the SVG must not paint an opaque rect over it.\n //\n // AUTHORITY RULE: this renderer keys ONLY on opts.canvasMarks, never on\n // layout.markRenderMode. Static/SSR rendering (static.ts under happy-dom,\n // where no canvas exists) omits the option and therefore always emits a\n // complete SVG -- background, gridlines, and point marks included.\n const canvasMarks = opts?.canvasMarks === true;\n if (layout.display !== 'sparkline' && !canvasMarks) {\n const bg = createSVGElement('rect');\n setAttrs(bg, {\n x: 0,\n y: 0,\n width,\n height,\n fill: layout.theme.colors.background,\n });\n svg.appendChild(bg);\n }\n\n // Clip path to prevent marks (especially area fills) from overflowing\n // into the chrome region (title/subtitle). Extends full width so\n // end-of-line labels aren't clipped, but constrains vertically.\n const clipId = nextSvgId('oc-clip');\n const defs = createSVGElement('defs');\n const clipPath = createSVGElement('clipPath');\n clipPath.setAttribute('id', clipId);\n const maxPointR = layout.marks.reduce(\n (max, m) =>\n m.type === 'point' && (m as { r?: number }).r ? Math.max(max, (m as { r?: number }).r!) : max,\n 0,\n );\n const clipPad = Math.max(maxPointR, 2);\n const clipRect = createSVGElement('rect');\n setAttrs(clipRect, {\n x: 0,\n y: layout.area.y - clipPad,\n width,\n height: layout.area.height + clipPad * 2,\n });\n clipPath.appendChild(clipRect);\n defs.appendChild(clipPath);\n\n // Build gradient defs for marks with gradient fills\n const gradientMap = buildGradientDefs(layout.marks as Array<{ fill?: unknown }>, defs);\n\n // Build pattern defs for marks with engine-assigned fill patterns\n const patternMap = buildPatternDefs(\n layout.marks as Array<{ pattern?: ResolvedFillPattern }>,\n defs,\n );\n\n svg.appendChild(defs);\n\n // Prime mark-renderer module-level state so mark sub-renderers can resolve\n // animation + gradient/pattern fills without signature changes. try/finally\n // guarantees the reset fires even if any downstream renderer throws, so the\n // next render starts with a clean slate.\n setMarkRenderState({\n animation,\n gradientMap,\n patternMap,\n surface: resolvedSurface(layout.theme),\n });\n try {\n if (layout.facet) {\n // Faceted rendering: per-panel axes, marks, annotations\n renderFacetedPanels(svg, layout, layout.facet.panels, defs);\n } else {\n // Standard (non-faceted) rendering\n // Axes render outside clip (labels extend beyond chart area).\n // In canvas mark mode the canvas paints gridlines under the marks;\n // ticks, tick labels, and axis titles stay SVG.\n renderAxes(svg, layout, { skipGridlines: canvasMarks });\n\n // Marks are clipped to chart area so area fills don't cover chrome.\n // The clip and the camera transform must live on SEPARATE, nested groups:\n // `clip-path` with the default userSpaceOnUse resolves in the coordinate\n // system of the element's own `transform`, so putting both on one group\n // would drag the clip rect along with a story-camera zoom/pan and stop it\n // masking at the chart-area boundary. The outer group holds the clip fixed\n // to the area; the inner `data-oc-marks-group` is the stable hook external\n // features (e.g. the scrollytelling story camera) transform, so the camera\n // moves inside the clip. SVG transform (not CSS) so it composes with each\n // mark's own transform rather than replacing it.\n const clipGroup = createSVGElement('g');\n clipGroup.setAttribute('clip-path', `url(#${clipId})`);\n const clippedGroup = createSVGElement('g');\n clippedGroup.setAttribute('data-oc-marks-group', 'true');\n clipGroup.appendChild(clippedGroup);\n const markLabelsOverlay = renderMarks(clippedGroup, layout, { skipPoints: canvasMarks });\n\n // Single query+parse pass over point circles, reused below by both the\n // voronoi-overlay pointer-events pass and the endpoint-label suppression\n // pass, instead of each separately re-querying/re-parsing the subtree.\n let pointCircles: SVGCircleElement[] | null = null;\n let pointCircleCoords: { cx: number; cy: number }[] | null = null;\n const getPointCircles = () => {\n if (pointCircles === null) {\n pointCircles = Array.from(\n clippedGroup.querySelectorAll<SVGCircleElement>('circle.oc-mark-point'),\n );\n }\n return pointCircles;\n };\n const getPointCircleCoords = () => {\n if (pointCircleCoords === null) {\n pointCircleCoords = getPointCircles().map((el) => ({\n cx: Number(el.getAttribute('cx')),\n cy: Number(el.getAttribute('cy')),\n }));\n }\n return pointCircleCoords;\n };\n\n // Add transparent overlay rect for line/area charts to enable voronoi tooltip lookup.\n const hasLineOrAreaWithDataPoints = layout.marks.some(\n (m) => (m.type === 'line' || m.type === 'area') && m.dataPoints && m.dataPoints.length > 0,\n );\n if (hasLineOrAreaWithDataPoints) {\n for (const el of getPointCircles()) {\n el.setAttribute('pointer-events', 'none');\n }\n\n const overlay = createSVGElement('rect');\n setAttrs(overlay, {\n x: layout.area.x,\n y: layout.area.y,\n width: layout.area.width,\n height: layout.area.height,\n fill: 'transparent',\n });\n overlay.setAttribute('class', 'oc-voronoi-overlay');\n overlay.setAttribute('data-voronoi-overlay', 'true');\n clippedGroup.appendChild(overlay);\n\n if (opts?.crosshair) {\n const crosshairLine = createSVGElement('line');\n crosshairLine.setAttribute('data-crosshair', 'true');\n crosshairLine.setAttribute('class', 'oc-crosshair');\n setAttrs(crosshairLine, {\n x1: 0,\n y1: layout.area.y,\n x2: 0,\n y2: layout.area.y + layout.area.height,\n stroke: layout.theme.colors.axis,\n 'stroke-opacity': '0.4',\n 'stroke-dasharray': '3,3',\n 'stroke-width': '1',\n 'pointer-events': 'none',\n });\n crosshairLine.style.display = 'none';\n clippedGroup.appendChild(crosshairLine);\n }\n\n const dotsGroup = createSVGElement('g');\n dotsGroup.setAttribute('data-snap-dots', 'true');\n dotsGroup.setAttribute('class', 'oc-snap-dots');\n dotsGroup.setAttribute('pointer-events', 'none');\n clippedGroup.appendChild(dotsGroup);\n }\n\n svg.appendChild(clipGroup);\n\n // Annotations go under the data-label overlay so a refline or range\n // passes behind a label's knockout halo instead of over it; auto\n // placement already keeps annotation text clear of data labels.\n renderAnnotations(svg, layout);\n\n if (markLabelsOverlay) {\n svg.appendChild(markLabelsOverlay);\n }\n\n renderEndpointLabels(svg, layout);\n\n // Suppress decorative point marks under endpoint markers\n const epEntries = layout.endpointLabels?.entries ?? [];\n if (epEntries.length > 0) {\n const pointEls = getPointCircles();\n const coords = getPointCircleCoords();\n for (const entry of epEntries) {\n if (!entry.marker) continue;\n const mx = entry.marker.dataX;\n const my = entry.marker.y;\n for (let i = 0; i < pointEls.length; i++) {\n const { cx, cy } = coords[i];\n if (Math.abs(cx - mx) < 0.5 && Math.abs(cy - my) < 0.5) {\n pointEls[i].setAttribute('opacity', '0');\n }\n }\n }\n }\n }\n\n // Every legend, not just the primary: a bubble chart carries a color legend\n // AND a size legend. Falls back to the singular slot for layouts built by\n // paths that don't populate `legends` (graph, sankey).\n for (const legend of layout.legends ?? [layout.legend]) {\n renderLegend(svg, legend);\n }\n\n // Chrome renders on top so titles are never obscured by chart elements\n renderChrome(svg, layout);\n renderMetrics(svg, layout);\n\n // Brand renders as a footer item, right-aligned on the source/footer row.\n // Suppressed when the spec supplies a custom chrome.brand so the two\n // brand blocks don't stack.\n if (layout.watermark && !layout.chrome.brand) {\n renderBrand(svg, layout);\n }\n } finally {\n resetMarkRenderState();\n }\n\n if (canvasMarks) {\n // Pointer events fall through the SVG to the canvas beneath, which owns\n // quadtree hit-testing for point marks. Interactive chrome that still\n // lives in the SVG (legend toggles, annotation and chrome edit handles)\n // opts back in individually.\n svg.style.pointerEvents = 'none';\n for (const g of svg.querySelectorAll('.oc-legend, .oc-annotations, .oc-chrome, .oc-metrics')) {\n (g as SVGElement & ElementCSSInlineStyle).style.pointerEvents = 'auto';\n }\n // The canvas is absolutely positioned, so it would paint over a statically\n // positioned SVG regardless of DOM order. Positioning both makes DOM order\n // decide, keeping the canvas underneath.\n svg.style.position = 'relative';\n }\n\n container.appendChild(svg);\n return svg;\n}\n\nfunction renderFacetedPanels(\n svg: SVGElement,\n layout: ChartLayout,\n panels: FacetPanelLayout[],\n defs: SVGElement,\n): void {\n for (const panel of panels) {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-facet-panel');\n g.setAttribute('data-facet', panel.key);\n\n // Panel background for visual grouping (starts below the header)\n const bg = createSVGElement('rect');\n setAttrs(bg, {\n x: panel.area.x,\n y: panel.area.y,\n width: panel.area.width,\n height: panel.area.height,\n rx: 3,\n fill: layout.theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)',\n });\n g.appendChild(bg);\n\n // Panel header label\n const headerText = createSVGElement('text');\n setAttrs(headerText, {\n x: panel.header.x,\n y: panel.header.y,\n 'text-anchor': panel.header.textAnchor ?? 'middle',\n 'font-family': layout.theme.fonts.family,\n 'font-size': panel.header.fontSize,\n 'font-weight': panel.header.fontWeight,\n fill: layout.theme.colors.text,\n });\n headerText.textContent = panel.header.text;\n g.appendChild(headerText);\n\n // Build a per-panel synthetic layout for the existing renderers\n const panelLayout: ChartLayout = {\n ...layout,\n area: panel.area,\n axes: panel.axes,\n marks: panel.marks,\n annotations: panel.annotations,\n };\n\n // Panel axes (ticks already suppressed for inner panels by the engine)\n renderAxes(g, panelLayout);\n\n // Panel clip path\n const panelClipId = nextSvgId('oc-facet-clip');\n const clipPath = createSVGElement('clipPath');\n clipPath.setAttribute('id', panelClipId);\n const clipRect = createSVGElement('rect');\n setAttrs(clipRect, {\n x: panel.area.x,\n y: panel.area.y,\n width: panel.area.width,\n height: panel.area.height,\n });\n clipPath.appendChild(clipRect);\n defs.appendChild(clipPath);\n\n // Panel marks (clipped)\n const clippedGroup = createSVGElement('g');\n clippedGroup.setAttribute('clip-path', `url(#${panelClipId})`);\n const panelLabelsOverlay = renderMarks(clippedGroup, panelLayout);\n g.appendChild(clippedGroup);\n\n // Panel annotations, under the label overlay (same order as the single path)\n renderAnnotations(g, panelLayout);\n\n if (panelLabelsOverlay) {\n g.appendChild(panelLabelsOverlay);\n }\n\n svg.appendChild(g);\n }\n}\n","/**\n * Shared animation CSS custom-property stamping for vanilla renderers.\n *\n * Every renderer (svg, table, tilemap, sankey, map, barlist) sets the same\n * family of `--oc-animation-*` custom properties before the element enters\n * the DOM. This centralizes that stamping so each renderer only computes\n * its own timing values and delegates the DOM writes here.\n */\n\n/** CSS easing preset map for inline style custom properties. */\nexport const EASE_VAR_MAP: Record<string, string> = {\n smooth: 'var(--oc-ease-smooth)',\n snappy: 'var(--oc-ease-snappy)',\n};\n\nexport interface AnimationVars {\n /** Entrance animation duration, in ms. */\n duration: number;\n /** Per-item stagger delay, in ms. May be fractional (map renderer) — do NOT round. */\n stagger: number;\n /** Delay before annotations begin animating, in ms. */\n annotationDelay?: number;\n /** Easing preset name, mapped through EASE_VAR_MAP (defaults to smooth). */\n ease?: string;\n /** Per-segment duration for stacked bar sequencing, in ms (svg-renderer only). */\n stackSegmentDuration?: number;\n}\n\n/** Stamp animation CSS custom properties onto an element's inline style. */\nexport function stampAnimationVars(el: ElementCSSInlineStyle, v: AnimationVars): void {\n el.style.setProperty('--oc-animation-duration', `${v.duration}ms`);\n el.style.setProperty('--oc-animation-stagger', `${v.stagger}ms`);\n if (v.annotationDelay !== undefined) {\n el.style.setProperty('--oc-annotation-delay', `${v.annotationDelay}ms`);\n }\n if (v.ease !== undefined) {\n el.style.setProperty('--oc-animation-ease', EASE_VAR_MAP[v.ease] || EASE_VAR_MAP.smooth);\n }\n if (v.stackSegmentDuration !== undefined) {\n el.style.setProperty('--oc-stack-segment-duration', `${v.stackSegmentDuration}ms`);\n }\n}\n","/**\n * Annotation rendering: range rects, reference lines, labels with connectors.\n */\n\nimport type { ChartLayout, Point, ResolvedAnnotation } from '@opendata-ai/openchart-core';\nimport {\n ARROWHEAD_LENGTH,\n BOLD_SPAN_FONT_WEIGHT,\n computeArrowheadPoints,\n DEFAULT_ANNOTATION_FONT_SIZE,\n DEFAULT_LINE_HEIGHT,\n parseAnnotationSpans,\n} from '@opendata-ai/openchart-engine';\nimport { resolvedSurface } from '../theme-tokens';\nimport { applyKnockoutHalo, applyTextStyle, createSVGElement, setAttrs } from './svg-dom';\n\n/** Stroke width shared by every connector voice except the drop-line hairline. */\nconst CONNECTOR_STROKE_WIDTH = 1.25;\n\n/**\n * Render an open-V arrowhead at the given tip with tangent direction. A stroked\n * polyline, not a filled triangle — the hand-annotated look.\n */\nfunction renderArrowhead(\n parent: SVGElement,\n tipX: number,\n tipY: number,\n tangentX: number,\n tangentY: number,\n stroke: string,\n): void {\n const head = computeArrowheadPoints(tipX, tipY, tangentX, tangentY);\n const arrow = createSVGElement('polyline');\n // `oc-annotation-arrowhead` is what edit mode selects on to find the tip. Both\n // the straight and the curved connector draw their head through here, and both\n // keep the shared `oc-annotation-connector` class for styling -- without the\n // dedicated class the drag code has to select `polyline.oc-annotation-connector`\n // and rely on no other polyline existing in the group.\n arrow.setAttribute('class', 'oc-annotation-connector oc-annotation-arrowhead');\n setAttrs(arrow, {\n points: [\n `${head.baseLeft.x},${head.baseLeft.y}`,\n `${head.tip.x},${head.tip.y}`,\n `${head.baseRight.x},${head.baseRight.y}`,\n ].join(' '),\n fill: 'none',\n stroke,\n 'stroke-width': CONNECTOR_STROKE_WIDTH,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n });\n parent.appendChild(arrow);\n}\n\n/**\n * Render a curved connector from a label to a data point: a single quadratic\n * with one decisive bend, no S-swoop. The control point leans along the axis the\n * connector left the text block on, so a vertical exit sweeps out and down while\n * a horizontal exit runs flat then dives.\n */\nfunction renderCurvedConnector(\n parent: SVGElement,\n from: Point,\n to: Point,\n stroke: string,\n showArrow: boolean,\n exit: 'horizontal' | 'vertical' | undefined,\n): void {\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n\n const vertical = exit ? exit === 'vertical' : Math.abs(dy) >= Math.abs(dx);\n const cpx = from.x + dx * (vertical ? 0.2 : 0.6);\n const cpy = from.y + dy * (vertical ? 0.6 : 0.2);\n\n // Tangent at the tip runs from the control point to the tip.\n const tx = to.x - cpx;\n const ty = to.y - cpy;\n const tLen = Math.sqrt(tx * tx + ty * ty) || 1;\n\n // When arrowed, stop the stroke at the arrowhead base so it doesn't poke\n // through the open V.\n const endX = showArrow ? to.x - (tx / tLen) * ARROWHEAD_LENGTH : to.x;\n const endY = showArrow ? to.y - (ty / tLen) * ARROWHEAD_LENGTH : to.y;\n\n const path = createSVGElement('path');\n path.setAttribute('class', 'oc-annotation-connector');\n setAttrs(path, {\n d: `M ${from.x} ${from.y} Q ${cpx} ${cpy} ${endX} ${endY}`,\n fill: 'none',\n stroke,\n 'stroke-width': CONNECTOR_STROKE_WIDTH,\n 'stroke-linecap': 'round',\n });\n parent.appendChild(path);\n\n if (showArrow) {\n renderArrowhead(parent, to.x, to.y, tx, ty, stroke);\n }\n}\n\n/**\n * Fill a `<text>` with annotation copy: one `<tspan>` per `**bold**` span, lines\n * split on `\\n`. The first span of each line carries the `x` reset and the `dy`\n * line advance, so multi-line and inline-bold compose. The engine measures these\n * same spans at these same weights, so bounds match what lands on screen.\n */\nfunction fillRichText(text: SVGElement, content: string, x: number, lineHeight: number): void {\n const lines = content.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const spans = parseAnnotationSpans(lines[i]);\n for (let s = 0; s < spans.length; s++) {\n const tspan = createSVGElement('tspan');\n if (s === 0) {\n setAttrs(tspan, { x, dy: i === 0 ? 0 : lineHeight });\n }\n if (spans[s].bold) {\n tspan.setAttribute('font-weight', String(BOLD_SPAN_FONT_WEIGHT));\n }\n tspan.textContent = spans[s].text;\n text.appendChild(tspan);\n }\n }\n}\n\nfunction renderAnnotation(\n parent: SVGElement,\n annotation: ResolvedAnnotation,\n index: number,\n bgColor?: string,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', `oc-annotation oc-annotation-${annotation.type}`);\n // Edit mode reads this back as `spec.annotations[index]`, so it has to be the\n // SPEC index, not the render position. `layout.annotations` is both filtered\n // (an annotation outside the domain resolves to nothing and is dropped) and\n // re-sorted by `zIndex`, so the two diverge: with a zIndex set, position 0 can\n // be spec annotation 1, and dragging one label moves the other. Fall back to the\n // position for a hand-built layout that carries no `specIndex`.\n g.setAttribute('data-annotation-index', String(annotation.specIndex ?? index));\n if (annotation.id) {\n g.setAttribute('data-annotation-id', annotation.id);\n }\n\n // Range rect\n if (annotation.rect) {\n const rect = createSVGElement('rect');\n rect.setAttribute('class', 'oc-annotation-range');\n // Range fills cover large chart-area regions; if they intercept pointer\n // events the voronoi tooltip overlay below stops receiving mousemove\n // inside the range, creating hover dead zones. The label still receives\n // events for annotation click handlers.\n rect.setAttribute('pointer-events', 'none');\n setAttrs(rect, {\n x: annotation.rect.x,\n y: annotation.rect.y,\n width: annotation.rect.width,\n height: annotation.rect.height,\n });\n if (annotation.fill) rect.setAttribute('fill', annotation.fill);\n if (annotation.opacity !== undefined) {\n rect.setAttribute('fill-opacity', String(annotation.opacity));\n }\n g.appendChild(rect);\n }\n\n // Reference line\n if (annotation.line) {\n const line = createSVGElement('line');\n line.setAttribute('class', 'oc-annotation-line');\n setAttrs(line, {\n x1: annotation.line.start.x,\n y1: annotation.line.start.y,\n x2: annotation.line.end.x,\n y2: annotation.line.end.y,\n 'stroke-width': annotation.strokeWidth ?? 1,\n });\n if (annotation.stroke) line.setAttribute('stroke', annotation.stroke);\n if (annotation.strokeDasharray) {\n line.setAttribute('stroke-dasharray', annotation.strokeDasharray);\n }\n g.appendChild(line);\n }\n\n // Footnote marker: annotation was demoted by auto-thinning. Render a numbered\n // dot at the data point instead of the full label text.\n if (annotation.footnoteIndex != null && annotation.label) {\n // Prefer the data point. A suppressed connector (too short to be worth\n // drawing) still leaves a resolved dot there, so fall through to it before\n // giving up on the label position.\n const cx =\n annotation.label.connector?.endpoint?.x ??\n annotation.label.connector?.to.x ??\n annotation.dot?.x ??\n annotation.label.x;\n const cy =\n annotation.label.connector?.endpoint?.y ??\n annotation.label.connector?.to.y ??\n annotation.dot?.y ??\n annotation.label.y;\n const r = 8;\n const circle = createSVGElement('circle');\n circle.setAttribute('class', 'oc-annotation-footnote-marker');\n setAttrs(circle, {\n cx,\n cy,\n r,\n fill: bgColor ?? '#ffffff',\n stroke: annotation.label.style.fill ?? '#666',\n 'stroke-width': 1.5,\n });\n g.appendChild(circle);\n\n const num = createSVGElement('text');\n num.setAttribute('class', 'oc-annotation-footnote-number');\n setAttrs(num, {\n x: cx,\n y: cy,\n 'dominant-baseline': 'central',\n 'text-anchor': 'middle',\n });\n applyTextStyle(num, {\n ...annotation.label.style,\n fontSize: 9,\n fontWeight: 600,\n });\n num.textContent = String(annotation.footnoteIndex);\n g.appendChild(num);\n parent.appendChild(g);\n return;\n }\n\n // Label with optional connector line\n if (annotation.label?.visible) {\n // Render connector first (behind the label text)\n if (annotation.label.connector) {\n const c = annotation.label.connector;\n if (c.style === 'curve') {\n renderCurvedConnector(g, c.from, c.to, c.stroke, c.arrow, c.exit);\n } else if (c.style === 'drop-line') {\n // Quiet voice: hairline, slightly recessed, snapped to the pixel grid.\n const connector = createSVGElement('line');\n connector.setAttribute('class', 'oc-annotation-connector oc-annotation-drop-line');\n setAttrs(connector, {\n x1: c.from.x,\n y1: c.from.y,\n x2: c.to.x,\n y2: c.to.y,\n stroke: c.stroke,\n 'stroke-width': 1,\n 'stroke-opacity': 0.7,\n 'shape-rendering': 'crispEdges',\n });\n g.appendChild(connector);\n } else if (c.arrow) {\n const dx = c.to.x - c.from.x;\n const dy = c.to.y - c.from.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n const ux = dx / dist;\n const uy = dy / dist;\n const lineEndX = c.to.x - ux * ARROWHEAD_LENGTH;\n const lineEndY = c.to.y - uy * ARROWHEAD_LENGTH;\n\n const connector = createSVGElement('line');\n connector.setAttribute('class', 'oc-annotation-connector');\n setAttrs(connector, {\n x1: c.from.x,\n y1: c.from.y,\n x2: lineEndX,\n y2: lineEndY,\n stroke: c.stroke,\n 'stroke-width': CONNECTOR_STROKE_WIDTH,\n 'stroke-linecap': 'round',\n });\n g.appendChild(connector);\n\n renderArrowhead(g, c.to.x, c.to.y, dx, dy, c.stroke);\n } else {\n const connector = createSVGElement('line');\n connector.setAttribute('class', 'oc-annotation-connector');\n setAttrs(connector, {\n x1: c.from.x,\n y1: c.from.y,\n x2: c.to.x,\n y2: c.to.y,\n stroke: c.stroke,\n 'stroke-width': CONNECTOR_STROKE_WIDTH,\n 'stroke-linecap': 'round',\n });\n g.appendChild(connector);\n }\n }\n\n // Endpoint marker: rendered AFTER the connector but BEFORE the label text so\n // it sits on top of the leader and under any text halo. The engine resolves\n // dot.x/y at the data point (never the pulled-back connector tip) and picks\n // the stroke so marker and leader read as one system.\n if (annotation.dot) {\n const dot = createSVGElement('circle');\n dot.setAttribute('class', 'oc-annotation-dot');\n setAttrs(dot, {\n cx: annotation.dot.x,\n cy: annotation.dot.y,\n r: annotation.dot.radius,\n fill: annotation.dot.fill,\n stroke: annotation.dot.stroke,\n 'stroke-width': annotation.dot.strokeWidth,\n });\n g.appendChild(dot);\n }\n\n const text = createSVGElement('text');\n text.setAttribute('class', 'oc-annotation-label');\n setAttrs(text, { x: annotation.label.x, y: annotation.label.y });\n applyTextStyle(text, annotation.label.style);\n\n const fontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;\n const lineHeight = fontSize * (annotation.label.style.lineHeight ?? DEFAULT_LINE_HEIGHT);\n\n fillRichText(text, annotation.label.text, annotation.label.x, lineHeight);\n\n // Render background rect behind text if specified, otherwise use\n // paint-order stroke halo to knock out lines behind text\n if (annotation.label.background && annotation.label.bounds) {\n // Size the plate from the engine's measured bounds, full stop. There used to\n // be a fallback branch here that re-derived the box from `fontSize * 0.55`\n // per character and center-aligned it for multi-line text. It was\n // unreachable (every resolver stamps `label.bounds`), but it was a third\n // copy of text geometry living in the renderer, and a *stale* one: it\n // center-aligned, which the redesign explicitly does not do. Dead code that\n // contradicts live code is the drift trap, not protection from it.\n const pad = 3;\n const b = annotation.label.bounds;\n const bgX = b.x - pad;\n const bgY = b.y - pad;\n const bgW = b.width + pad * 2;\n const bgH = b.height + pad * 2;\n\n const bgRect = createSVGElement('rect');\n bgRect.setAttribute('class', 'oc-annotation-bg');\n setAttrs(bgRect, {\n x: bgX,\n y: bgY,\n width: bgW,\n height: bgH,\n fill: annotation.label.background,\n rx: 2,\n });\n g.appendChild(bgRect);\n } else if (bgColor && annotation.label.halo !== false) {\n applyKnockoutHalo(text, bgColor, fontSize);\n }\n\n g.appendChild(text);\n\n // Optional muted subtitle, positioned by the engine below the primary label.\n if (annotation.subtitle) {\n const sub = createSVGElement('text');\n sub.setAttribute('class', 'oc-annotation-subtitle');\n setAttrs(sub, { x: annotation.subtitle.x, y: annotation.subtitle.y });\n applyTextStyle(sub, annotation.subtitle.style);\n const subFontSize = annotation.subtitle.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;\n const subLineHeight =\n subFontSize * (annotation.subtitle.style.lineHeight ?? DEFAULT_LINE_HEIGHT);\n fillRichText(sub, annotation.subtitle.text, annotation.subtitle.x, subLineHeight);\n g.appendChild(sub);\n }\n }\n\n parent.appendChild(g);\n}\n\nexport function renderAnnotations(parent: SVGElement, layout: ChartLayout): void {\n if (layout.annotations.length === 0) return;\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-annotations');\n\n // Annotations are already sorted by zIndex from the engine, so render in order.\n // The knockout halo has to be cut in an OPAQUE color: theme.colors.background\n // defaults to 'transparent', which made every halo a no-op on default themes.\n const bgColor = resolvedSurface(layout.theme);\n for (let i = 0; i < layout.annotations.length; i++) {\n renderAnnotation(g, layout.annotations[i], i, bgColor);\n }\n\n parent.appendChild(g);\n}\n","/**\n * Axis rendering: axis line, ticks, tick labels, gridlines, axis title.\n */\n\nimport type { AxisLayout, ChartLayout } from '@opendata-ai/openchart-core';\nimport {\n axisTitleOffset,\n estimateTextWidth,\n TICK_LABEL_OFFSET,\n textAscent,\n} from '@opendata-ai/openchart-core';\nimport { serializeKeyValue } from '@opendata-ai/openchart-engine';\nimport { applyTextStyle, createSVGElement, setAttrs } from './svg-dom';\n\nfunction appendCompoundLabel(\n parent: SVGElement,\n primaryText: string,\n subtitle: string,\n fontWeight: number,\n): void {\n const primarySpan = createSVGElement('tspan');\n primarySpan.setAttribute('font-weight', String(fontWeight));\n primarySpan.textContent = primaryText;\n parent.appendChild(primarySpan);\n\n const subtitleSpan = createSVGElement('tspan');\n subtitleSpan.setAttribute('dx', '0.5em');\n subtitleSpan.textContent = subtitle;\n subtitleSpan.setAttribute('font-weight', '400');\n subtitleSpan.setAttribute('fill-opacity', '0.6');\n parent.appendChild(subtitleSpan);\n}\n\nfunction renderAxis(\n parent: SVGElement,\n axis: AxisLayout,\n orientation: 'x' | 'y',\n layout: ChartLayout,\n opts?: { skipGridlines?: boolean },\n): void {\n const g = createSVGElement('g');\n const isRight = orientation === 'y' && axis.orient === 'right';\n const isInlineY = orientation === 'y' && axis.tickPosition === 'inline' && !isRight;\n g.setAttribute(\n 'class',\n `oc-axis oc-axis-${isRight ? 'y2' : orientation}${isInlineY ? ' oc-axis-inline' : ''}`,\n );\n\n const { area } = layout;\n\n // Only draw axis line for x-axis (bottom baseline), unless explicitly disabled.\n // Horizontal gridlines already guide y-values, so the vertical y-axis line is redundant.\n if (orientation === 'x' && axis.domainLine !== false) {\n const line = createSVGElement('line');\n line.setAttribute('class', 'oc-axis-line');\n setAttrs(line, {\n x1: axis.start.x,\n y1: axis.start.y,\n x2: axis.end.x,\n y2: axis.end.y,\n stroke: layout.theme.colors.axis,\n 'stroke-width': 1,\n });\n g.appendChild(line);\n }\n\n // Ticks and labels\n // Tick positions are absolute pixel coordinates from D3 scales whose range\n // was set to [chartArea.x, chartArea.x + chartArea.width] (and similarly for y).\n // Don't add area.x/area.y again or you'll double-offset everything.\n for (const tick of axis.ticks) {\n // Stable key for data-update transitions\n const tickKey = serializeKeyValue(tick.value);\n\n if (orientation === 'x') {\n // Label (no tick marks -- gridlines provide sufficient reference)\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-axis-tick');\n label.setAttribute('data-tick-key', tickKey);\n\n if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {\n // Rotated labels: anchor at the rotation pivot point\n const labelX = tick.labelPosition ?? tick.position;\n const xLabelPad = axis.labelPadding ?? layout.theme.spacing.xAxisLabelPadding;\n const labelY = area.y + area.height + xLabelPad;\n setAttrs(label, {\n x: labelX,\n y: labelY,\n 'text-anchor': axis.tickAngle < 0 ? 'end' : 'start',\n 'dominant-baseline': 'central',\n transform: `rotate(${axis.tickAngle}, ${labelX}, ${labelY})`,\n });\n } else {\n const xLabelPad = axis.labelPadding ?? layout.theme.spacing.xAxisLabelPadding;\n // xLabelPad is the literal gap between the axis line and the TOP of\n // the label regardless of font size, so shift down by the ascent to\n // land on the alphabetic baseline. (dominant-baseline:hanging would\n // express this directly, but WebKit positions hanging from different\n // font metrics than Blink, drifting labels on iOS Safari.)\n setAttrs(label, {\n x: tick.labelPosition ?? tick.position,\n y: area.y + area.height + xLabelPad + textAscent(axis.tickLabelStyle.fontSize),\n 'text-anchor': 'middle',\n });\n }\n\n applyTextStyle(label, axis.tickLabelStyle);\n label.textContent = tick.label;\n // The engine ellipsizes rotated labels that would overflow the reserved\n // band and stashes the original in `fullLabel`. Expose it as a <title> so\n // the complete category is still reachable on hover and to a screen\n // reader — truncation is a visual affordance, not data loss.\n if (tick.fullLabel) {\n const title = createSVGElement('title');\n title.textContent = tick.fullLabel;\n label.appendChild(title);\n }\n g.appendChild(label);\n } else if (isInlineY) {\n // Inline y-tick: label sits above its gridline at the chart-area left\n // edge, no gutter reserved. The gridline itself is the visual axis.\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-axis-tick oc-axis-tick-inline');\n label.setAttribute('data-tick-key', tickKey);\n setAttrs(label, {\n x: area.x,\n y: tick.position - 6,\n 'text-anchor': 'start',\n });\n applyTextStyle(label, axis.tickLabelStyle);\n label.textContent = tick.label;\n g.appendChild(label);\n } else {\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-axis-tick');\n label.setAttribute('data-tick-key', tickKey);\n setAttrs(label, {\n x: isRight ? area.x + area.width + TICK_LABEL_OFFSET : area.x - TICK_LABEL_OFFSET,\n y: tick.position,\n 'text-anchor': isRight ? 'start' : 'end',\n 'dominant-baseline': 'central',\n });\n applyTextStyle(label, axis.tickLabelStyle);\n if (!isRight) {\n // Truncate categorical left y-axis labels that exceed available space\n const availableWidth = area.x - TICK_LABEL_OFFSET;\n const fontSize = axis.tickLabelStyle.fontSize;\n const fontWeight = axis.tickLabelStyle.fontWeight;\n\n if (tick.subtitle) {\n // Compound label: primary + gap + subtitle via tspan elements\n const gapWidth = fontSize * 0.5;\n const subtitleWidth = estimateTextWidth(tick.subtitle, fontSize, fontWeight);\n const primaryWidth = estimateTextWidth(tick.label, fontSize, fontWeight);\n const totalWidth = primaryWidth + gapWidth + subtitleWidth;\n\n if (totalWidth > availableWidth && availableWidth > 20) {\n const ellipsis = '…';\n const ellipsisWidth = estimateTextWidth(ellipsis, fontSize, fontWeight);\n const budgetForPrimary = availableWidth - gapWidth - subtitleWidth - ellipsisWidth;\n\n let primaryText = tick.label;\n if (budgetForPrimary > 0) {\n let lo = 0;\n let hi = tick.label.length;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n const candidate = tick.label.slice(0, mid);\n if (estimateTextWidth(candidate, fontSize, fontWeight) <= budgetForPrimary) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n primaryText = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;\n } else {\n primaryText = ellipsis;\n }\n\n appendCompoundLabel(label, primaryText, tick.subtitle, fontWeight);\n\n const titleEl = createSVGElement('title');\n titleEl.textContent = `${tick.label} ${tick.subtitle}`;\n label.appendChild(titleEl);\n } else {\n appendCompoundLabel(label, tick.label, tick.subtitle, fontWeight);\n }\n } else {\n // Plain label (no subtitle)\n const fullWidth = estimateTextWidth(tick.label, fontSize, fontWeight);\n if (fullWidth > availableWidth && availableWidth > 20) {\n const ellipsis = '…';\n const ellipsisWidth = estimateTextWidth(ellipsis, fontSize, fontWeight);\n let lo = 0;\n let hi = tick.label.length;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n const candidate = tick.label.slice(0, mid);\n if (\n estimateTextWidth(candidate, fontSize, fontWeight) + ellipsisWidth <=\n availableWidth\n ) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n label.textContent = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;\n const titleEl = createSVGElement('title');\n titleEl.textContent = tick.label;\n label.appendChild(titleEl);\n } else {\n label.textContent = tick.label;\n }\n }\n } else {\n label.textContent = tick.label;\n }\n g.appendChild(label);\n }\n }\n\n // Gridlines (positions are also absolute from the scales)\n // Skip gridlines for right-side y-axis (left y-axis gridlines are sufficient)\n if (!isRight && !opts?.skipGridlines) {\n // Build position -> tick value map for keying gridlines\n const posToTickKey = new Map<number, string>();\n for (const tick of axis.ticks) {\n posToTickKey.set(tick.position, serializeKeyValue(tick.value));\n }\n\n for (const gridline of axis.gridlines) {\n const gl = createSVGElement('line');\n gl.setAttribute('class', 'oc-gridline');\n // Stamp data-tick-key for data-update transitions\n const glKey = posToTickKey.get(gridline.position);\n if (glKey) {\n gl.setAttribute('data-tick-key', glKey);\n }\n if (orientation === 'y') {\n setAttrs(gl, {\n x1: area.x,\n y1: gridline.position,\n x2: area.x + area.width,\n y2: gridline.position,\n stroke: layout.theme.colors.gridline,\n 'stroke-width': 1,\n 'stroke-opacity': 0.6,\n });\n } else {\n setAttrs(gl, {\n x1: gridline.position,\n y1: area.y,\n x2: gridline.position,\n y2: area.y + area.height,\n stroke: layout.theme.colors.gridline,\n 'stroke-width': 1,\n 'stroke-opacity': 0.6,\n });\n }\n g.appendChild(gl);\n }\n }\n\n // Axis label\n if (axis.label && axis.labelStyle) {\n const axisLabel = createSVGElement('text');\n axisLabel.setAttribute('class', 'oc-axis-title');\n applyTextStyle(axisLabel, axis.labelStyle);\n axisLabel.textContent = axis.label;\n\n const tp = axis.titlePosition;\n if (tp) {\n const attrs: Record<string, string | number> = {\n x: tp.x,\n y: tp.y,\n 'text-anchor': 'middle',\n };\n if (tp.angle) {\n attrs.transform = `rotate(${tp.angle}, ${tp.x}, ${tp.y})`;\n }\n setAttrs(axisLabel, attrs);\n } else if (orientation === 'x') {\n const tickFontSize = axis.tickLabelStyle.fontSize;\n const tickBand = Math.max(26, 4 + tickFontSize + Math.max(11, Math.ceil(tickFontSize * 0.7)));\n let titleY = area.y + area.height + tickBand + 9;\n if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {\n const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);\n let maxLabelWidth = 40;\n for (const tick of axis.ticks) {\n const w = estimateTextWidth(tick.label, tickFontSize, axis.tickLabelStyle.fontWeight);\n if (w > maxLabelWidth) maxLabelWidth = w;\n }\n const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);\n titleY = area.y + area.height + rotatedHeight + 14;\n }\n setAttrs(axisLabel, {\n x: area.x + area.width / 2,\n y: titleY,\n 'text-anchor': 'middle',\n });\n } else if (isRight) {\n const maxTickLabelWidth = axis.ticks.reduce((max, t) => {\n const w = estimateTextWidth(\n t.label,\n axis.tickLabelStyle.fontSize,\n axis.tickLabelStyle.fontWeight ?? 400,\n );\n return Math.max(max, w);\n }, 0);\n const titleOffset = axisTitleOffset(\n maxTickLabelWidth,\n axis.labelStyle.fontSize,\n layout.dimensions.width,\n );\n const titleX = area.x + area.width + titleOffset;\n setAttrs(axisLabel, {\n x: titleX,\n y: area.y + area.height / 2,\n 'text-anchor': 'middle',\n transform: `rotate(90, ${titleX}, ${area.y + area.height / 2})`,\n });\n } else {\n const maxTickLabelWidth = axis.ticks.reduce((max, t) => {\n const w = estimateTextWidth(\n t.label,\n axis.tickLabelStyle.fontSize,\n axis.tickLabelStyle.fontWeight ?? 400,\n );\n return Math.max(max, w);\n }, 0);\n // Inline y-ticks live inside the plot, so the title clears only the chart\n // edge — mirror the engine's reservation (see axisTitleOffset).\n const titleOffset = axisTitleOffset(\n maxTickLabelWidth,\n axis.labelStyle.fontSize,\n layout.dimensions.width,\n isInlineY,\n );\n setAttrs(axisLabel, {\n x: area.x - titleOffset,\n y: area.y + area.height / 2,\n 'text-anchor': 'middle',\n transform: `rotate(-90, ${area.x - titleOffset}, ${area.y + area.height / 2})`,\n });\n }\n g.appendChild(axisLabel);\n }\n\n parent.appendChild(g);\n}\n\n/**\n * Render the chart's axes.\n *\n * `opts.skipGridlines` suppresses the gridline pass for canvas mark mode,\n * where the canvas layer paints gridlines beneath the marks instead. Ticks,\n * tick labels, and axis titles still render as SVG either way.\n */\nexport function renderAxes(\n parent: SVGElement,\n layout: ChartLayout,\n opts?: { skipGridlines?: boolean },\n): void {\n if (layout.axes.x) {\n renderAxis(parent, layout.axes.x, 'x', layout, opts);\n }\n if (layout.axes.y) {\n renderAxis(parent, layout.axes.y, 'y', layout, opts);\n }\n if (layout.axes.y2) {\n renderAxis(parent, layout.axes.y2, 'y', layout, opts);\n }\n}\n","/**\n * Brand rendering: the \"OpenData\" watermark footer.\n */\n\nimport type { ChartLayout } from '@opendata-ai/openchart-core';\nimport { BRAND_MIN_WIDTH, textAscent } from '@opendata-ai/openchart-core';\nimport { footnoteBandHeight } from './chrome';\nimport { createSVGElement, setAttrs, XLINK_NS } from './svg-dom';\n\nconst BRAND_URL = 'https://tryopendata.ai';\n\n/**\n * Render the brand watermark as a footer-row element, right-aligned on the\n * same baseline as the first bottom chrome text (source/byline/footer).\n * Uses the same font size as chrome source text so it blends in as a subtle\n * footer item rather than occupying independent visual space.\n */\nexport function renderBrand(parent: SVGElement, layout: ChartLayout): void {\n if (layout.dimensions.width < BRAND_MIN_WIDTH) return;\n\n const { width } = layout.dimensions;\n const padding = layout.theme.spacing.padding;\n const rightEdge = width - padding;\n const fill = layout.theme.colors.axis;\n\n // Vertically align with the first bottom chrome element.\n const { chrome } = layout;\n const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;\n const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;\n // When no bottom chrome items exist, derive a fallback offset that still\n // clears any bottom-positioned legend so the brand watermark doesn't\n // overlap legend swatches.\n const { legend } = layout;\n const bottomLegendOffset =\n legend.position === 'bottom' && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;\n // renderChrome pushes the whole footer row down past the footnote list; the\n // brand shares that row, so it takes the same shift or the footnotes land on\n // top of it.\n const bandHeight = footnoteBandHeight(layout);\n const chromeY = firstBottom\n ? bottomOffset + firstBottom.y + bandHeight\n : bottomOffset + layout.theme.spacing.chartToFooter + bottomLegendOffset + bandHeight;\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 // chromeY is the top edge shared with source/byline chrome text; anchor the\n // shared alphabetic baseline from the largest tspan's ascent. (Hanging\n // baseline is avoided: WebKit positions it differently and never inherits\n // it into tspans, which scattered the three spans on iOS Safari.)\n const BRAND_LARGE = 16;\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: chromeY + textAscent(BRAND_LARGE),\n 'font-family': layout.theme.fonts.family,\n 'font-size': BRAND_LARGE,\n 'font-weight': '600',\n 'text-anchor': 'end',\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n text.textContent = 'OpenData';\n\n a.appendChild(text);\n parent.appendChild(a);\n}\n","/**\n * Chrome rendering: title, subtitle, source, byline, footer.\n */\n\nimport type {\n ChartLayout,\n MeasureTextFn,\n ResolvedChromeElement,\n} from '@opendata-ai/openchart-core';\nimport {\n FOOTNOTE_LINE_HEIGHT,\n footnoteBandHeight as footnoteBandHeightFor,\n textAscent,\n truncateToWidth,\n wrapText,\n} from '@opendata-ai/openchart-core';\nimport { applyTextStyle, createSVGElement, setAttrs } from './svg-dom';\n\n/**\n * Vertical space the auto-thinning footnote list occupies below the plot.\n * Everything on the footer row (source, byline, footer, brand) shifts down by\n * this much so the footnotes don't land on top of it. Zero when there are no\n * footnotes. The brand watermark renders from a separate path\n * (`renderBrand`), so it has to read the same number from here rather than\n * recompute it.\n *\n * A `ChartLayout`-shaped adapter over the core helper -- deliberately not a\n * second implementation. The engine reserves this band in the bottom margin\n * from the same function, so a local copy of the arithmetic is a standing\n * invitation for the two to drift and drop the footnotes onto the watermark.\n */\nexport function footnoteBandHeight(layout: ChartLayout): number {\n return footnoteBandHeightFor(layout.chrome.footnotes?.length ?? 0, layout.theme);\n}\n\nexport function renderChromeElement(\n parent: SVGElement,\n element: ResolvedChromeElement,\n className: string,\n chromeKey: string,\n measureText?: MeasureTextFn,\n uppercase = false,\n): void {\n const text = createSVGElement('text');\n // element.y is the TOP of the text box; convert to the alphabetic baseline\n // here instead of relying on dominant-baseline:hanging, which WebKit\n // positions from different font metrics and never inherits into tspans.\n setAttrs(text, { x: element.x, y: element.y + textAscent(element.style.fontSize) });\n applyTextStyle(text, element.style);\n text.setAttribute('class', className);\n text.setAttribute('data-chrome-key', chromeKey);\n\n // happy-dom doesn't apply CSS text-transform inside SVG measurement, so\n // pre-uppercase the rendered text for elements that style as uppercase.\n // Browsers honor the CSS rule too, so this is double-applied harmlessly.\n const renderedText = uppercase ? element.text.toUpperCase() : element.text;\n\n const lines = wrapText(\n renderedText,\n element.style.fontSize,\n element.style.fontWeight,\n element.maxWidth,\n measureText,\n );\n\n // Cap wrapped output to maxLines, truncating the last kept line with an\n // ellipsis so it fits maxWidth. Only when the natural wrap exceeds the cap.\n let truncated = false;\n if (element.maxLines != null && lines.length > element.maxLines) {\n truncated = true;\n lines.length = element.maxLines;\n const lastIndex = element.maxLines - 1;\n const measure = measureText\n ? (text: string, fontSize: number, fontWeight?: number) =>\n measureText(text, fontSize, fontWeight).width\n : undefined;\n // Content was dropped, so the last kept line always gets an ellipsis.\n // Append it first, then shrink to fit maxWidth if the combined string\n // overflows.\n const withEllipsis = `${lines[lastIndex]}…`;\n lines[lastIndex] = truncateToWidth(\n withEllipsis,\n element.maxWidth,\n element.style.fontSize,\n element.style.fontWeight,\n measure,\n );\n }\n\n if (lines.length === 1 && !truncated) {\n text.textContent = renderedText;\n } else if (lines.length === 1) {\n text.textContent = lines[0];\n } else {\n const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);\n for (let i = 0; i < lines.length; i++) {\n const tspan = createSVGElement('tspan');\n setAttrs(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });\n tspan.textContent = lines[i];\n text.appendChild(tspan);\n }\n }\n\n parent.appendChild(text);\n}\n\nexport function renderChrome(parent: SVGElement, layout: ChartLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome, measureText } = layout;\n\n // Editorial rule: a short colored bar above the top chrome block.\n if (chrome.rule) {\n const rule = createSVGElement('rect');\n rule.setAttribute('class', 'oc-chrome-rule');\n setAttrs(rule, {\n x: chrome.rule.x,\n y: chrome.rule.y,\n width: chrome.rule.width,\n height: chrome.rule.thickness,\n });\n rule.setAttribute('fill', chrome.rule.color);\n rule.setAttribute('shape-rendering', 'crispEdges');\n g.appendChild(rule);\n }\n\n // Top chrome: render at their stored y positions (already absolute)\n if (chrome.eyebrow && chrome.rule) {\n // The rule already carries the accent above the block; a dot as well is\n // two pieces of furniture saying the same thing.\n renderChromeElement(g, chrome.eyebrow, 'oc-eyebrow', 'eyebrow', measureText, true);\n } else if (chrome.eyebrow) {\n // Leading accent dot — matches the editorial design system mock.\n // eyebrow.y is the top of the text box. Visual center is roughly\n // y + fontSize * 0.55 (cap height).\n const eyebrow = chrome.eyebrow;\n const dotR = 3;\n const dotGap = 8;\n const dotX = eyebrow.x + dotR;\n const dotY = eyebrow.y + eyebrow.style.fontSize * 0.42;\n const dot = createSVGElement('circle');\n dot.setAttribute('class', 'oc-eyebrow-dot');\n setAttrs(dot, { cx: dotX, cy: dotY, r: dotR });\n dot.setAttribute('fill', eyebrow.style.fill ?? 'currentColor');\n g.appendChild(dot);\n\n const shifted: ResolvedChromeElement = {\n ...eyebrow,\n x: eyebrow.x + dotR * 2 + dotGap,\n };\n renderChromeElement(g, shifted, 'oc-eyebrow', 'eyebrow', measureText, true);\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\n const bottomOffset = layout.chrome.bottomAnchorY ?? layout.area.y + layout.area.height;\n\n // Footnotes from auto-thinned annotations, rendered above source/byline.\n // Each footnote gets its own line to avoid horizontal overflow.\n const bandHeight = footnoteBandHeight(layout);\n if (chrome.footnotes && chrome.footnotes.length > 0) {\n const fontSize = layout.theme.fonts.sizes.small;\n const pad = layout.theme.spacing.padding;\n const lineHeight = fontSize * FOOTNOTE_LINE_HEIGHT;\n const style = {\n fontFamily: layout.theme.fonts.family,\n fontSize,\n fontWeight: layout.theme.fonts.weights.normal,\n fill: layout.theme.chrome.source.color,\n lineHeight: FOOTNOTE_LINE_HEIGHT,\n textAnchor: 'start' as const,\n };\n\n for (let i = 0; i < chrome.footnotes.length; i++) {\n const f = chrome.footnotes[i];\n const y =\n bottomOffset + layout.theme.spacing.chartToFooter + textAscent(fontSize) + i * lineHeight;\n const el = createSVGElement('text');\n el.setAttribute('class', 'oc-footnotes');\n setAttrs(el, { x: pad, y });\n applyTextStyle(el, style);\n el.textContent = `${f.index}. ${f.text}`;\n g.appendChild(el);\n }\n }\n\n if (chrome.source) {\n renderChromeElement(\n g,\n { ...chrome.source, y: bottomOffset + chrome.source.y + bandHeight },\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 + bandHeight },\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 + bandHeight },\n 'oc-footer',\n 'footer',\n measureText,\n );\n }\n if (chrome.brand) {\n const brandY = bottomOffset + chrome.brand.y + bandHeight;\n renderChromeElement(g, { ...chrome.brand, y: brandY }, 'oc-brand', 'brand', measureText);\n }\n\n parent.appendChild(g);\n}\n","/**\n * Endpoint-labels rendering: per-series label columns for multi-series\n * line/area charts. Supports trailing (right) and optional leading (left)\n * columns when `ends: 'both'` is set.\n *\n * The engine resolves all positions, colors, and styles. This renderer is dumb:\n * it reads `layout.endpointLabels` and stamps SVG. Suppression logic lives in\n * the engine — when entries is empty, the renderer is a no-op.\n */\n\nimport type {\n ChartLayout,\n EndpointLabelEntry,\n EndpointLabelsLayout,\n Rect,\n} from '@opendata-ai/openchart-core';\nimport { applyTextStyle, createSVGElement, setAttrs } from './svg-dom';\n\nconst LEADER_STROKE_WIDTH = 1;\nconst LEADER_OPACITY = 0.45;\n\nfunction renderColumn(\n root: SVGElement,\n entries: EndpointLabelEntry[],\n bounds: Rect,\n ep: EndpointLabelsLayout,\n leaderAnchorX: number,\n side: 'trailing' | 'leading',\n): void {\n const labelFontSize = ep.labelStyle.fontSize ?? 11;\n const labelLineHeight = labelFontSize * (ep.labelStyle.lineHeight ?? 1.25);\n const valueFontSize = ep.valueStyle.fontSize ?? 11;\n\n const chipX = bounds.x;\n const chipWidth = ep.swatchSize;\n const textX = chipX + chipWidth + ep.gap;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n\n const entryG = createSVGElement('g');\n entryG.setAttribute('class', 'oc-endpoint-label-entry');\n entryG.setAttribute('role', 'listitem');\n entryG.setAttribute('data-endpoint-index', String(i));\n entryG.setAttribute('data-endpoint-key', entry.seriesKey);\n entryG.setAttribute('data-endpoint-side', side);\n entryG.setAttribute('aria-label', `${entry.seriesKey}: ${entry.value}`);\n\n if (entry.showLeader) {\n const leader = createSVGElement('line');\n leader.setAttribute('class', 'oc-endpoint-leader');\n setAttrs(leader, {\n x1: chipX,\n y1: entry.labelY + labelFontSize / 2,\n x2: leaderAnchorX,\n y2: entry.dataY,\n stroke: entry.color,\n 'stroke-width': LEADER_STROKE_WIDTH,\n 'stroke-opacity': LEADER_OPACITY,\n });\n entryG.appendChild(leader);\n }\n\n const rowY = entry.labelY + labelFontSize / 2;\n const line = createSVGElement('line');\n line.setAttribute('class', 'oc-endpoint-swatch-line');\n setAttrs(line, {\n x1: chipX,\n y1: rowY,\n x2: chipX + chipWidth,\n y2: rowY,\n stroke: entry.color,\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n });\n entryG.appendChild(line);\n\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-endpoint-label');\n setAttrs(label, { x: textX, y: entry.labelY + labelFontSize });\n applyTextStyle(label, ep.labelStyle);\n (label as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', entry.color);\n\n if (entry.labelLines.length <= 1) {\n label.textContent = entry.labelLines[0] ?? entry.seriesKey;\n } else {\n for (let li = 0; li < entry.labelLines.length; li++) {\n const tspan = createSVGElement('tspan');\n setAttrs(tspan, { x: textX, dy: li === 0 ? 0 : labelLineHeight });\n tspan.textContent = entry.labelLines[li];\n label.appendChild(tspan);\n }\n }\n entryG.appendChild(label);\n\n const lineCount = Math.max(entry.labelLines.length, 1);\n const valueY =\n entry.labelY +\n labelFontSize +\n (lineCount - 1) * labelLineHeight +\n ep.valueGap +\n valueFontSize;\n const value = createSVGElement('text');\n value.setAttribute('class', 'oc-endpoint-value');\n setAttrs(value, { x: textX, y: valueY });\n applyTextStyle(value, ep.valueStyle);\n value.textContent = entry.value;\n entryG.appendChild(value);\n\n if (entry.marker) {\n const marker = createSVGElement('circle');\n marker.setAttribute('class', 'oc-endpoint-marker');\n setAttrs(marker, {\n cx: entry.marker.x,\n cy: entry.marker.y,\n r: entry.marker.radius,\n fill: entry.marker.fill,\n stroke: entry.marker.stroke,\n 'stroke-width': entry.marker.strokeWidth,\n });\n entryG.appendChild(marker);\n }\n\n root.appendChild(entryG);\n }\n}\n\nexport function renderEndpointLabels(parent: SVGElement, layout: ChartLayout): void {\n const ep = layout.endpointLabels;\n if (!ep || ep.entries.length === 0) return;\n\n const chartArea = layout.area;\n\n const root = createSVGElement('g');\n root.setAttribute('class', 'oc-endpoint-labels');\n root.setAttribute('role', 'list');\n root.setAttribute('aria-label', 'Endpoint labels');\n\n // Trailing column (right side).\n const chartRightX = chartArea.x + chartArea.width;\n renderColumn(root, ep.entries, ep.bounds, ep, chartRightX, 'trailing');\n\n // Leading column (left side) when `ends: 'both'`.\n if (ep.leading && ep.leading.length > 0 && ep.leadingBounds) {\n const chartLeftX = chartArea.x;\n renderColumn(root, ep.leading, ep.leadingBounds, ep, chartLeftX, 'leading');\n }\n\n parent.appendChild(root);\n}\n","/**\n * Legend rendering: swatches + labels with wrap/overflow handling, plus the\n * continuous variant (gradient bar / binned swatch row) for quantitative\n * color scales.\n */\n\nimport type {\n CategoricalLegendLayout,\n ContinuousLegendLayout,\n LegendLayout,\n SizeLegendLayout,\n TextStyle,\n} from '@opendata-ai/openchart-core';\nimport { estimateTextWidth } from '@opendata-ai/openchart-core';\nimport { nextSvgId } from '../svg-ids';\nimport { applyTextStyle, createSVGElement, setAttrs } from './svg-dom';\n\n/** Minimum legend hit-target height in px (WCAG 2.2 SC 2.5.8). */\nconst LEGEND_HIT_HEIGHT = 24;\n/** Horizontal slack on each side of the legend hit rect. */\nconst LEGEND_HIT_PAD = 4;\n\nfunction isCategorical(legend: LegendLayout): legend is CategoricalLegendLayout {\n return !legend.type || legend.type === 'categorical';\n}\n\n/**\n * Detached \"no data\" swatch + label, set only by maps with unjoined features.\n * Not an entry: it carries no toggle role and no `data-legend-index`.\n *\n * The categorical legend passes a chip (rounded corners, label on the slot's\n * centerline) so the swatch matches its neighbouring entries; the continuous\n * legend keeps the crisp full-height square that matches the color bar.\n */\nfunction renderNoDataSwatch(\n parent: SVGElement,\n noData: NonNullable<CategoricalLegendLayout['noData']>,\n labelStyle: TextStyle,\n chip?: { rx: number; baseline: string },\n): void {\n const swatch = createSVGElement('rect');\n swatch.setAttribute('class', 'oc-legend-nodata');\n setAttrs(swatch, {\n x: noData.x,\n y: noData.y,\n width: noData.size,\n height: noData.size,\n fill: noData.fill,\n ...(chip ? { rx: chip.rx, ry: chip.rx } : { 'shape-rendering': 'crispEdges' }),\n });\n parent.appendChild(swatch);\n\n const label = createSVGElement('text');\n setAttrs(label, {\n x: noData.labelX,\n y: noData.labelY,\n 'text-anchor': 'start',\n ...(chip ? { 'dominant-baseline': chip.baseline } : {}),\n });\n applyTextStyle(label, labelStyle);\n label.textContent = noData.label;\n parent.appendChild(label);\n}\n\n/**\n * Render a continuous color legend: a gradient-filled bar (gradient mode) or\n * a contiguous swatch row (binned mode) with value labels below.\n *\n * The gradient ID comes from the shared `nextSvgId` counter like every other\n * gradient/clip-path def, so IDs stay deterministic at generation time.\n */\nfunction renderContinuousLegend(parent: SVGElement, legend: ContinuousLegendLayout): void {\n if (legend.bounds.width <= 0 || legend.bounds.height <= 0) return;\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-legend oc-legend--continuous');\n g.setAttribute('role', 'img');\n const first = legend.ticks[0];\n const last = legend.ticks[legend.ticks.length - 1];\n if (first && last) {\n g.setAttribute('aria-label', `Color scale from ${first.label} to ${last.label}`);\n } else {\n g.setAttribute('aria-label', 'Color scale');\n }\n\n if (legend.mode === 'gradient') {\n // linearGradient def + one rect. Defs may already exist (clip paths).\n let defs: SVGElement | null = parent.querySelector('defs');\n if (!defs) {\n defs = createSVGElement('defs');\n parent.insertBefore(defs, parent.firstChild);\n }\n const gradientId = nextSvgId('oc-legend-gradient');\n const grad = createSVGElement('linearGradient');\n grad.setAttribute('id', gradientId);\n setAttrs(grad, { x1: '0%', y1: '0%', x2: '100%', y2: '0%' });\n for (const stop of legend.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) attrs['stop-opacity'] = stop.opacity;\n setAttrs(s, attrs);\n grad.appendChild(s);\n }\n defs.appendChild(grad);\n\n const bar = createSVGElement('rect');\n bar.setAttribute('class', 'oc-legend-gradient-bar');\n setAttrs(bar, {\n x: legend.bar.x,\n y: legend.bar.y,\n width: legend.bar.width,\n height: legend.bar.height,\n rx: 2,\n ry: 2,\n fill: `url(#${gradientId})`,\n });\n g.appendChild(bar);\n } else {\n // Binned: contiguous class swatches, square-cornered (Datawrapper-style).\n // `crispEdges` keeps the seams between classes on whole pixels; at 12px\n // tall an antialiased seam reads as a sixth, paler class.\n for (let i = 0; i < legend.bins.length; i++) {\n const bin = legend.bins[i];\n const rect = createSVGElement('rect');\n rect.setAttribute('class', 'oc-legend-bin');\n setAttrs(rect, {\n x: bin.x,\n y: legend.bar.y,\n width: bin.width,\n height: legend.bar.height,\n fill: bin.color,\n 'shape-rendering': 'crispEdges',\n });\n rect.setAttribute('data-bin-index', String(i));\n g.appendChild(rect);\n }\n }\n\n if (legend.title && legend.titleY !== undefined) {\n const title = createSVGElement('text');\n title.setAttribute('class', 'oc-legend-title');\n setAttrs(title, { x: legend.bar.x, y: legend.titleY, 'text-anchor': 'start' });\n applyTextStyle(title, legend.titleStyle ?? legend.labelStyle);\n title.textContent = legend.title;\n g.appendChild(title);\n }\n\n for (const tick of legend.ticks) {\n const label = createSVGElement('text');\n setAttrs(label, { x: tick.x, y: legend.labelY, 'text-anchor': tick.anchor });\n applyTextStyle(label, legend.labelStyle);\n label.textContent = tick.label;\n g.appendChild(label);\n }\n\n if (legend.noData) {\n renderNoDataSwatch(g, legend.noData, legend.labelStyle);\n }\n\n parent.appendChild(g);\n}\n\n/**\n * Render a size legend: graduated circles, nested (concentric, sharing a bottom\n * edge) so the ratio between magnitudes reads, not just their order.\n *\n * Emits **no** `data-legend-index`. That attribute is what\n * `wireLegendInteraction` sweeps up to wire click-to-toggle-series, and a size\n * circle is not a series -- clicking \"500M\" must not try to hide a series named\n * \"500M\". The circles are inert, and `aria-hidden` keeps them out of the AT tree\n * as decorative chrome (the values are already in each mark's aria label).\n */\nfunction renderSizeLegend(parent: SVGElement, legend: SizeLegendLayout): void {\n if (legend.circles.length === 0) return;\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-legend oc-legend--size');\n // Decorative, not announced. `role=\"img\"` + a bare \"Size legend\" label would\n // tell a screen-reader user a key exists while conveying none of the values it\n // keys -- pure noise. The magnitudes are already in each mark's own aria label,\n // which is where a non-sighted reader actually gets them.\n g.setAttribute('aria-hidden', 'true');\n\n // Absolute coordinates, NOT a group `transform`. `getBBox()` reports a group's\n // box in its own local space and ignores the group's transform, so a\n // translated legend measures as if it sat at the origin -- which is exactly\n // how a size/color legend collision slipped past the rendered-invariant\n // overlap checks. Every other legend bakes absolute coords into its children;\n // this one does too, so the same measurement sees it where it really is.\n const ox = legend.bounds.x;\n const oy = legend.bounds.y;\n const labelX = ox + legend.circles[0].cx + legend.circles[0].radius + 8;\n const leaderX = ox + legend.circles[0].cx + legend.circles[0].radius + 4;\n\n if (legend.title && legend.titleY !== undefined) {\n const title = createSVGElement('text');\n title.setAttribute('class', 'oc-legend-title');\n setAttrs(title, { x: ox, y: oy + legend.titleY, 'text-anchor': 'start' });\n applyTextStyle(title, legend.titleStyle ?? legend.labelStyle);\n title.textContent = legend.title;\n g.appendChild(title);\n }\n\n for (const circle of legend.circles) {\n const c = createSVGElement('circle');\n setAttrs(c, {\n cx: ox + circle.cx,\n cy: oy + circle.cy,\n r: circle.radius,\n fill: 'none',\n stroke: legend.stroke,\n 'stroke-width': 1,\n });\n g.appendChild(c);\n\n // Leader line from the circle's top edge out to its label.\n const line = createSVGElement('line');\n setAttrs(line, {\n x1: ox + circle.cx,\n y1: oy + circle.cy - circle.radius,\n x2: leaderX,\n y2: oy + circle.cy - circle.radius,\n stroke: legend.stroke,\n 'stroke-width': 1,\n 'stroke-dasharray': '2 2',\n });\n g.appendChild(line);\n\n const label = createSVGElement('text');\n setAttrs(label, {\n x: labelX,\n y: oy + circle.labelY,\n });\n applyTextStyle(label, legend.labelStyle);\n label.textContent = circle.label;\n g.appendChild(label);\n }\n\n parent.appendChild(g);\n}\n\nexport function renderLegend(parent: SVGElement, legend: LegendLayout): void {\n if (legend.type === 'continuous') {\n renderContinuousLegend(parent, legend);\n return;\n }\n if (legend.type === 'size') {\n renderSizeLegend(parent, legend);\n return;\n }\n if (!isCategorical(legend) || legend.entries.length === 0) return;\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-legend');\n // A group of toggle buttons, not a list: each entry is an interactive\n // control with aria-pressed, and role=\"listitem\" inside role=\"list\" would\n // announce them as static content.\n g.setAttribute('role', 'group');\n g.setAttribute('aria-label', 'Chart legend');\n\n const isHorizontal =\n legend.position === 'top' || legend.position === 'top-left' || legend.position === 'bottom';\n const positions = 'entryPositions' in legend ? legend.entryPositions : undefined;\n let offsetX = legend.bounds.x;\n let offsetY = legend.bounds.y;\n\n for (let i = 0; i < legend.entries.length; i++) {\n const entry = legend.entries[i];\n\n const pos = positions?.[i];\n if (pos) {\n offsetX = pos.x;\n offsetY = pos.y;\n } else if (isHorizontal && i > 0) {\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;\n if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {\n offsetX = legend.bounds.x;\n offsetY += legend.swatchSize + 6;\n }\n }\n const entryG = createSVGElement('g');\n entryG.setAttribute('class', 'oc-legend-entry');\n entryG.setAttribute('data-legend-index', String(i));\n entryG.setAttribute('data-legend-label', entry.label);\n if (entry.overflow) {\n entryG.setAttribute('data-legend-overflow', 'true');\n entryG.setAttribute('aria-label', entry.label);\n entryG.setAttribute('opacity', '0.5');\n } else {\n const visible = entry.active !== false;\n entryG.setAttribute('role', 'button');\n entryG.setAttribute('tabindex', '0');\n entryG.setAttribute('aria-pressed', String(visible));\n // Read by the hover-emphasis controller, which must not touch a\n // toggled-off entry (it already carries opacity 0.3 as an attribute).\n entryG.setAttribute('data-legend-active', String(visible));\n entryG.setAttribute('aria-label', `${entry.label}: ${visible ? 'visible' : 'hidden'}`);\n entryG.setAttribute('style', 'cursor: pointer');\n\n // Apply dimming for inactive entries\n if (!visible) {\n entryG.setAttribute('opacity', '0.3');\n }\n\n // Transparent hit + focus target, at least 24px tall (WCAG 2.2 SC 2.5.8).\n // First child so it paints behind the swatch and label.\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const hit = createSVGElement('rect');\n hit.setAttribute('class', 'oc-legend-hit');\n setAttrs(hit, {\n x: offsetX - LEGEND_HIT_PAD,\n y: offsetY + legend.swatchSize / 2 - LEGEND_HIT_HEIGHT / 2,\n width: legend.swatchSize + legend.swatchGap + labelWidth + LEGEND_HIT_PAD * 2,\n height: LEGEND_HIT_HEIGHT,\n rx: 2,\n fill: 'transparent',\n });\n entryG.appendChild(hit);\n }\n\n // Swatch: bare colored mark matching the chart type.\n // Circle for point/scatter, line segment for line/area, filled rect for bar.\n const midX = offsetX + legend.swatchSize / 2;\n const midY = offsetY + legend.swatchSize / 2;\n if (entry.shape === 'circle') {\n const circle = createSVGElement('circle');\n setAttrs(circle, {\n cx: midX,\n cy: midY,\n r: legend.swatchSize / 2,\n fill: entry.color,\n });\n entryG.appendChild(circle);\n } else if (entry.shape === 'line') {\n const lineWidth = legend.swatchSize;\n const line = createSVGElement('line');\n line.setAttribute('class', 'oc-legend-swatch-line');\n setAttrs(line, {\n x1: offsetX,\n y1: midY,\n x2: offsetX + lineWidth,\n y2: midY,\n stroke: entry.color,\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n });\n entryG.appendChild(line);\n } else {\n const rectSize = Math.round(legend.swatchSize * 0.6);\n const rect = createSVGElement('rect');\n rect.setAttribute('class', 'oc-legend-swatch-rect');\n setAttrs(rect, {\n x: offsetX + (legend.swatchSize - rectSize) / 2,\n y: midY - rectSize / 2,\n width: rectSize,\n height: rectSize,\n rx: 2,\n ry: 2,\n fill: entry.color,\n });\n entryG.appendChild(rect);\n }\n\n // Label\n const label = createSVGElement('text');\n setAttrs(label, {\n x: offsetX + legend.swatchSize + legend.swatchGap,\n y: offsetY + legend.swatchSize / 2,\n 'dominant-baseline': 'central',\n });\n applyTextStyle(label, legend.labelStyle);\n label.textContent = entry.label;\n entryG.appendChild(label);\n\n g.appendChild(entryG);\n\n if (!pos) {\n if (isHorizontal) {\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;\n offsetX += entryWidth;\n } else {\n offsetY +=\n 'rowHeight' in legend && legend.rowHeight\n ? legend.rowHeight\n : legend.swatchSize + legend.entryGap;\n }\n }\n }\n\n if (legend.noData) {\n renderNoDataSwatch(g, legend.noData, legend.labelStyle, { rx: 2, baseline: 'central' });\n }\n\n parent.appendChild(g);\n}\n","/**\n * KPI metric bar rendering. Emits a `<g class=\"oc-metrics\">` group containing\n * a label/value pair per cell. Visual styling lives in `chrome.css`\n * (`.oc-metric-*` classes); this renderer only sets positions and structure.\n *\n * Fills come from the CSS classes (via `--oc-*` variables), not inline attrs.\n * That works on-screen and, for exports, `injectThemeStyleBlock` inlines those\n * class rules into the export clone so they survive the detach from the page\n * stylesheet — the same mechanism that carries every other class-based chrome\n * element (eyebrow dot, legend text, endpoint labels). See `theme-style-block.ts`.\n */\n\nimport type { ChartLayout } from '@opendata-ai/openchart-core';\nimport { createSVGElement, setAttrs } from './svg-dom';\n\n// Delta and secondary text run at a fraction of the primary value size. This\n// preserves the editorial mock's proportion (12px delta against a 22px value)\n// at any theme-driven value size, so scaling metricValue scales the whole cell.\nconst DELTA_SIZE_RATIO = 12 / 22;\n\nexport function renderMetrics(parent: SVGElement, layout: ChartLayout): void {\n const bar = layout.metrics;\n if (!bar || bar.cells.length === 0) return;\n\n // Font sizes flow from the theme. Set them inline so they override the CSS\n // defaults (.oc-metric-* in chrome.css) when a chart customizes the sizes.\n const labelSize = layout.theme.fonts.sizes.metricLabel;\n const valueSize = layout.theme.fonts.sizes.metricValue;\n const deltaSize = Math.round(valueSize * DELTA_SIZE_RATIO);\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-metrics');\n\n for (const cell of bar.cells) {\n const label = createSVGElement('text');\n label.setAttribute('class', 'oc-metric-label');\n setAttrs(label, { x: cell.x, y: cell.labelY, 'font-size': labelSize });\n label.textContent = cell.metric.label.toUpperCase();\n g.appendChild(label);\n\n const value = createSVGElement('text');\n value.setAttribute('class', 'oc-metric-value');\n setAttrs(value, { x: cell.x, y: cell.valueY, 'font-size': valueSize });\n value.textContent = cell.metric.value;\n\n if (cell.metric.delta) {\n const delta = createSVGElement('tspan');\n const tone = cell.metric.deltaTone ?? 'up';\n delta.setAttribute('class', tone === 'down' ? 'oc-metric-delta-down' : 'oc-metric-delta-up');\n delta.setAttribute('dx', '8');\n delta.setAttribute('font-size', String(deltaSize));\n delta.textContent = cell.metric.delta;\n value.appendChild(delta);\n }\n\n if (cell.metric.secondary) {\n const secondary = createSVGElement('tspan');\n secondary.setAttribute('class', 'oc-metric-secondary');\n secondary.setAttribute('dx', '6');\n secondary.setAttribute('font-size', String(deltaSize));\n secondary.textContent = cell.metric.secondary;\n value.appendChild(secondary);\n }\n\n g.appendChild(value);\n }\n\n parent.appendChild(g);\n}\n"],"mappings":"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","names":["SVG_NS","SVG_NS","textAscent","textAscent","textAscent","text","textAscent","estimateTextWidth","estimateTextWidth"]}