Rendering A Million Points with <canvas/>
Table of Contents
- LineChart with Canvas
- BarChart with Canvas
- Canvas Features
- Best Practices
- Switching Between Modes
- Conclusion
Use canvas={true} for large datasets (1000+ points). Canvas renders data 20-30x faster than SVG, while axes and labels remain crisp in SVG.
LineChart with Canvas
Basic Usage
Option + Scroll to zoom, Click + Drag to pan. Viewing 0 - 1000 (0.1% of 1M points).
import { useState } from "react";
import { durationMinutes, LineChart, SeriesTooltip, useZoomPan, VerticalMarker } from "@vuer-ai/vuer-viz";
import { generateSineWaveData } from "./generateData";
const { x, y } = generateSineWaveData(1_000_000);
const series = [{ label: "Data", x, y, color: "#3498db" }];
export default function BasicCanvas() {
const [cursorX, setCursorX] = useState<number | null>(null);
const [xMin, setXMin] = useState(0);
const [xMax, setXMax] = useState(1_000);
const { plotRef } = useZoomPan({min : xMin, max : xMax, setMin : setXMin, setMax : setXMax, minLimit : 0, maxLimit : 1_000_000});
return (
<div>
<LineChart
plotRef={plotRef}
series={series}
width={770}
canvas
xMin={xMin}
xMax={xMax}
xFormat="minutes"
onChange={(e) => setCursorX(e.x)}
>
<VerticalMarker x={cursorX} />
<SeriesTooltip x={cursorX} series={series} xFormat="minutes"/>
</LineChart>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Option + Scroll to zoom, Click + Drag to pan.
Viewing {xMin.toFixed(0)} - {xMax.toFixed(0)} ({((xMax - xMin) / 10000).toFixed(1)}% of 1M points).
</p>
</div>
);
}
Multi-Series with Canvas
Canvas rendering supports all line chart features:
import { LineChart } from '@vuer-ai/vuer-viz';
import { generateMultiSineData } from './generateData';
const [dataA, dataB] = generateMultiSineData(5000, 2);
export default function MultiSeriesCanvas() {
return (
<LineChart
series={[
{
label: 'Series A',
x: dataA.x,
y: dataA.y,
color: '#23aaff',
lineWidth: 2,
showPoints: false,
},
{
label: 'Series B',
x: dataB.x,
y: dataB.y,
color: '#ffaf24',
lineWidth: 2,
dashArray: '5 5',
showPoints: true,
pointRadius: 3,
},
]}
canvas
width={770}
height={400}
/>
);
}
Performance Comparison
| Dataset Size | SVG Render Time | Canvas Render Time | Improvement |
|---|---|---|---|
| 100 points | ~5ms | ~3ms | 1.7x faster |
| 1,000 points | ~50ms | ~8ms | 6x faster |
| 10,000 points | ~500ms | ~25ms | 20x faster |
| 50,000 points | ~2500ms (slow) | ~80ms | 31x faster |
Times measured on a modern laptop. Results may vary based on hardware.
Live Comparison: SVG vs Canvas
Below are two identical charts with 5,000 data points. The first uses SVG rendering (default), the second uses canvas rendering. Notice the difference in rendering performance.
SVG Rendering (5,000 points)
import { LineChart } from '@vuer-ai/vuer-viz';
import { generateSineWaveData } from './generateData';
// Generate 5,000 data points
const { x, y } = generateSineWaveData(5000);
export default function SvgLargeDataset() {
return (
<LineChart
series={[
{
label: 'SVG Rendering',
x,
y,
color: '#23aaff',
lineWidth: 2,
showPoints: false,
},
]}
title="SVG Rendering (5,000 points)"
xLabel="X Axis"
yLabel="Y Axis"
width={770}
height={400}
grid={true}
legend={true}
/>
);
}
Canvas Rendering (5,000 points)
import { LineChart } from '@vuer-ai/vuer-viz';
import { generateSineWaveData } from './generateData';
// Generate 5,000 data points (same as SVG example)
const { x, y } = generateSineWaveData(5000);
export default function CanvasLargeDataset() {
return (
<LineChart
series={[
{
label: 'Canvas Rendering',
x,
y,
color: '#ffaf24',
lineWidth: 2,
showPoints: false,
},
]}
title="Canvas Rendering (5,000 points)"
xLabel="X Axis"
yLabel="Y Axis"
width={770}
height={400}
grid={true}
legend={true}
canvas={true}
/>
);
}
The canvas version renders significantly faster, especially on initial load and when resizing the browser window.
BarChart with Canvas
Vertical Bars
import { BarChart } from '@vuer-ai/vuer-viz';
const label = Array.from({ length: 200 }, (_, i) => `Item ${i}`);
const value = Array.from({ length: 200 }, () => Math.random() * 100);
export default function BarCanvas() {
return (
<BarChart
label={label}
value={value}
canvas
orientation="vertical"
/>
);
}
Horizontal Bars
Canvas works with both orientations:
<BarChart data={data} canvas orientation="horizontal" showLabels={true} // Value labels also render on canvas />
Colored Bars
Individual bar colors work seamlessly:
const coloredData = [ { label: 'A', value: 100, color: '#23aaff' }, { label: 'B', value: 85, color: '#ffaf24' }, { label: 'C', value: 92, color: '#ff4be1' }, // ... 100+ more bars ]; <BarChart data={coloredData} canvas />
Canvas Features
High-DPI Display Support
Canvas rendering automatically accounts for device pixel ratio:
// Automatically crisp on retina displays <LineChart series={data} canvas />
The canvas is internally scaled to match your display's pixel density, ensuring sharp rendering on all devices.
Value Labels on Canvas
When using showLabels={true}, labels are rendered directly on the canvas:
<BarChart data={data} canvas showLabels={true} fontSize={14} />
Dash Arrays
Dashed lines work on canvas using the same syntax:
<LineChart series={[ { label: 'Dashed', data: data, dashArray: '5 5', // 5px dash, 5px gap }, ]} canvas />
Best Practices
1. Profile Before Optimizing
Start with SVG (default). Only switch to canvas if you experience performance issues.
// Start here <LineChart series={data} /> // If slow, add canvas <LineChart series={data} canvas />
2. Use React.memo
Prevent unnecessary re-renders:
const MemoizedChart = React.memo(LineChart); <MemoizedChart series={data} canvas />
3. Memoize Data
Avoid recreating datasets on every render:
const data = useMemo(() => { return generateLargeDataset(); }, [dependencies]); <LineChart series={data} canvas />
4. Throttle Updates
For real-time charts, throttle updates:
import { useEffect, useState } from 'react'; import { throttle } from 'lodash'; function RealtimeChart() { const [data, setData] = useState(initialData); useEffect(() => { const updateData = throttle((newData) => { setData(newData); }, 100); // Update at most every 100ms const subscription = dataStream.subscribe(updateData); return () => subscription.unsubscribe(); }, []); return <LineChart series={data} canvas />; }
Switching Between Modes
You can dynamically toggle between canvas and SVG:
function AdaptiveChart({ data }) { const canvas = data[0].data.length > 1000; return ( <LineChart series={data} canvas={canvas} /> ); }
The canvas is embedded in SVG using <foreignObject>, allowing seamless integration with SVG UI elements.
Canvas Context
Charts use 2D canvas context with these settings:
- Device pixel ratio scaling for high-DPI displays
- Anti-aliasing enabled for smooth lines
- Line join: round (for smooth corners)
- Line cap: round (for smooth endpoints)
Conclusion
Canvas rendering in Vuer-Viz provides:
- ⚡ 20-30x faster rendering for large datasets
- 📱 Better mobile performance
- 🎨 Hybrid approach keeps text crisp
- 🔧 Easy toggle with single prop
Use it when performance matters, stick with SVG when interactivity and accessibility are priorities.