Chart.js is a simple, flexible JavaScript charting library that renders beautiful charts on HTML5 canvas elements. It ships eight chart types out of the box, supports responsive design by default, and offers a tree-shakeable architecture so you only bundle what you need.
This guide covers Chart.js v4.5.1 and walks through everything from your first chart to advanced customization patterns.
1. Quick Start
Get a chart on screen in under two minutes using the CDN build.
1.1 Minimal Example
Create an HTML file with a <canvas> element and a <script> tag:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My First Chart</title>
</head>
<body>
<canvas id="myChart" width="400" height="200"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
<script>
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
datasets: [{
label: 'Sales',
data: [12, 19, 3, 5, 14],
borderWidth: 1
}]
},
options: {
scales: {
y: { beginAtZero: true }
}
}
});
</script>
</body>
</html>
Open the file in any modern browser and you will see a bar chart with five data points. The Chart constructor accepts a canvas element, a canvas ID string, or a 2D rendering context.
1.2 What Just Happened
The UMD bundle (chart.umd.min.js) auto-registers every built-in controller, element, scale, and plugin. You called new Chart(element, config) and Chart.js:
- Created a bar controller with a category x-axis and a linear y-axis
- Applied default styling (the built-in Colors plugin assigned a blue palette)
- Made the chart responsive via ResizeObserver
- Animated everything in over 1000ms with easeOutQuart easing
Tip
For production applications, use the tree-shakeable ESM import (covered in section 2.3) instead of the UMD bundle. This can cut your Chart.js bundle size significantly.
2. Installation
2.1 CDN (No Build Step)
Use jsDelivr or unpkg to load the UMD bundle directly:
<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
<!-- unpkg -->
<script src="https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js"></script>
This registers all chart types and plugins automatically. No further setup is needed.
2.2 npm / pnpm / yarn
npm install chart.js
pnpm add chart.js
yarn add chart.js
Chart.js has a single runtime dependency: @kurkle/color (^0.3.0), which handles color parsing and manipulation. It installs automatically.
2.3 ESM Imports (Tree-Shakeable)
For the smallest bundle, import only what you need and register it explicitly:
import {
Chart,
BarController,
CategoryScale,
LinearScale,
BarElement,
Legend,
Tooltip
} from 'chart.js';
Chart.register(
BarController,
CategoryScale,
LinearScale,
BarElement,
Legend,
Tooltip
);
If you prefer convenience over bundle size, import the auto-registering entry point:
import Chart from 'chart.js/auto';
This registers all built-in controllers, elements, scales, and plugins in one line.
Important
When using tree-shaking, you must register every component your chart uses. A bar chart needs
BarController,BarElement,CategoryScale, andLinearScaleat minimum. Forgetting a component will produce a runtime error like "Cannot determine type of '...' axis."
2.4 Package Exports
Chart.js provides three entry points:
| Import Path | Description |
|---|---|
chart.js |
Tree-shakeable ESM (dist/chart.js) and CJS (dist/chart.cjs) |
chart.js/auto |
Auto-registers all built-ins |
chart.js/helpers |
Utility functions for plugin and extension authors |
2.5 TypeScript
Type definitions ship with the package at dist/types.d.ts. No separate @types package is needed.
import { Chart, ChartConfiguration } from 'chart.js';
const config: ChartConfiguration<'bar'> = {
type: 'bar',
data: {
labels: ['A', 'B', 'C'],
datasets: [{
label: 'Values',
data: [10, 20, 30]
}]
}
};
const chart = new Chart(document.getElementById('myChart') as HTMLCanvasElement, config);
2.6 Requirements
- A modern browser with HTML5 Canvas support (all current major browsers)
- For time-based axes, an external date adapter is required (see section 5.8)
3. Configuration
Every chart starts with a configuration object containing type, data, and options.
3.1 Configuration Structure
const config = {
type: 'line', // Chart type string
data: {
labels: [...], // x-axis labels
datasets: [{
label: 'Series 1',
data: [...],
// dataset-level options
}]
},
options: {
responsive: true, // Chart-level options
plugins: { ... }, // Plugin configuration
scales: { ... }, // Scale configuration
animation: { ... }, // Animation settings
interaction: { ... } // Hover/click behavior
}
};
3.2 Responsive Behavior
Charts are responsive by default. Chart.js uses ResizeObserver to detect container size changes and redraws automatically.
options: {
responsive: true, // Default: true
maintainAspectRatio: true, // Default: true
aspectRatio: 2 // Width/height ratio (default: 2 for most, 1 for radial)
}
Note
The canvas element must be wrapped in a dedicated container
<div>. Chart.js sets the canvas dimensions based on the container size. If the canvas is not inside a block-level container, responsive resizing may behave unexpectedly.
3.3 Animation
The default animation runs for 1000ms with easeOutQuart easing. You can customize or disable it:
options: {
animation: {
duration: 1000, // Default: 1000ms
easing: 'easeOutQuart' // Default easing
}
}
Built-in transitions have their own defaults:
| Transition | Duration | Purpose |
|---|---|---|
active |
400ms | Hover state changes |
resize |
0ms | Container resize (instant) |
show |
default | Dataset shown |
hide |
default | Dataset hidden |
To disable all animation:
options: {
animation: false
}
3.4 Scriptable Options
Most styling options accept a function instead of a static value. The function receives a context object and returns the value to apply:
datasets: [{
label: 'Revenue',
data: [65, 59, 80, 81, 56],
backgroundColor: (ctx) => {
const value = ctx.dataset.data[ctx.dataIndex];
return value > 70 ? '#22c55e' : '#3b82f6';
}
}]
The context object (ctx) includes properties like chart, dataset, dataIndex, datasetIndex, and parsed depending on where the option is used.
3.5 Indexable Options
Many styling options accept arrays, applying each value to the corresponding data point:
datasets: [{
label: 'Scores',
data: [65, 59, 80, 81, 56],
backgroundColor: [
'#ef4444', '#f97316', '#22c55e', '#22c55e', '#f97316'
]
}]
3.6 Color Handling
The built-in Colors plugin auto-assigns colors from a 7-color palette when you do not specify backgroundColor or borderColor:
| Index | Color |
|---|---|
| 0 | Blue |
| 1 | Red |
| 2 | Orange |
| 3 | Yellow |
| 4 | Green |
| 5 | Purple |
| 6 | Grey |
Colors cycle for datasets beyond index 6. To override auto-coloring globally:
options: {
plugins: {
colors: {
enabled: true, // Default: true
forceOverride: false // Set true to override even manually set colors
}
}
}
3.7 Interaction Modes
Control how the chart responds to mouse hover and click events:
options: {
interaction: {
mode: 'index', // 'point', 'nearest', 'index', 'dataset', 'x', 'y'
intersect: true, // Must hover directly over element
axis: 'x' // Axis to use for distance calculation
}
}
| Mode | Behavior |
|---|---|
point |
Items at the exact intersection point |
nearest |
Single nearest item |
index |
All items at the same index |
dataset |
All items in the same dataset |
x |
All items at the same x value |
y |
All items at the same y value |
4. Core Usage
4.1 Bar Charts
Bar charts display data as vertical or horizontal rectangular bars using a category axis and a linear value axis.
Basic vertical bar chart:
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Revenue ($K)',
data: [120, 180, 95, 210],
backgroundColor: '#3b82f6'
}]
},
options: {
scales: {
y: { beginAtZero: true }
}
}
});
Horizontal bar chart (set indexAxis to 'y'):
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Alice', 'Bob', 'Carol'],
datasets: [{
label: 'Tasks Completed',
data: [23, 17, 31],
backgroundColor: '#8b5cf6'
}]
},
options: {
indexAxis: 'y'
}
});
Stacked bar chart:
options: {
scales: {
x: { stacked: true },
y: { stacked: true }
}
}
Floating bars accept [start, end] arrays instead of single values:
datasets: [{
label: 'Temperature Range',
data: [[12, 24], [15, 28], [10, 22], [18, 30]]
}]
Bar-specific defaults:
| Option | Default | Description |
|---|---|---|
categoryPercentage |
0.8 | Width of category block as fraction of grid |
barPercentage |
0.9 | Width of bar within category block |
grouped |
true | Group bars of different datasets |
skipNull |
false | Skip null data points |
barThickness |
auto | Fixed pixel width |
maxBarThickness |
— | Maximum pixel width |
minBarLength |
— | Minimum pixel length (for visibility) |
4.2 Line Charts
Line charts connect data points with lines and optionally fill the area beneath.
Basic line chart:
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Visitors',
data: [1200, 1900, 3000, 5000, 4200, 3100],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.3
}]
}
});
Smooth curves use the tension option (0 = straight lines, 0.4 = smooth):
datasets: [{
tension: 0.4
}]
For Bezier interpolation that preserves monotonicity:
datasets: [{
cubicInterpolationMode: 'monotone'
}]
Stepped lines:
datasets: [{
stepped: true // or 'before', 'after', 'middle'
}]
Handling gaps in data with spanGaps:
datasets: [{
data: [10, 20, null, null, 50, 60],
spanGaps: true // Connects across null values
// spanGaps: 86400000 // Or max gap in ms for time axes
}]
Per-segment styling lets you style individual line segments differently:
datasets: [{
segment: {
borderColor: (ctx) => ctx.p0.parsed.y > ctx.p1.parsed.y ? '#ef4444' : '#22c55e',
borderDash: (ctx) => ctx.p0.skip || ctx.p1.skip ? [6, 6] : undefined
}
}]
Line-specific defaults:
| Option | Default | Description |
|---|---|---|
showLine |
true | Draw the connecting line |
spanGaps |
false | Connect across null/NaN |
fill |
false | Fill area below line |
tension |
0 | Bezier curve tension |
4.3 Pie and Doughnut Charts
Pie and doughnut charts display data as proportional arc segments. The doughnut is a pie with a hole in the center.
Pie chart:
new Chart(ctx, {
type: 'pie',
data: {
labels: ['Desktop', 'Mobile', 'Tablet'],
datasets: [{
data: [55, 35, 10],
backgroundColor: ['#3b82f6', '#ef4444', '#f97316']
}]
}
});
Doughnut chart:
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Used', 'Free'],
datasets: [{
data: [73, 27],
backgroundColor: ['#3b82f6', '#e5e7eb'],
cutout: '70%' // Default: '50%'
}]
}
});
Semi-circle doughnut:
options: {
rotation: -90,
circumference: 180
}
Doughnut/Pie defaults:
| Option | Default | Description |
|---|---|---|
cutout |
'50%' (doughnut), 0 (pie) |
Inner radius |
rotation |
0 | Start angle in degrees |
circumference |
360 | Sweep angle in degrees |
radius |
'100%' |
Outer radius |
spacing |
0 | Gap between arcs in pixels |
spacingMode |
'angular' |
How spacing is applied |
animation.animateRotate |
true | Animate arc rotation on load |
animation.animateScale |
true | Scale from center on load |
4.4 Scatter Charts
Scatter charts plot individual data points on linear x and y axes.
new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: 'Height vs Weight',
data: [
{ x: 165, y: 60 },
{ x: 170, y: 72 },
{ x: 175, y: 68 },
{ x: 180, y: 85 },
{ x: 160, y: 55 }
],
backgroundColor: '#3b82f6'
}]
},
options: {
scales: {
x: { title: { display: true, text: 'Height (cm)' } },
y: { title: { display: true, text: 'Weight (kg)' } }
}
}
});
Scatter defaults to showLine: false and interaction mode 'point'. To connect scatter points with lines, set showLine: true on the dataset.
4.5 Bubble Charts
Bubble charts add a third dimension (radius) to scatter plots. Each data point is an object with x, y, and r properties.
new Chart(ctx, {
type: 'bubble',
data: {
datasets: [{
label: 'Market Cap',
data: [
{ x: 20, y: 30, r: 15 },
{ x: 40, y: 10, r: 25 },
{ x: 15, y: 45, r: 10 },
{ x: 35, y: 25, r: 35 }
],
backgroundColor: 'rgba(59, 130, 246, 0.5)'
}]
},
options: {
scales: {
x: { title: { display: true, text: 'Risk' } },
y: { title: { display: true, text: 'Return (%)' } }
}
}
});
The r value is in pixels, not scaled to the axis. Both x and y axes are linear.
4.6 Radar Charts
Radar charts plot multivariate data on radial axes from a central point, forming a polygon.
new Chart(ctx, {
type: 'radar',
data: {
labels: ['Speed', 'Power', 'Defense', 'Stamina', 'Skill', 'Luck'],
datasets: [{
label: 'Player A',
data: [85, 72, 90, 68, 78, 60],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.2)'
}, {
label: 'Player B',
data: [65, 88, 70, 85, 92, 75],
borderColor: '#ef4444',
backgroundColor: 'rgba(239, 68, 68, 0.2)'
}]
},
options: {
scales: {
r: {
beginAtZero: true,
max: 100
}
}
}
});
Radar charts default to showLine: true, fill: 'start', and loop: true (closing the polygon).
4.7 Polar Area Charts
Polar area charts are similar to pie charts, but each segment has equal angle and the radius encodes the value.
new Chart(ctx, {
type: 'polarArea',
data: {
labels: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'],
datasets: [{
data: [11, 16, 7, 3, 14],
backgroundColor: [
'rgba(239, 68, 68, 0.5)',
'rgba(34, 197, 94, 0.5)',
'rgba(234, 179, 8, 0.5)',
'rgba(156, 163, 175, 0.5)',
'rgba(59, 130, 246, 0.5)'
]
}]
}
});
Uses a radialLinear scale. Defaults: startAngle: 0, spacing: 0, spacingMode: 'proportional'.
4.8 Mixed Charts
Combine chart types on the same canvas by setting type per dataset:
new Chart(ctx, {
type: 'bar', // Default type
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
datasets: [{
type: 'line',
label: 'Trend',
data: [10, 15, 20, 25, 30],
borderColor: '#ef4444',
fill: false,
order: 0 // Draw on top
}, {
label: 'Sales',
data: [12, 19, 8, 25, 22],
backgroundColor: '#3b82f6',
order: 1 // Draw behind
}]
}
});
Use the order property to control drawing order (lower values draw on top).
5. Common Tasks
5.1 Updating Chart Data
Call chart.update() after modifying data or options:
// Add a data point
chart.data.labels.push('Jun');
chart.data.datasets[0].data.push(42);
chart.update();
// Replace all data
chart.data.datasets[0].data = [5, 10, 15, 20, 25, 30];
chart.update();
// Update without animation
chart.update('none');
5.2 Destroying and Recreating Charts
You must destroy a chart before reusing its canvas:
chart.destroy();
// Now safe to create a new chart on the same canvas
const newChart = new Chart(ctx, newConfig);
Warning
Failing to destroy a chart before creating a new one on the same canvas produces: "Canvas is already in use. Chart with ID '...' must be destroyed before the canvas with ID '...' can be reused." Always call
.destroy()first.
5.3 Customizing the Legend
The legend is a built-in plugin displayed at the top by default:
options: {
plugins: {
legend: {
display: true, // Default: true
position: 'top', // 'top', 'left', 'bottom', 'right'
align: 'center', // 'start', 'center', 'end'
labels: {
color: '#374151',
font: { size: 14 },
padding: 20,
usePointStyle: true // Use point shape instead of box
}
}
}
}
Clicking a legend item toggles the dataset's visibility by default. To customize:
options: {
plugins: {
legend: {
onClick: (event, legendItem, legend) => {
// Custom toggle behavior
const index = legendItem.datasetIndex;
const chart = legend.chart;
const meta = chart.getDatasetMeta(index);
meta.hidden = !meta.hidden;
chart.update();
}
}
}
}
5.4 Adding Titles and Subtitles
options: {
plugins: {
title: {
display: true, // Default: false
text: 'Monthly Revenue',
font: { size: 18, weight: 'bold' },
padding: { top: 10, bottom: 20 },
position: 'top' // Default: 'top'
},
subtitle: {
display: true,
text: 'FY 2026 — All regions',
font: { size: 14 },
padding: { bottom: 10 }
}
}
}
The title plugin has weight 2000 and the subtitle has weight 1500, which determines their ordering in the layout.
5.5 Customizing Tooltips
options: {
plugins: {
tooltip: {
enabled: true,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleFont: { size: 14 },
bodyFont: { size: 12 },
padding: 12,
cornerRadius: 4,
callbacks: {
label: (context) => {
const label = context.dataset.label || '';
const value = context.parsed.y;
return `${label}: $${value.toLocaleString()}`;
}
}
}
}
}
5.6 Configuring Scales
Linear scale with grid styling:
options: {
scales: {
y: {
type: 'linear',
beginAtZero: true,
title: {
display: true,
text: 'Amount ($)'
},
grid: {
color: 'rgba(0, 0, 0, 0.1)',
drawBorder: false
},
ticks: {
callback: (value) => '$' + value.toLocaleString()
}
}
}
}
Logarithmic scale:
options: {
scales: {
y: {
type: 'logarithmic',
min: 1,
max: 10000
}
}
}
Multiple axes:
options: {
scales: {
y1: {
type: 'linear',
position: 'left',
title: { display: true, text: 'Revenue' }
},
y2: {
type: 'linear',
position: 'right',
title: { display: true, text: 'Users' },
grid: { drawOnChartArea: false }
}
}
}
Assign datasets to axes with yAxisID: 'y2'.
5.7 Configuring the Radial Scale
Radar and polar area charts use the radialLinear scale:
options: {
scales: {
r: {
beginAtZero: true,
max: 100,
ticks: {
stepSize: 20,
backdropColor: 'transparent'
},
pointLabels: {
font: { size: 14 }
},
angleLines: {
color: 'rgba(0, 0, 0, 0.1)'
},
startAngle: 0
}
}
}
5.8 Using Time Axes
The time scale requires an external date adapter. Install one alongside Chart.js:
npm install chartjs-adapter-date-fns date-fns
# or
npm install chartjs-adapter-luxon luxon
# or
npm install chartjs-adapter-moment moment
Then import the adapter before creating your chart:
import 'chartjs-adapter-date-fns';
// or
import 'chartjs-adapter-luxon';
new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Signups',
data: [
{ x: '2026-01-01', y: 100 },
{ x: '2026-02-01', y: 250 },
{ x: '2026-03-01', y: 180 },
{ x: '2026-04-01', y: 320 }
]
}]
},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'month',
displayFormats: {
month: 'MMM yyyy'
}
}
}
}
}
});
Caution
Without a date adapter installed and imported, time axes throw: "This method is not implemented: Check that a complete date adapter is provided." Chart.js does not bundle any date library — you must provide one.
Available time units: millisecond, second, minute, hour, day, week, month, quarter, year.
The timeseries scale type extends time and distributes data points evenly regardless of the time gaps between them.
5.9 Area Fills
Use the Filler plugin to fill the area between a dataset and a boundary:
datasets: [{
label: 'Temperature',
data: [15, 18, 22, 25, 21, 17],
fill: true, // Fill to the x-axis
backgroundColor: 'rgba(59, 130, 246, 0.2)',
borderColor: '#3b82f6'
}]
Fill targets:
| Value | Fills to |
|---|---|
true or 'origin' |
The origin (zero line) |
'start' |
The bottom of the chart area |
'end' |
The top of the chart area |
0, 1, 2... |
Dataset at that index |
'-1', '+1' |
Relative dataset index |
5.10 Data Decimation for Large Datasets
When plotting thousands of points, enable the Decimation plugin to reduce visual clutter and improve performance:
options: {
plugins: {
decimation: {
enabled: true,
algorithm: 'lttb', // 'min-max' (default) or 'lttb'
samples: 500 // Target number of points for LTTB
}
},
parsing: false, // Required: parsing must be disabled
scales: {
x: {
type: 'linear' // Must use linear or time x-axis
}
}
}
Important
Decimation only works with line charts that have a
linearortimex-axis andparsing: false. The data must be provided as{x, y}objects sorted by x value.
5.11 Custom Point Styles
The PointElement supports various built-in shapes:
datasets: [{
pointStyle: 'circle', // Default
// Other options: 'cross', 'crossRot', 'dash', 'line', 'rect',
// 'rectRounded', 'rectRot', 'star', 'triangle'
pointRadius: 5,
pointHoverRadius: 8,
pointBorderWidth: 2,
pointHitRadius: 10 // Invisible hit detection area
}]
You can also pass an Image object or a canvas element as pointStyle for custom icons.
5.12 Stacking Datasets
Enable stacking on the relevant scales:
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [
{ label: 'Product A', data: [10, 20, 30], stack: 'combined' },
{ label: 'Product B', data: [20, 10, 25], stack: 'combined' },
{ label: 'Target', data: [25, 25, 40], stack: 'target' }
]
},
options: {
scales: {
x: { stacked: true },
y: { stacked: true }
}
}
});
Use the stack property on datasets to create separate stacking groups on the same chart.
5.13 Handling Click Events
const chart = new Chart(ctx, config);
ctx.canvas.addEventListener('click', (event) => {
const elements = chart.getElementsAtEventForMode(
event, 'nearest', { intersect: true }, false
);
if (elements.length > 0) {
const { datasetIndex, index } = elements[0];
const label = chart.data.labels[index];
const value = chart.data.datasets[datasetIndex].data[index];
console.log(`Clicked: ${label} = ${value}`);
}
});
5.14 Writing Custom Plugins
Chart.js plugins hook into lifecycle events:
const customPlugin = {
id: 'customBackground',
beforeDraw: (chart) => {
const { ctx, chartArea: { top, bottom, left, right } } = chart;
ctx.save();
ctx.fillStyle = '#f8fafc';
ctx.fillRect(left, top, right - left, bottom - top);
ctx.restore();
}
};
new Chart(ctx, {
type: 'line',
data: { ... },
plugins: [customPlugin], // Register per-chart
options: { ... }
});
Key lifecycle hooks: beforeInit, afterInit, beforeUpdate, afterUpdate, beforeDraw, afterDraw, beforeDatasetsDraw, afterDatasetsDraw, beforeEvent, afterEvent, beforeDestroy.
5.15 Exporting Charts as Images
// Get a base64-encoded PNG
const imageUrl = chart.toBase64Image();
// Create a download link
const link = document.createElement('a');
link.download = 'chart.png';
link.href = chart.toBase64Image();
link.click();
6. Troubleshooting
6.1 Canvas Already in Use
Error:
Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID 'myChart' can be reused.
Cause: Creating a second chart on a canvas that already has an active chart instance.
Solution: Destroy the existing chart before creating a new one:
if (chart) {
chart.destroy();
}
chart = new Chart(ctx, newConfig);
Prevention: Store the chart instance in a variable accessible to your update/recreate logic. In frameworks like React or Vue, destroy the chart in your cleanup/unmount lifecycle method.
6.2 Missing Date Adapter
Error:
This method is not implemented: Check that a complete date adapter is provided.
Cause: Using a time or timeseries scale without installing and importing a date adapter.
Solution: Install a date adapter package and import it:
npm install chartjs-adapter-date-fns date-fns
import 'chartjs-adapter-date-fns';
Prevention: Always pair time-axis usage with a date adapter import at the top of your module.
6.3 Cannot Determine Axis Type
Error:
Cannot determine type of 'x' axis.
Cause: When using tree-shaken imports, the required scale was not registered.
Solution: Register the appropriate scale:
import { CategoryScale, LinearScale } from 'chart.js';
Chart.register(CategoryScale, LinearScale);
Prevention: When using tree-shaking, register all controllers, elements, and scales your chart needs. Use chart.js/auto during development, then switch to selective imports for production.
6.4 Unsupported Decimation Algorithm
Error:
Unsupported decimation algorithm '...'
Cause: Setting options.plugins.decimation.algorithm to a value other than 'min-max' or 'lttb'.
Solution: Use one of the two supported algorithms:
plugins: {
decimation: {
algorithm: 'min-max' // or 'lttb'
}
}
Prevention: Refer to the two valid values listed above.
6.5 Chart Does Not Resize
Cause: The canvas element is not inside a dedicated block-level container, or CSS constraints prevent the container from resizing.
Solution:
<!-- Wrap the canvas in a dedicated div -->
<div style="width: 100%; max-width: 800px;">
<canvas id="myChart"></canvas>
</div>
Ensure responsive: true is set (it is by default). Do not set explicit width and height attributes on the canvas when using responsive mode — let the container control sizing.
Prevention: Always wrap <canvas> in a dedicated container <div> and control dimensions on the container, not the canvas.
6.6 Chart Not Rendering (Blank Canvas)
Cause: Multiple possible reasons — zero-size container, missing registration, or data format mismatch.
Solution checklist:
- Verify the container has non-zero width and height
- Verify all required components are registered (when tree-shaking)
- Verify data format matches the chart type (e.g., bubble requires
{x, y, r}objects) - Check the browser console for errors
Prevention: Start with a working example from this guide and modify incrementally.
6.7 TypeError: Cannot Read Properties of Null
Error:
TypeError: Cannot read properties of null (reading 'getLabelAndValue')
Cause: A dataset references a scale or configuration that does not exist or has been misconfigured. This often happens with mixed chart types or when yAxisID points to a nonexistent scale.
Solution: Verify that every yAxisID and xAxisID in your datasets matches a key in options.scales:
datasets: [{
yAxisID: 'y2', // Must match a key in options.scales
data: [...]
}],
options: {
scales: {
y2: { type: 'linear', position: 'right' }
}
}
Prevention: Double-check axis ID strings for typos. Use TypeScript for compile-time checking of configuration shapes.
6.8 No Dataset Found at Index
Error:
No dataset found at index ...
Cause: Attempting to access a dataset by index that does not exist, typically from stale references after data updates.
Solution: Verify the dataset index is within bounds before accessing:
if (chart.data.datasets.length > datasetIndex) {
const dataset = chart.data.datasets[datasetIndex];
}
Prevention: When dynamically adding or removing datasets, always call chart.update() and refresh any cached indices.
6.9 Recursion Detected
Error:
Recursion detected: ...
Cause: A circular reference in the configuration — typically a scriptable option that triggers its own resolution.
Solution: Review scriptable option functions to ensure they do not read the same option they are computing. Avoid referencing ctx.element.options from within a scriptable option resolver for that same property.
Prevention: Keep scriptable option functions focused on reading from ctx.dataset.data, ctx.dataIndex, or ctx.parsed, not from resolved element options.
6.10 PolarArea Chart Shows Nothing When Data Is Zero
Cause: Polar area charts compute arc radius from the data value. When all values are zero, all arcs have zero radius and nothing is visible.
Solution: Filter out or replace zero values before charting, or set a min on the radial scale:
options: {
scales: {
r: { min: 0, suggestedMax: 10 }
}
}
Prevention: Validate incoming data and handle the all-zeros edge case in your application logic.
6.11 Doughnut Arc Spacing Looks Inconsistent
Cause: The spacing property on doughnut charts applies gaps between arcs, but the visual result can appear uneven with very small or very large segments.
Solution: Use spacingMode: 'angular' (the default) for consistent angular gaps, or try adjusting the spacing value. For very small segments, consider merging them into an "Other" category.
Prevention: Preview charts with representative data including edge cases (very small and very large segments) during development.
6.12 Animations Janky After Resize
Cause: The resize transition defaults to 0ms (instant). If your layout triggers frequent resizes during animation, the chart may appear to flicker.
Solution: Debounce container size changes, or set a resize transition duration:
options: {
transitions: {
resize: {
animation: { duration: 200 }
}
}
}
Prevention: Avoid CSS transitions on the chart container that continuously change its size.
7. FAQ
Q: What browsers does Chart.js support? A: Chart.js works in all modern browsers that support HTML5 Canvas and ES6. This includes current versions of Chrome, Firefox, Safari, and Edge. Internet Explorer is not supported.
Q: How do I use Chart.js with React / Vue / Angular?
A: Use a wrapper library (e.g., react-chartjs-2 for React, vue-chartjs for Vue). These handle component lifecycle (creation, updates, destruction) automatically. Alternatively, create the chart in a useEffect / onMounted hook and destroy it in the cleanup function.
Q: Can I have multiple charts on the same page?
A: Yes. Each chart needs its own <canvas> element. Create separate new Chart() instances for each canvas.
Q: How do I change a chart's type after creation? A: You cannot change a chart's type in place. Destroy the chart and create a new one with the desired type.
Q: How do I make the chart fill its container?
A: Ensure responsive: true (default) and maintainAspectRatio: false, then set the container's CSS width and height:
<div style="width: 100%; height: 400px;">
<canvas id="myChart"></canvas>
</div>
options: {
responsive: true,
maintainAspectRatio: false
}
Q: How do I format tick labels (currency, percentage, etc.)?
A: Use the ticks.callback function on the scale:
scales: {
y: {
ticks: {
callback: (value) => value + '%'
}
}
}
Q: How do I show data labels directly on the chart?
A: Chart.js does not include a built-in data label plugin. Use the community plugin chartjs-plugin-datalabels:
npm install chartjs-plugin-datalabels
Q: Can I animate dataset changes?
A: Yes. When you modify data and call chart.update(), Chart.js automatically animates the transition. Pass 'none' to skip animation: chart.update('none').
Q: Why is my chart blurry on high-DPI (Retina) screens? A: Chart.js handles device pixel ratio automatically. If your chart appears blurry, ensure you are not setting the canvas width/height via CSS (which stretches the bitmap). Let Chart.js and responsive mode handle sizing through the container.
Q: What is the difference between chart.js and chart.js/auto?
A: chart.js exports all components individually for tree-shaking — you import and register only what you need. chart.js/auto imports everything and registers it automatically, giving you the same behavior as the UMD bundle but as an ES module.
Q: How large is Chart.js?
A: The full UMD bundle (chart.umd.min.js) is approximately 200KB minified. With tree-shaking, a single chart type can be significantly smaller depending on which components you register.
Q: Can I use Chart.js without npm (plain HTML)?
A: Yes. Use the CDN link as shown in section 1.1. The UMD bundle works in a plain <script> tag with no build tools.
Q: How do I handle real-time / streaming data?
A: Push new data points to the dataset's data array and new labels to labels, then call chart.update(). To keep a sliding window, also shift old data off the front:
chart.data.labels.push(newLabel);
chart.data.datasets[0].data.push(newValue);
if (chart.data.labels.length > 50) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
Tip
For high-frequency streaming data (multiple updates per second), disable animations with
animation: falseand consider using the decimation plugin to keep rendering performant.
Q: Where can I find the full configuration reference? A: See the Admin Guide for full configuration reference. The official Chart.js documentation is also available at https://www.chartjs.org/docs/.