https://github.com/ChartGPU/ChartGPU
Beautiful, open source, WebGPU-based charting library
https://github.com/ChartGPU/ChartGPU
candlestick-chart chart chart-library charting charting-library data-analysis data-analytics data-visualization data-visualization-tools data-viz gpu npm npm-package react react-chart-library typescript-library webgl webgpu webgpu-shaders
Last synced: about 14 hours ago
JSON representation
Beautiful, open source, WebGPU-based charting library
- Host: GitHub
- URL: https://github.com/ChartGPU/ChartGPU
- Owner: ChartGPU
- License: mit
- Created: 2026-01-17T00:52:00.000Z (16 days ago)
- Default Branch: main
- Last Pushed: 2026-02-01T07:17:32.000Z (about 20 hours ago)
- Last Synced: 2026-02-01T09:21:14.980Z (about 18 hours ago)
- Topics: candlestick-chart, chart, chart-library, charting, charting-library, data-analysis, data-analytics, data-visualization, data-visualization-tools, data-viz, gpu, npm, npm-package, react, react-chart-library, typescript-library, webgl, webgpu, webgpu-shaders
- Language: TypeScript
- Homepage: https://chartgpu.github.io/ChartGPU/
- Size: 9.12 MB
- Stars: 2,405
- Watchers: 9
- Forks: 59
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: .github/SECURITY.md
Awesome Lists containing this project
- awesome - ChartGPU/ChartGPU - Beautiful, open source, WebGPU-based charting library (TypeScript)
README
High-performance charts powered by WebGPU
Documentation |
Live Demo |
Examples
ChartGPU is a TypeScript charting library built on WebGPU for smooth, interactive rendering—especially when you have lots of data.
## Highlights
- 🚀 WebGPU-accelerated rendering for high FPS with large datasets
- ⚡ Worker-based rendering with OffscreenCanvas (optional - for maximum performance)
- 📈 Multiple series types: line, area, bar, scatter, pie, candlestick
- 🌡️ Scatter density/heatmap mode (`mode: 'density'`) for large point clouds — see [`docs/api/options.md#scatterseriesconfig`](docs/api/options.md#scatterseriesconfig) and [`examples/scatter-density-1m/`](examples/scatter-density-1m/)
- 🧭 Built-in interaction: hover highlight, tooltip, crosshair
- 🔁 Streaming updates via `appendData(...)` (cartesian series)
- 🔍 X-axis zoom (inside gestures + optional slider UI)
- 🎛️ Theme presets (`'dark' | 'light'`) and custom theme support
## Architecture
At a high level, `ChartGPU.create(...)` owns the canvas + WebGPU lifecycle, and delegates render orchestration (layout/scales/data upload/render passes + internal overlays) to the render coordinator. For deeper internal notes, see [`docs/api/INTERNALS.md`](https://github.com/hunterg325/ChartGPU/blob/main/docs/api/INTERNALS.md) (especially “Render coordinator”).
```mermaid
flowchart TB
UserApp["Consumer app"] --> PublicAPI["src/index.ts (Public API exports)"]
PublicAPI --> ChartCreate["ChartGPU.create(container, options)"]
PublicAPI --> SyncAPI["connectCharts(charts)"]
subgraph MainThread["🔷 MAIN THREAD RENDERING (Default)"]
subgraph ChartInstance["Chart instance (src/ChartGPU.ts)"]
ChartCreate --> SupportCheck["checkWebGPUSupport()"]
ChartCreate --> Canvas["Create canvas + mount into container"]
ChartCreate --> Options["resolveOptionsForChart(options)
(adds bottom reserve when slider present)"]
ChartCreate --> GPUInit["GPUContext.create(canvas)"]
ChartCreate --> Coordinator["createRenderCoordinator(gpuContext, resolvedOptions)"]
ChartCreate --> InstanceAPI["ChartGPUInstance APIs"]
InstanceAPI --> RequestRender["requestAnimationFrame (coalesced)"]
RequestRender --> Coordinator
InstanceAPI --> SetOption["setOption(...)"]
InstanceAPI --> AppendData["appendData(...)"]
InstanceAPI --> Resize["resize()"]
subgraph PublicEvents["Public events + hit-testing (ChartGPU.ts)"]
Canvas --> PointerHandlers["Pointer listeners"]
PointerHandlers --> PublicHitTest["findNearestPoint() / findPieSlice()"]
PointerHandlers --> EmitEvents["emit('click'/'mouseover'/'mouseout')"]
end
DataZoomSlider["dataZoom slider (absolute-positioned DOM overlay)
chart reserves bottom space for x-axis"] --> Coordinator
end
subgraph WebGPUCore["WebGPU core (src/core/GPUContext.ts)"]
GPUInit --> AdapterDevice["navigator.gpu.requestAdapter/device"]
GPUInit --> CanvasConfig["canvasContext.configure(format)"]
end
subgraph RenderCoordinatorLayer["Render coordinator (src/core/createRenderCoordinator.ts)"]
Coordinator --> Layout["GridArea layout"]
Coordinator --> Scales["xScale/yScale (clip space for render)"]
Coordinator --> DataUpload["createDataStore(device) (GPU buffer upload/caching)"]
Coordinator --> DensityCompute["Encode + submit compute pass
(scatter density mode)"]
DensityCompute --> RenderPass["Encode + submit render pass"]
subgraph InternalOverlays["Internal interaction overlays (coordinator)"]
Coordinator --> Events["createEventManager(canvas, gridArea)"]
Events --> OverlayHitTest["hover/tooltip hit-testing"]
Events --> InteractionX["interaction-x state (crosshair)"]
Coordinator --> OverlaysDOM["DOM overlays: legend / tooltip / text labels"]
end
end
end
subgraph WorkerThread["⚡ WORKER THREAD RENDERING (Optional - src/worker/)"]
subgraph WorkerProxyAPI["Worker Proxy API (src/worker/)"]
CreateInWorker["createChartInWorker(container, options)
ChartGPU.createInWorker(container, options)"]
CreateInWorker --> ProxyInit["ChartGPUWorkerProxy initialization"]
ProxyInit --> CanvasTransfer["canvas.transferControlToOffscreen()"]
ProxyInit --> WorkerCreate["Create Worker (built-in or custom)"]
end
subgraph MainThreadProxy["Main Thread: ChartGPUWorkerProxy (src/worker/ChartGPUWorkerProxy.ts)"]
ProxyInit --> ProxyInstance["ChartGPUWorkerProxy implements ChartGPUInstance"]
ProxyInstance --> ProxyState["Local state cache
(options, interactionX, zoomRange)"]
ProxyInstance --> EventForwarding["Event forwarding to worker
(pointerdown/move/up/leave/wheel)"]
ProxyInstance --> ProxyOverlays["DOM overlay management
(tooltip, legend, text, slider)"]
ProxyInstance --> ResizeMonitoring["ResizeObserver + DPR monitoring
(RAF batched)"]
EventForwarding --> ForwardPointer["computePointerEventData()
(calculates grid coords on main thread)"]
ResizeMonitoring --> ResizeRAF["RAF-batched resize messages"]
end
subgraph WorkerInbound["Main → Worker (src/worker/protocol.ts)"]
CanvasTransfer -->|"postMessage: init"| WorkerInit["InitMessage + OffscreenCanvas transfer
(includes devicePixelRatio from main thread)"]
ProxyInstance -->|"postMessage: setOption"| WorkerSetOpt["SetOptionMessage"]
ProxyInstance -->|"postMessage: appendData"| WorkerAppend["AppendDataMessage + ArrayBuffer transfer"]
ResizeRAF -->|"postMessage: resize"| WorkerResize["ResizeMessage
(includes devicePixelRatio)"]
ForwardPointer -->|"postMessage: forwardPointerEvent"| WorkerPointer["ForwardPointerEventMessage
(includes pre-computed grid coordinates)"]
ProxyInstance -->|"postMessage: setZoomRange"| WorkerZoom["SetZoomRangeMessage"]
ProxyInstance -->|"postMessage: setInteractionX"| WorkerInteractionX["SetInteractionXMessage"]
ProxyInstance -->|"postMessage: dispose"| WorkerDispose["DisposeMessage"]
end
subgraph WorkerCore["Worker Thread: ChartGPUWorkerController (src/worker/ChartGPUWorkerController.ts)"]
WorkerInit --> WGPUInit["GPUContext.create(offscreenCanvas)"]
WGPUInit --> WOptions["resolveOptionsForChart(msg.options)
(adds bottom reserve when slider present)"]
WOptions --> WCoordinator["createRenderCoordinator(gpuContext, resolvedOptions)
computeInteractionScalesGridCssPx
(supports OffscreenCanvas)"]
WCoordinator --> WRenderLoop["MessageChannel render loop"]
WorkerSetOpt --> WOptions
WorkerAppend --> WDataStore["Worker DataStore (GPU buffer upload)"]
WorkerResize --> WCoordinator
WorkerPointer --> WHitTest["Worker hit-testing
(uses interactionScales with grid coords)
findNearestPoint/findPointsAtX"]
WorkerZoom --> WCoordinator
WorkerInteractionX --> WCoordinator
WorkerDispose --> WCleanup["Resource cleanup"]
end
subgraph WorkerOutbound["Worker → Main (postMessage)"]
WGPUInit -->|"ready"| ReadyMsg["ReadyMessage + GPU capabilities + PerformanceCapabilities"]
WRenderLoop -->|"rendered"| RenderedMsg["RenderedMessage (frame stats)"]
WRenderLoop -->|"performanceUpdate"| PerfMsg["PerformanceUpdateMessage (FPS, frame time, memory)"]
WHitTest -->|"tooltipUpdate"| TooltipMsg["TooltipUpdateMessage
(complete tooltip content + position)"]
WCoordinator -->|"legendUpdate"| LegendMsg["LegendUpdateMessage"]
WCoordinator -->|"axisLabelsUpdate"| AxisMsg["AxisLabelsUpdateMessage"]
WHitTest -->|"hoverChange"| HoverMsg["HoverChangeMessage"]
WHitTest -->|"click"| ClickMsg["ClickMessage"]
WHitTest -->|"crosshairMove"| CrosshairMsg["CrosshairMoveMessage"]
WCoordinator -->|"zoomChange"| ZoomMsg["ZoomChangeMessage"]
WGPUInit -->|"deviceLost"| DeviceLostMsg["DeviceLostMessage"]
WCleanup -->|"disposed"| DisposedMsg["DisposedMessage"]
WCoordinator -->|"error"| ErrorMsg["ErrorMessage"]
end
subgraph MainThreadDOM["Main Thread: DOM Overlay Rendering (ChartGPUWorkerProxy)"]
ReadyMsg --> ProxyOverlays
ReadyMsg --> PerfCache["Cache PerformanceCapabilities + set isInitialized"]
PerfMsg --> PerfUpdate["Cache PerformanceMetrics + notify callbacks"]
TooltipMsg --> DOMTooltip["RAF-batched tooltip.show(x, y, content)
(receives complete tooltip data from worker)"]
LegendMsg --> DOMLegend["RAF-batched legend.update(items, theme)"]
AxisMsg --> DOMAxis["RAF-batched textOverlay.addLabel(...)
(auto-handles container overflow)"]
HoverMsg --> DOMHover["Re-emit 'mouseover'/'mouseout' events"]
ClickMsg --> DOMClick["Re-emit 'click' event"]
CrosshairMsg --> DOMCrosshair["Update cached interactionX + emit"]
ZoomMsg --> DOMZoom["Update cached zoomRange + zoomState"]
ProxyOverlays --> DOMTooltip
ProxyOverlays --> DOMLegend
ProxyOverlays --> DOMAxis
end
end
subgraph Renderers["GPU renderers (src/renderers/*)"]
RenderPass --> GridR["Grid"]
RenderPass --> AreaR["Area"]
RenderPass --> BarR["Bar"]
RenderPass --> ScatterR["Scatter"]
RenderPass --> ScatterDensityR["Scatter density/heatmap"]
RenderPass --> LineR["Line"]
RenderPass --> PieR["Pie"]
RenderPass --> CandlestickR["Candlestick"]
RenderPass --> CrosshairR["Crosshair overlay"]
RenderPass --> HighlightR["Hover highlight overlay"]
RenderPass --> AxisR["Axes/ticks"]
WRenderLoop --> GridR
end
subgraph Shaders["WGSL shaders (src/shaders/*)"]
GridR --> gridWGSL["grid.wgsl"]
AreaR --> areaWGSL["area.wgsl"]
BarR --> barWGSL["bar.wgsl"]
ScatterR --> scatterWGSL["scatter.wgsl"]
ScatterDensityR --> scatterDensityBinningWGSL["scatterDensityBinning.wgsl"]
ScatterDensityR --> scatterDensityColormapWGSL["scatterDensityColormap.wgsl"]
LineR --> lineWGSL["line.wgsl"]
PieR --> pieWGSL["pie.wgsl"]
CandlestickR --> candlestickWGSL["candlestick.wgsl"]
CrosshairR --> crosshairWGSL["crosshair.wgsl"]
HighlightR --> highlightWGSL["highlight.wgsl"]
end
subgraph ChartSync["Chart sync (src/interaction/createChartSync.ts)"]
SyncAPI --> ListenX["listen: 'crosshairMove'"]
SyncAPI --> DriveX["setCrosshairX(...) on peers"]
end
InteractionX --> ListenX
DriveX --> InstanceAPI
CrosshairMsg --> ListenX
```
## Demo

### Candlestick Charts
Financial OHLC (open-high-low-close) candlestick rendering with classic/hollow style toggle and color customization. The live streaming demo renders **5 million candlesticks at over 100 FPS** with real-time updates.

### Scatter Density (1M points)
GPU-binned density/heatmap mode for scatter plots (`mode: 'density'`) to reveal structure in overplotted point clouds. See [`docs/api/options.md#scatterseriesconfig`](docs/api/options.md#scatterseriesconfig) and the demo in [`examples/scatter-density-1m/`](examples/scatter-density-1m/).

### 10M points (benchmark)
10,000,000 points rendered at ~120 FPS (benchmark mode).

## Quick start
```ts
import { ChartGPU } from 'chartgpu';
const container = document.getElementById('chart')!;
await ChartGPU.create(container, {
series: [{ type: 'line', data: [[0, 1], [1, 3], [2, 2]] }],
});
```
### Worker-based rendering (optional)
For maximum performance with large datasets, use worker-based rendering to keep the main thread responsive:
```ts
import { ChartGPU } from 'chartgpu';
const container = document.getElementById('chart')!;
// Identical API, but rendering happens in a Web Worker
await ChartGPU.createInWorker(container, {
series: [{ type: 'line', data: [[0, 1], [1, 3], [2, 2]] }],
});
```
**When to use workers:**
- Large datasets (>10K points) with frequent updates
- Real-time streaming data
- Complex multi-series charts
- Mobile/low-power devices
See [Worker API Documentation](https://github.com/hunterg325/ChartGPU/blob/main/docs/api/worker.md) for details.
## Installation
`npm install chartgpu`
## React Integration
React bindings are available via [`chartgpu-react`](https://github.com/ChartGPU/chartgpu-react):
```bash
npm install chartgpu-react
```
```tsx
import { ChartGPUChart } from 'chartgpu-react';
function MyChart() {
return (
);
}
```
See the [chartgpu-react repository](https://github.com/ChartGPU/chartgpu-react) for full documentation and examples.
## Browser support (WebGPU required)
- Chrome 113+ or Edge 113+ (WebGPU enabled by default)
- Safari 18+ (WebGPU enabled by default)
- Firefox: not supported (WebGPU support in development)
## Documentation
- Full documentation: [Getting Started](https://github.com/hunterg325/ChartGPU/blob/main/docs/GETTING_STARTED.md)
- API reference: [`docs/api/README.md`](https://github.com/hunterg325/ChartGPU/blob/main/docs/api/README.md)
## Examples
- Browse examples: [`examples/`](https://github.com/hunterg325/ChartGPU/tree/main/examples)
- Run locally:
- `npm install`
- `npm run dev` (opens `http://localhost:5176/examples/`)
## Contributing
See [`CONTRIBUTING.md`](https://github.com/hunterg325/ChartGPU/blob/main/CONTRIBUTING.md).
## License
MIT — see [`LICENSE`](https://github.com/hunterg325/ChartGPU/blob/main/LICENSE).