Skip to content

Stage 6: SVG Vector Parity (Full v3 Homolog)

Target versions: v3.1.0 → v4.0.0
Prerequisite: Stage 5 exit checklist — v3.0.0 canvas/WebGL feature set is frozen and documented
Parallel with: Maintenance releases on v3.x; does not block v3.0.0 ship
Theme: Every user-visible chart capability in v3 must have a vector SVG homolog — no feature left as raster-only or silently omitted from export


Goal

Build a complete SVG export layer that mirrors the interactive v3 chart engine. If a user can see it on screen (series, axes, overlays, annotations, indicators, stack layout, trading drawings, scientific overlays), they must be able to export it as true vector SVG — not a PNG embedded in an <image> tag.

Parity rule: For each row in the Parity matrix, status must reach ✅ SVG homolog before Stage 6 exits. Partial raster fallbacks are allowed only during development behind an explicit svgExportMode: 'legacy-raster' flag, never as the default in v4.

mermaid
flowchart TB
  subgraph v3_runtime [v3 Runtime — Canvas + WebGL]
    GL[WebGL series renderers]
    OL[OverlayRenderer — axes, grid, legend]
    PL[Plugin overlays — annotations, LaTeX, drawings]
  end
  subgraph svg_layer [Stage 6 — SVG homolog]
    SG[SVGSceneGraph / SVGExporter v2]
    SL[Shared layout + theme tokens]
    SS[StackSVGComposer]
  end
  v3_runtime --> SL
  SL --> SG
  SL --> SS
  SG --> OUT1["chart.exportSVG()"]
  SG --> OUT2["chart.snapshot({ format: 'svg' })"]
  SS --> OUT3["stack.exportSVG()"]

Current state (v1.13 / pre–Stage 6)

What SVG export does today

AreaStatusLocation
Line, step, scatter (circle), bar, area/band, candlestick⚠️ Partialsrc/core/chart/exporter/SVGExporter.ts
Major grid (simplified)⚠️ PartialdrawSVGGrid — no minor grid, fixed 10 ticks
X/Y axis lines + tick labels (primary Y only)⚠️ PartialdrawSVGAxes, drawSVGTickLabels
chart.exportSVG()✅ Sync APIChartCore.ts
PluginSnapshot format svg✅ Delegates to exportSVG()src/plugins/snapshot/index.ts
Stack composite export❌ Raster onlystackExport.ts — PNG/JPEG/WebP
Visual regression tests for SVG⚠️ MinimalSVGExporter.test.ts — 3 smoke tests

What is not in SVG today (but exists in runtime)

GapRuntime location
Heatmap, polar, radar, gauge, sankey, ternary, waterfall, boxplotDedicated renderers / overlay paths
Indicator composite (histogram, colorZones, fills, markers)buildIndicatorSeries.ts
Error barsOverlayRenderer.drawErrorBars
Legend, chart title, plot borderOverlayRenderer
Multi Y-axis (left + right)OverlayRenderer.drawYAxis × N
Scatter symbols (square, diamond, star, …)WebGL + legend symbols
LaTeX axis/legend/annotation labelsPluginLaTeX
Annotations (lines, shapes, text, arrows)PluginAnnotations
Polar gridOverlayRenderer.drawPolarGrid
Cursor / crosshair / tooltipsIntentionally interactive — export policy TBD
Stacked multi-pane single SVGNot implemented
Horizontal + vertical stack layout in one SVGNot implemented
Broken axis gapsPluginBrokenAxis
Regression / forecast / ML overlay bandsAnalysis plugins
Drawing tools (Stage 2)PluginDrawingTools (planned)
Trade markers on candlesStage 2
3D seriesWebGL-only — see 3D SVG policy

Parity matrix (v3 → SVG)

Legend: ✅ done · ⚠️ partial · ❌ missing · 🔒 interactive-only (export snapshot optional) · ➖ N/A

Core chart & layout

