Skip to content

Stage 1: Render Engine & Performance

Target versions: v1.16.0 → v1.19.0 (possible v2.0.0 if breaking render API)
Prerequisite: Stage 0 exit checklist
Parallel with: Stage 2/3 planning can start, but perf benchmarks should land first


Goal

Make performance claims measurable and reproducible. Extend virtualization to all high-volume series types, resolve the WebGPU story, and offload heavy indicator computation to Web Workers.

Target: maintain 60 FPS with 1M+ OHLC bars and 10M+ line points on mid-range hardware.


Current state

Rendering architecture

requestRender() → rAF coalesce → performRender()
  ├── WebGL: lines, bars, candlesticks, heatmap, bands
  └── Canvas 2D overlay: axes, grid, crosshair, legend, gauge, sankey, polar

Key files:

  • src/core/chart/ChartRenderLoop.ts — frame scheduling, flushRender() for resize
  • src/renderer/native/NativeWebGLRenderer.ts — zoom via uniforms, buffer reuse
  • src/core/chart/ChartRenderer.ts — 2D overlay rendering
  • src/core/chart/ChartSetup.ts — dual-canvas DOM setup, DPR handling

Performance features (existing)

FeatureLocationScope
LTTB / min-max downsamplingsrc/workers/downsample.tsLine data
PluginVirtualizationsrc/plugins/virtualization/line, scatter, step, bar, candlestick
PluginOffscreensrc/plugins/offscreen/Worker + OffscreenCanvas
PluginCachingsrc/plugins/caching/Buffer cache with auto-invalidate on data update
PluginLazyLoadsrc/plugins/lazy-load/Viewport chunks + distance unload + setDataWindow
GpuComputesrc/gpu/compute/WebGPU stats (experimental)
WebGPURenderersrc/renderer/WebGPURenderer.tsExperimental, not default
ChartInitQueuesrc/core/ChartInitQueue.tsSerializes multi-chart init
Backpressuresrc/streaming/backpressure.tsCircularBuffer for streams

Gaps

  • Virtualization does not cover candlestick or barresolved v1.16
  • Indicator calculations run on main thread — async worker pool available
  • PluginLazyLoad / PluginCaching partial — complete (distance unload + auto-invalidate)
  • Virtualization re-downsamples on every pan without viewport slice — resolved v1.16 (viewport slice + rAF debounce + worker pool)
  • Axes and grid render on Canvas 2D — spike evaluated; defer WebGL grid (Spike 001)
  • WebGPU renderer: "webgpu" opt-in via GpuChartRenderer — see ADR 001
  • Browser FPS benchmarks require local benchmarkRender() — CI runs CPU downsampling benchmarks
  • Legacy roadmap claimed "10M points @ 60 FPS" without automated verification — partial CI coverage added

Work items

P0 — WebGPU decision

IDTaskPriorityComplexityDefinition of done
1.1WebGPU strategy documentP0LowADR: ship WebGPU as opt-in, or remove flag until ready
1.2If shipping: complete WebGPU path for line + candlestickP0Very Highrenderer: "webgpu" renders without warning; fallback documented
1.3If deferring: remove misleading webgpu option from public APIP0LowNo silent fallback; docs state WebGL2 only

P0 — Virtualization expansion

IDTaskPriorityComplexityDefinition of done
1.4LOD for candlestick seriesP0HighPluginVirtualization handles OHLC; visible bars ≤ budget; 60 FPS with 500k bars in benchmark
1.5LOD for bar / histogram seriesP0HighSame as 1.4 for bar type
1.6Viewport-aware data windowing APIP1Mediumchart.setDataWindow({ from, to }) loads only visible range + buffer

P0 — Worker offload

IDTaskPriorityComplexityDefinition of done
1.7Indicator worker poolP0Highrsi(), macd(), bollingerBands() callable via worker; main thread non-blocking for 100k+ points
1.8Reuse downsample.worker.ts patternP1LowShared worker pool infrastructure in src/workers/

P1 — Render pipeline improvements

IDTaskPriorityComplexityDefinition of done
1.9Complete PluginCaching auto-invalidateP1Mediumsrc/plugins/caching/index.ts:388 TODO resolved
1.10Complete PluginLazyLoad distance unloadingP1Mediumsrc/plugins/lazy-load/index.ts:231 TODO resolved
1.11Evaluate WebGL axis/grid renderingP2Very High✅ Spike doc + benchmark; defer (grid <5% frame time)
1.12Batch overlay redraws during stacked resizeP1Medium✅ Verified — setResizeSuspended batching in createStackedChart

P0 — Benchmarks

IDTaskPriorityComplexityDefinition of done
1.13Benchmark suite in src/testing/P0MediumScripts: 1M line, 500k candlestick, 5-pane stack resize
1.14CI benchmark job (non-blocking initially)P1LowGitHub Actions uploads results as artifact; alert on >20% regression
1.15Document performance limitsP0Lowlarge-datasets guide updated with real numbers

Performance targets (v1.19.0)

ScenarioTargetMeasurement
1M line points, pan/zoom≥55 FPS avgBenchmark script
500k candlestick bars, pan/zoom≥50 FPS avgBenchmark script
5-pane stacked chart, divider drag≥55 FPS avgBenchmark script
First paint (empty chart)<100msLighthouse or custom
Indicator calc (RSI 14, 100k bars)<200msWorker path
Memory (1M points, 3 series)<300MBChrome heap snapshot

Risks

RiskMitigation
WebGL candlestick LOD changes visual fidelityDocument aggregation rules; allow precision: 'full' opt-out
WebGPU browser support fragmentedFeature-detect; WebGL always available
Worker serialization overheadTransferable ArrayBuffers; benchmark before shipping
Breaking render options at v2.0Deprecation period in v1.18–v1.19

Exit checklist (v1.19.0)

  • [x] WebGPU strategy executed (ship or remove flag) — ADR 001: defer; explicit warning
  • [x] Candlestick + bar virtualization shipped — PluginVirtualization + ohlcMinMaxDownsample
  • [x] Indicator worker pool for top 5 indicators (RSI, MACD, EMA, SMA, Bollinger) — src/workers/indicatorsAsync.ts
  • [x] Benchmark suite runs locally and in CI — src/testing/stage1-perf.test.ts + CI benchmark job
  • [x] Performance guide updated with verified numbers — large-datasets guide
  • [x] PluginCaching and PluginLazyLoad TODOs closed — onDataUpdate + distance unload + setDataWindow
  • [x] No FPS regression >10% vs v1.15 baseline — src/testing/baselines/v1.15.0.json + browser CI + compare-benchmark-regression.mjs
  • [x] Vitest coverage ≥25% lines — Stage 1 modules in vitest.config.ts

Released under the MIT License.