Architecture
ChartGPU is built around four composable primitives: Chart, Panel, Indicator, and DrawingTool.
Overview
Chart
├── gpuCanvas ← WebGPU renders candlestick bodies & wicks
├── overlayCanvas ← Canvas 2D renders everything else
│
├── Panel[]
│ ├── CandlePanel (heightRatio: 0.55)
│ │ ├── Indicator: SMAIndicator
│ │ ├── Indicator: EMAIndicator
│ │ └── Indicator: BollingerBandsIndicator
│ ├── VolumePanel (heightRatio: 0.10)
│ ├── RSIPanel (heightRatio: 0.17)
│ └── MACDPanel (heightRatio: 0.17)
│
└── DrawingTool[]
├── TrendLine
└── HorizontalRayTwo-Canvas Rendering
The chart mounts two stacked canvases inside the container:
- gpuCanvas — WebGPU context, renders candle bodies and wicks as GPU geometry.
pointer-events: noneso it doesn't intercept mouse events. - overlayCanvas — Canvas 2D context, renders axes, grid lines, indicators, crosshair, drawing tools, and panel labels. Receives all pointer events.
This separation means indicators and UI remain fast on the CPU while the heavy geometry (thousands of candles) stays on the GPU.
Panels
A Panel is a horizontal section of the chart. Panels stack vertically and divide available height by their heightRatio.
import { Panel } from 'chartgpu';
class MyPanel extends Panel {
heightRatio = 0.2;
draw(state: DrawState): void {
const { ctx, candles, viewport, section } = state;
// Draw into section.top → section.top + section.height
}
}Each panel receives a DrawState with everything it needs: the 2D context, full candle array, current viewport, its pixel section, and an optional priceRange (set by CandlePanel for overlays).
Indicators
An Indicator is a drawing layer inside a Panel. They receive the same DrawState as the panel, plus priceRange injected by CandlePanel.
import { Indicator } from 'chartgpu';
class MyIndicator extends Indicator {
draw(state: DrawState): void {
if (!state.priceRange) return;
// Draw a line, histogram, etc.
}
}
candle.addIndicator(new MyIndicator());Toggle visibility with indicator.visible = false.
Drawing Tools
A DrawingTool handles pointer events and renders an overlay on the CandlePanel section.
import { DrawingTool } from 'chartgpu';
class MyTool extends DrawingTool {
get cursor() { return 'crosshair'; }
onPointerDown(e: PointerEvent, state: DrawState): boolean {
// return true to consume the event and suppress pan
return false;
}
onPointerMove(e: PointerEvent, state: DrawState): boolean { return false; }
onPointerUp(e: PointerEvent, state: DrawState): boolean { return false; }
onDblClick(e: MouseEvent, state: DrawState): boolean { return false; }
draw(state: DrawState): void {
// Render when chart redraws
}
}Activate with chart.activateTool(tool) and deactivate with chart.activateTool(null).
Viewport
The viewport defines which slice of the candle array is visible:
interface ViewPort {
start: number; // fractional index of left edge
end: number; // fractional index of right edge
}Pan and zoom change start/end. The renderer maps candle indices to pixel coordinates based on these values.