Featurev3 runtimeSVG (today)Target ID
Plot area + margins⚠️ implicit in plotArea6.1
Theme colors (background, axes, grid)⚠️ partial tokens6.2
Chart title6.3
Plot border6.4
Multi Y-axis (left/right)❌ primary only6.5
Log / time / inverted axes⚠️ labels only if scale matches6.6
Minor grid lines6.7
Broken axisStage 36.8
LaTeX axis titles & ticks✅ PluginLaTeX❌ plain text only6.9
Legend (DOM + overlay)6.10
Responsive / DPR➖ SVG is unitless vector

Series types (SeriesType)

Typev3 runtimeSVG (today)Target ID
line✅ WebGL✅ polyline
scatter✅ WebGL⚠️ circle only6.11
line+scatter6.11
step / step+scatter⚠️ basic step modes6.12
band / area⚠️ polygon, no gradient6.13
bar⚠️ fixed width heuristic6.14
candlestick⚠️ no hollow/wick styling edge cases6.15
heatmap6.16
polar6.17
radar6.18
gauge✅ overlay6.19
sankey✅ overlay6.20
ternary6.21
waterfall6.22
boxplot6.23
indicator (composite)6.24
Error bars (any series)✅ overlay6.25

Scatter symbols

Symbolv3SVG target
circle, square, diamond, triangle, triangleDown, cross, x, star6.11 — reuse OverlayRenderer.drawLegendSymbol paths

Multi-pane stack

Featurev3 runtimeSVG (today)Target ID
Vertical stack (1–5 panes)❌ raster composite only6.30
Horizontal stack✅ v1.13+6.30
Shared X / shared Y axis compaction6.31
Resize dividers in export✅ raster❌ vector divider lines6.32
stack.exportSVG()6.33
WYSIWYG view bounds per pane6.34
Aligned margins across panes6.35

Sync & cursor (export semantics)

Featurev3 runtimeSVG export policyTarget ID
Crosshair at capture time🔒 Optional includeCursor6.40
Selection rectangle🔒 Optional includeSelection6.40
Tooltip DOM➖ omit or optional static snapshot6.41
Synced cursor across stack🔒 single SVG documents all panes at capture instant6.42

Plugins & overlays

Plugin / featurev3SVG (today)Target ID
PluginAnnotations6.50
PluginLaTeX6.51
PluginRegression (fit line, band)6.52
PluginForecasting (forecast + CI band)v3 complete6.53
PluginROI (selection shapes)🔒 optional6.54
PluginBrokenAxis6.55
Analysis markers (peaks, etc.)6.56
Pattern recognition highlights6.57
PluginDrawingTools (Stage 2)planned6.58
Trade markers (Stage 2)planned6.59
Stats panel (DOM)➖ optional foreignObject or omit6.60

Trading (Stage 2 — must homolog when v3 ships trading)

FeatureSVG homolog requiredTarget ID
Business-day time axis gaps✅ tick labels skip non-session6.70
Heikin-Ashi / hollow candles✅ path equivalents6.71
Baseline / % scale series6.72
Drawing tools persistence✅ vector paths in SVG6.58
Replay state at frame N🔒 exportSVG({ at: timestamp })6.73

Scientific (Stage 3)

FeatureSVG homolog requiredTarget ID
LaTeX 300+ commands in labels<text> or embedded paths6.51
Contour / spectrogram slices⚠️ raster tile fallback if no vector isolines6.74
3D projections (waterfall, surface)See 3D policy6.75
ML prediction overlay✅ line + band6.76

Export API surface

APIv3 rasterSVG targetTarget ID
chart.exportSVG(options?)✅ basic✅ full options6.80
chart.snapshot({ format: 'svg' })✅ same output6.80
stack.exportSVG(options?)✅ new6.33
stack.snapshot({ format: 'svg' })✅ alias6.33
Batch export / download helperspartial✅ unified6.81
SVG export options: includeOverlays, includeLegend, includeAnnotations, embedFontspartial✅ documented6.82

Architecture

P0 — Unified SVG pipeline (replace monolith SVGExporter.ts)

