Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 108 additions & 147 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

React bindings for [ChartGPU](https://github.com/huntergemmer/chart-gpu) - a WebGPU-powered charting library delivering high-performance data visualization.

## Features
## Documentation

- **WebGPU-Accelerated**: Leverages GPU compute and rendering for exceptional performance
- **React 18+**: Built with modern React patterns (hooks, functional components)
- **Type-Safe**: Full TypeScript support with comprehensive type definitions
- **Production-Ready**: Handles async initialization, cleanup, and edge cases safely
- **Flexible API**: Declarative options-based configuration
- **Getting started**: [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md)
- **API reference**: [`docs/API.md`](./docs/API.md)
- **Recipes**:
- [`onCrosshairMove`](./docs/recipes/crosshair-move.md)
- [`useConnectCharts` / `connectCharts`](./docs/recipes/chart-sync.md)
- [`createAnnotationAuthoring`](./docs/recipes/annotation-authoring.md)
- [`appendData` streaming](./docs/recipes/streaming.md)
- [`dataZoom` + `onZoomChange`](./docs/recipes/datazoom-basics.md)

## Installation

Expand All @@ -29,10 +32,11 @@ Check browser compatibility at [caniuse.com/webgpu](https://caniuse.com/webgpu)
## Quick Start

```tsx
import { ChartGPUChart } from 'chartgpu-react';
import { ChartGPU } from 'chartgpu-react';
import type { ChartGPUOptions } from 'chartgpu-react';

function MyChart() {
const options = {
const options: ChartGPUOptions = {
series: [
{
type: 'line',
Expand All @@ -53,133 +57,138 @@ function MyChart() {
};

return (
<ChartGPUChart
<ChartGPU
options={options}
style={{ width: '100%', height: '400px' }}
/>
);
}
```

## Component API
## What this package provides

### `ChartGPUChart`
- **`ChartGPU`** React component (recommended)
- lifecycle management (async create + dispose)
- `ResizeObserver` resize (debounced)
- event props: `onClick`, `onCrosshairMove`, `onZoomChange`, etc.
- imperative `ref` API: `ChartGPUHandle` (`getChart`, `getContainer`, `appendData`, `setOption`)
- **Hooks**
- `useChartGPU(containerRef, options)` — create/manage a chart instance
- `useConnectCharts([chartA, chartB, ...])` — sync crosshair/interaction-x across charts
- **Deprecated**
- `ChartGPUChart` (legacy adapter; use `ChartGPU` instead)
- **Helper re-exports** (from peer dependency `chartgpu`)
- `createChart`, `connectCharts`, `createAnnotationAuthoring`

Main React component wrapping ChartGPU functionality.
For details, start with the [API reference](./docs/API.md).

#### Props
## v0.2.3 feature snippets (ChartGPU core)

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `options` | `ChartGPUOptions` | Yes | Chart configuration (series, axes, styling) |
| `className` | `string` | No | CSS class name for the container div |
| `style` | `React.CSSProperties` | No | Inline styles for the container div |
| `onInit` | `(instance: ChartGPUInstance) => void` | No | Callback fired when chart instance is created |
| `onDispose` | `() => void` | No | Callback fired when chart instance is disposed |
These snippets use helpers and events from the `chartgpu` core library (peer dependency of `chartgpu-react`).

#### Behavior
### Crosshair / interaction X (`'crosshairMove'`)

**Lifecycle Management:**
- Creates ChartGPU instance on mount via `ChartGPU.create()`
- Safely handles async initialization race conditions
- Automatically disposes instance on unmount
- Prevents state updates after component unmount
```tsx
import { ChartGPU } from 'chartgpu-react';
import type { ChartGPUCrosshairMovePayload } from 'chartgpu-react';

<ChartGPU
options={options}
onCrosshairMove={(p: ChartGPUCrosshairMovePayload) => {
// p.x is the current interaction x (domain units), or null when cleared
console.log('crosshair x:', p.x, 'source:', p.source);
}}
/>;
```

**Options Updates:**
- Calls `instance.setOption(options)` when `options` prop changes
- Triggers automatic re-render of the chart
### Connect charts (sync crosshair/tooltip)

**Resize Handling:**
- Listens to window resize events
- Calls `instance.resize()` automatically
- Ensures chart maintains correct dimensions
```tsx
import { connectCharts } from 'chartgpu';

## Examples
// When you have two ChartGPUInstance objects:
const disconnect = connectCharts([chartA, chartB]);

// Later:
disconnect();
```

If you prefer a hook-driven approach, you can use `onReady` (or `useChartGPU`) to capture instances, then call `connectCharts(...)` once both are available.

### Multi-Series Line Chart
### Annotation authoring UI (`createAnnotationAuthoring`)

```tsx
import { ChartGPUChart } from 'chartgpu-react';
import { useEffect, useRef, useState } from 'react';
import { ChartGPU } from 'chartgpu-react';
import type { ChartGPUHandle, ChartGPUInstance } from 'chartgpu-react';
import { createAnnotationAuthoring } from 'chartgpu-react';

function AnnotationAuthoringExample() {
const chartRef = useRef<ChartGPUHandle>(null);
const [chart, setChart] = useState<ChartGPUInstance | null>(null);

useEffect(() => {
const container = chartRef.current?.getContainer();
const instance = chartRef.current?.getChart();
if (!container || !instance) return;

const authoring = createAnnotationAuthoring(container, instance, {
enableContextMenu: true,
});

function MultiSeriesExample() {
const options = {
series: [
{
type: 'line',
name: 'Revenue',
data: generateData(50),
lineStyle: { color: '#667eea', width: 2 },
},
{
type: 'line',
name: 'Expenses',
data: generateData(50),
lineStyle: { color: '#f093fb', width: 2 },
},
],
xAxis: { type: 'value' },
yAxis: { type: 'value' },
grid: { left: 60, right: 40, top: 40, bottom: 40 },
tooltip: { show: true },
};
// IMPORTANT: dispose authoring before disposing the chart
return () => authoring.dispose();
}, [chart]);

return (
<ChartGPUChart
options={options}
style={{ width: '100%', height: '400px' }}
/>
);
return <ChartGPU ref={chartRef} options={options} onReady={setChart} />;
}
```

### Area Chart with Gradient
### Candlestick streaming (`appendData` + `OHLCDataPoint`)

```tsx
function AreaChartExample() {
const options = {
import { useEffect, useRef } from 'react';
import { ChartGPU } from 'chartgpu-react';
import type { ChartGPUHandle, ChartGPUOptions } from 'chartgpu-react';
import type { OHLCDataPoint } from 'chartgpu';

function CandlestickStreaming() {
const ref = useRef<ChartGPUHandle>(null);

const options: ChartGPUOptions = {
xAxis: { type: 'time' },
dataZoom: [{ type: 'inside' }, { type: 'slider' }],
autoScroll: true,
series: [
{
type: 'line',
data: generateSineWave(100),
lineStyle: { color: '#667eea', width: 2 },
areaStyle: { color: 'rgba(102, 126, 234, 0.2)' },
type: 'candlestick',
sampling: 'ohlc',
data: [], // start empty; stream in candles below
},
],
xAxis: { type: 'value' },
yAxis: { type: 'value' },
};

return <ChartGPUChart options={options} style={{ height: '300px' }} />;
useEffect(() => {
const timer = window.setInterval(() => {
const next: OHLCDataPoint = {
timestamp: Date.now(),
open: 100,
close: 102,
low: 99,
high: 103,
};
ref.current?.appendData(0, [next]);
}, 500);
return () => window.clearInterval(timer);
}, []);

return <ChartGPU ref={ref} options={options} style={{ height: 360 }} />;
}
```

### Using Chart Instance Callbacks

```tsx
function ChartWithCallbacks() {
const handleInit = (instance: ChartGPUInstance) => {
console.log('Chart initialized:', instance);

// Add event listeners
instance.on('click', (payload) => {
console.log('Chart clicked:', payload);
});
};

const handleDispose = () => {
console.log('Chart cleaned up');
};
## Examples

return (
<ChartGPUChart
options={options}
onInit={handleInit}
onDispose={handleDispose}
style={{ height: '400px' }}
/>
);
}
```
See the runnable example app in [`examples/main.tsx`](./examples/main.tsx).

## Development

Expand Down Expand Up @@ -246,54 +255,6 @@ import type {
} from 'chartgpu-react';
```

## Technical Details

### Async Initialization Safety

The component uses a mounted ref pattern to prevent React state updates after unmount:

```typescript
useEffect(() => {
mountedRef.current = true;

const initChart = async () => {
const instance = await ChartGPU.create(container, options);

// Only update state if still mounted
if (mountedRef.current) {
instanceRef.current = instance;
} else {
// Dispose immediately if unmounted during async create
instance.dispose();
}
};

initChart();

return () => {
mountedRef.current = false;
instanceRef.current?.dispose();
};
}, []);
```

This pattern ensures:
1. No memory leaks from orphaned chart instances
2. No React warnings about setState on unmounted components
3. Clean resource cleanup even if creation takes time

### Options Updates

Options changes trigger `setOption()` on the existing instance rather than recreating the chart:

```typescript
useEffect(() => {
instanceRef.current?.setOption(options);
}, [options]);
```

This provides better performance for dynamic data updates.

## Browser Compatibility

WebGPU is required. Check support at runtime:
Expand Down
16 changes: 16 additions & 0 deletions context7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://context7.com/schema/context7.json",
"projectTitle": "chartgpu-react",
"description": "React bindings for ChartGPU — a WebGPU-powered charting library.",
"folders": ["docs", "examples"],
"excludeFolders": ["node_modules", "dist", "coverage", ".git", ".github", ".cursor"],
"excludeFiles": ["IMPLEMENTATION_REPORT.md", "package-lock.json"],
"rules": [
"Prefer the `ChartGPU` component; `ChartGPUChart` is deprecated and will be removed in a future major version.",
"`options` is treated as a full replacement object; `setOption()` does not partial-merge.",
"For streaming/real-time updates, prefer `ChartGPUHandle.appendData()` over replacing `options.series[].data`.",
"If you use `createAnnotationAuthoring(container, chart, ...)`, dispose the authoring instance before disposing the chart.",
"Ensure the chart container has an explicit, non-zero height; otherwise the chart may render blank.",
"In React 18 StrictMode (dev), effects run twice (mount→unmount→mount); avoid retaining stale chart instances outside refs/onReady."
]
}
Loading