IDTaskPriorityComplexityDefinition of done
6.1SVGExportContext shared with layout engineP0HighSame plotArea, margins, scales as ChartRenderLoop snapshot
6.2SVGThemeAdapter — map ChartTheme → SVG attributesP0MediumAll theme tokens used by overlay have SVG equivalent
6.3Split exporters: SVGSeriesExporter, SVGOverlayExporter, SVGAnnotationExporterP0HighModular; one file per domain; unit tested
6.4SVGDocumentBuilder — XML escape, defs, gradients, clipPathsP0MediumSingle place for <defs>, font embedding policy
6.5Parity manifest (svg-parity.json) generated from matrixP1LowCI fails if v3 adds series type not listed in manifest

P0 — Series homolog (complete SeriesType coverage)

IDTaskPriorityComplexityDefinition of done
6.11All scatter symbols as SVG pathsP0MediumPixel-aligned with WebGL at 1x; symbol tests
6.12Step modes before / after / center + step+scatterP0LowMatches SVGExporter step logic + scatter layer
6.13Band/area with opacity + gradient fillsP1MediumTwo-stop linearGradient optional
6.14Bar width from scale domain + barWidth optionP0MediumMatches on-screen bar spacing for time & index axes
6.15Candlestick wicks, bodies, bullish/bearish, hollowP0MediumVisual diff vs WebGL ≤ 1px
6.16Heatmap as <rect> grid or <image> fallback behind flagP0HighDefault vector rects for ≤ 10k cells
6.17–6.23Polar, radar, gauge, sankey, ternary, waterfall, boxplotP0Very HighOne exporter module each; example + test each
6.24Indicator composite exporterP0Very HighHistogram bars, colorZone line segments, fills, markers
6.25Error bars exporterP0MediumPort OverlayRenderer.drawErrorBars logic to SVG

P0 — Overlay homolog

IDTaskPriorityComplexityDefinition of done
6.7Major + minor grid with dash patternsP0LowMatches OverlayRenderer.drawGrid
6.5Multi Y-axis + offsetP0HighLeft/right axes with titles
6.3Chart title + axis titlesP0LowMatches drawChartTitle, axis labels
6.4Plot borderP0LowstrokeRect equivalent
6.10Legend as SVG <g> (no DOM)P0HighPositions match theme legend slots
6.9LaTeX → SVG paths or <foreignObject> policyP1Very HighDocument chosen strategy; axis labels render

P0 — Stack SVG composer

IDTaskPriorityComplexityDefinition of done
6.30StackSVGComposer moduleP0Very HighOne <svg> with nested <g transform="translate()"> per pane
6.31Shared-axis margin compaction in SVGP0HighSame rules as buildPaneChartOptions
6.32Divider lines as vector strokesP1LowMatches stackExport divider positions
6.33stack.exportSVG() + StackSnapshotOptions format 'svg'P0MediumPublic API + types + docs
6.34Per-pane view bounds at capture timeP0MediumWYSIWYG after pan/zoom
6.35Horizontal + vertical layoutP0MediumExtends 6.30; tests from Stage 0 stack tests

P1 — Plugin & trading/scientific overlays

IDTaskPriorityComplexityDefinition of done
6.50PluginAnnotations → SVGP1Highline, rect, band, text, arrow
6.51PluginLaTeX labels in SVGP1Very HighOr documented fallback to outlined paths
6.52–6.53Regression + forecast overlaysP1MediumLines + bands as SVG paths
6.55Broken axis gapsP2HighDiscontinuity markers in axis + clipped series
6.58–6.59Drawing tools + trade markers (when Stage 2 lands)P1HighBlock 6 exit until Stage 2 features exist OR marked N/A in manifest
6.70–6.73Trading-specific SVGP1HighTracks Stage 2 release — no trading feature without SVG row

P1 — Quality, tests, docs

IDTaskPriorityComplexityDefinition of done
6.90SVG parity test suite (one test per matrix row)P0Very High≥ 1 test per ✅ row; coverage ≥ 80% in src/core/chart/exporter/svg/
6.91Visual diff: SVG render vs canvas at 1xP0HighPlaywright or resvg-js; tolerance 1px
6.92Stack SVG golden files (vertical + horizontal)P0Medium3-pane + 2-pane horizontal
6.93Update image-export.md — remove “stack SVG not available”P0LowDocs match implementation
6.94Interactive demo: SVG export all presets in PaneStackDemo + SnapshotDemoP1LowSVG button on stack demo
6.95PLUGIN-STATUS.md SVG columnP1LowPer-plugin SVG support status

P2 — Advanced

IDTaskPriorityComplexityDefinition of done
6.96embedFonts: true for standalone SVGP2HighWOFF subset embedded in <defs>
6.97SVGZ gzip downloadP2LowOptional compression
6.98Accessible SVG (role="img", aria-label)P2MediumTitle/desc elements
6.99Server-side export (Node + resvg)P2HighSame API returns SVG string without DOM

3D SVG policy

3D WebGL content cannot be fully homologged as native SVG paths without a projection step.

ApproachWhenTarget
A. Orthographic projectionSurface, waterfall, ribbonExport as 2D projected paths + z-order (6.75)
B. Raster embedVoxel, point cloud > 100k points<image xlink:href="data:..."> behind svgExport3DMode: 'raster' flag only
C. OmitInteractive-only 3D chromeDocument in matrix as ➖

Default for v4: A for standard 3D chart types listed in docs; B opt-in only.


Implementation phases

mermaid
gantt
  title Stage 6 phases
  dateFormat YYYY-MM
  section Foundation
  SVG pipeline refactor     :2026-08, 2M
  section Core 2D
  Series + overlay homolog    :2026-10, 3M
  section Stack
  StackSVGComposer          :2027-01, 2M
  section Plugins
  Annotations LaTeX plugins :2027-03, 2M
  section Trading Sci
  Stage 2 and 3 overlays    :2027-05, 3M
  section Release
  v4.0.0 SVG complete       :2027-08, 1M
PhaseVersion tagDeliverable
v3.1.0Refactored pipeline; line/scatter/bar/candlestick/axes parity; tests
v3.5.0All SeriesType + error bars + legend + indicators
v3.8.0stack.exportSVG() vertical + horizontal
v4.0.0Full matrix ✅; trading + scientific overlays; docs + demos

Risks

RiskMitigation
SVG scope explodes with every new v3 featuresvg-parity.json manifest; PR template requires matrix update
LaTeX → SVG is hardStart with foreignObject + documented fallback; path trace later
Heatmap / large grids inflate SVG sizeCell budget; auto-switch to raster embed with warning
Duplicate layout logic (canvas vs SVG)Shared LayoutSnapshot from chart state at export time
Stage 2/3 land after 6βMatrix rows stay ❌ until feature ships; 6.58 blocks exit
Performance on 1M-point line exportPath simplification (Douglas-Peucker) at export time, not full fidelity for mega-series
Font mismatch vs canvas6.96 embed fonts; until then document required web fonts

Exit checklist (v4.0.0)

Parity

  • [ ] Every row in Parity matrix is ✅ or explicitly ➖/🔒 with documented policy
  • [ ] No series type in SeriesType exports as empty/group without warning
  • [ ] stack.exportSVG() works for vertical and horizontal stacks (1–5 panes)
  • [ ] PluginSnapshot SVG output identical to chart.exportSVG() for same options

Quality

  • [ ] SVG exporter line coverage ≥ 80%
  • [ ] Visual diff suite passes at 1x DPR for all core chart examples
  • [ ] No public API exports SVG that throws for supported series types

Documentation

  • [ ] image-export.md documents full SVG surface
  • [ ] Each example page notes SVG support status
  • [ ] PLUGIN-STATUS.md includes SVG column
  • [ ] Migration note: v3.x raster-only stack export unchanged; v4 adds stack.exportSVG()

CI

  • [ ] pnpm test:svg-parity job in CI
  • [ ] PR gate: new SeriesType or plugin overlay requires matrix + test update

Key files (planned)

src/core/chart/exporter/
  svg/
    SVGDocumentBuilder.ts
    SVGExportContext.ts
    SVGThemeAdapter.ts
    series/          # one exporter per SeriesType
    overlay/         # grid, axes, legend, title, error bars
    plugins/         # annotations, latex, regression, ...
  stack/
    StackSVGComposer.ts
  SVGExporter.ts     # thin facade → v2 pipeline

Existing files to migrate: SVGExporter.ts, stackExport.ts, OverlayRenderer.ts (shared path data), buildIndicatorSeries.ts.


Released under the MIT License.