Introduction
Chart.js is a powerful, open-source JavaScript library that allows developers to create responsive, interactive data visualizations on the web. Using HTML5 Canvas, Chart.js renders performant charts that work across all modern browsers. It’s lightweight (around 11KB when minified and gzipped), highly customizable, and provides eight chart types out of the box while supporting plugin extensions for enhanced functionality. Chart.js is ideal for projects requiring clean, professional data visualizations with minimal development effort.
Core Concepts
Concept | Description |
---|
Chart | The main object that represents a visualization |
Canvas | HTML5 element that Chart.js uses for rendering |
Datasets | Collections of data points to be visualized |
Options | Configuration settings that control appearance and behavior |
Plugins | Extensions that add functionality to charts |
Scales | Axes configurations that control how data is mapped to the visual space |
Controllers | Components that define how specific chart types behave |
Animations | Configurations that control transitions and motion effects |
Setup and Installation
Using CDN
<!-- Chart.js 3.x -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Chart.js 4.x -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.js"></script>
Using npm
# Chart.js 3.x or 4.x
npm install chart.js
# Optional dependencies for specific formats
npm install papaparse # CSV parsing
npm install luxon # Advanced date handling
Importing in Modern JavaScript
// ES modules
import Chart from 'chart.js/auto';
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
// Common JS
const Chart = require('chart.js/auto');
Basic Setup
<div style="width: 500px;">
<canvas id="myChart"></canvas>
</div>
<script>
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: 'My First Dataset',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
Chart Types
Line Chart
const lineChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Monthly Sales',
data: [65, 59, 80, 81, 56, 55],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
}
});
Bar Chart
const barChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Monthly Sales',
data: [65, 59, 80, 81, 56, 55],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
]
}]
}
});
Pie Chart
const pieChart = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
label: 'Dataset 1',
data: [300, 50, 100],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)'
]
}]
}
});
Doughnut Chart
const doughnutChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
label: 'Dataset 1',
data: [300, 50, 100],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)'
]
}]
}
});
Radar Chart
const radarChart = new Chart(ctx, {
type: 'radar',
data: {
labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'],
datasets: [{
label: 'My First Dataset',
data: [65, 59, 90, 81, 56, 55, 40],
fill: true,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
pointBackgroundColor: 'rgb(255, 99, 132)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 99, 132)'
}]
}
});
Polar Area Chart
const polarAreaChart = new Chart(ctx, {
type: 'polarArea',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple'],
datasets: [{
label: 'My First Dataset',
data: [11, 16, 7, 3, 14],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(75, 192, 192)',
'rgb(255, 205, 86)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)'
]
}]
}
});
Scatter Chart
const scatterChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: 'Scatter Dataset',
data: [{x: -10, y: 0}, {x: 0, y: 10}, {x: 10, y: 5}, {x: 0.5, y: 5.5}],
backgroundColor: 'rgb(255, 99, 132)'
}]
},
options: {
scales: {
x: {
type: 'linear',
position: 'bottom'
}
}
}
});
Bubble Chart
const bubbleChart = new Chart(ctx, {
type: 'bubble',
data: {
datasets: [{
label: 'First Dataset',
data: [
{x: 20, y: 30, r: 15},
{x: 40, y: 10, r: 10},
{x: 15, y: 37, r: 5},
{x: 30, y: 25, r: 8}
],
backgroundColor: 'rgb(255, 99, 132)'
}]
}
});
Data Structure and Configuration
Dataset Structure by Chart Type
Chart Type | Data Format | Special Properties |
---|
Line/Bar | [value1, value2, ...] | tension , fill |
Scatter/Bubble | [{x: x1, y: y1}, {x: x2, y: y2}, ...] | r (radius for bubble) |
Radar | [value1, value2, ...] | Matches labels count |
Pie/Doughnut/PolarArea | [value1, value2, ...] | Individual segment colors |
Mixed Charts | Varies by dataset | Each dataset specifies its type |
Data Structure Example
const data = {
// X-axis labels (not used in all chart types)
labels: ['January', 'February', 'March', 'April'],
// Array of dataset objects
datasets: [
{
label: 'Dataset 1',
data: [10, 20, 30, 40],
// Visual styling
backgroundColor: 'rgba(255, 99, 132, 0.5)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
// Dataset-specific options
tension: 0.1,
// Can include custom properties
myCustomProperty: 'value'
},
{
label: 'Dataset 2',
data: [5, 15, 25, 35],
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}
]
};
Common Options Configuration
const options = {
// Responsive sizing
responsive: true,
maintainAspectRatio: false,
// Chart title
plugins: {
title: {
display: true,
text: 'Chart Title',
font: {
size: 18
}
},
// Tooltip configuration
tooltip: {
enabled: true,
mode: 'index',
intersect: false
},
// Legend configuration
legend: {
position: 'top',
labels: {
usePointStyle: true
}
}
},
// Axis configuration
scales: {
x: {
title: {
display: true,
text: 'Month'
},
grid: {
display: false
}
},
y: {
title: {
display: true,
text: 'Value'
},
beginAtZero: true,
ticks: {
callback: function(value) {
return '$' + value;
}
}
}
},
// Animation configuration
animation: {
duration: 1000,
easing: 'easeOutQuart'
},
// Events handling
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']
};
Scales and Axes Configuration
Common Scale Types
Scale Type | Usage | Key Options |
---|
linear | Default for number values | min , max , beginAtZero |
logarithmic | For exponential data | min , max |
category | For text labels | labels |
time | For date/time data | time.unit , time.displayFormats |
radialLinear | For radar charts | angleLines , pointLabels |
Time Scale Configuration
scales: {
x: {
type: 'time',
time: {
unit: 'month',
displayFormats: {
month: 'MMM YYYY'
},
tooltipFormat: 'll'
},
title: {
display: true,
text: 'Date'
}
}
}
Logarithmic Scale
scales: {
y: {
type: 'logarithmic',
min: 1,
max: 1000
}
}
Multiple Y Axes
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Y Axis 1'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Y Axis 2'
},
grid: {
drawOnChartArea: false
}
}
}
Customization and Styling
Colors and Styling
Single Color
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
Color Arrays
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)'
],
Dynamic Colors with Functions
backgroundColor: function(context) {
const index = context.dataIndex;
const value = context.dataset.data[index];
return value < 20 ? 'red' :
value < 50 ? 'yellow' : 'green';
}
Border and Point Styling
borderWidth: 2,
borderDash: [5, 5],
borderJoinStyle: 'round',
borderCapStyle: 'round',
pointRadius: 4,
pointHoverRadius: 6,
pointBackgroundColor: 'white',
pointBorderColor: 'rgba(255, 99, 132, 1)',
pointStyle: 'circle', // 'triangle', 'rect', 'star', etc.
Fonts and Text
plugins: {
title: {
font: {
family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
size: 18,
weight: 'bold',
lineHeight: 1.2
}
},
legend: {
labels: {
font: {
family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
size: 12
}
}
}
}
Animation and Interactivity
Basic Animation
animation: {
duration: 2000,
easing: 'easeOutBounce',
delay: function(context) {
return context.dataIndex * 100;
}
}
Animation Types
Animation | Usage |
---|
easeInOutBack | Smooth with slight overshoot |
easeOutQuart | Fast start, slow end |
easeInExpo | Very slow start, fast end |
easeOutBounce | Bounces at the end |
linear | Constant speed |
Custom Animations
const chart = new Chart(ctx, {
// chart configuration...
});
// Animate all bars at once
chart.data.datasets[0].data = [newData];
chart.update();
// Animate specific bar
chart.data.datasets[0].data[2] = newValue;
chart.update();
// Animate with custom duration
chart.update({
duration: 800,
easing: 'easeOutBounce'
});
Tooltips Configuration
plugins: {
tooltip: {
enabled: true,
mode: 'index', // 'point', 'nearest', 'dataset', 'x', 'y'
intersect: false,
position: 'nearest',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
titleFont: {
size: 16
},
bodyFont: {
size: 14
},
padding: 10,
caretSize: 5,
displayColors: true,
callbacks: {
title: function(tooltipItems) {
return 'Custom Title: ' + tooltipItems[0].label;
},
label: function(tooltipItem) {
return 'Value: $' + tooltipItem.formattedValue;
}
}
}
}
Events Handling
const chart = new Chart(ctx, {
// chart configuration...
options: {
onClick: function(event, elements) {
if (elements.length > 0) {
const clickedElement = elements[0];
console.log('Clicked on element at index:', clickedElement.index);
console.log('Dataset:', clickedElement.datasetIndex);
console.log('Value:', this.data.datasets[clickedElement.datasetIndex].data[clickedElement.index]);
}
},
onHover: function(event, elements) {
// Change cursor on hoverable elements
event.native.target.style.cursor = elements.length > 0 ? 'pointer' : 'default';
}
}
});
// Adding listeners externally
chart.canvas.addEventListener('click', function() {
// Custom click action
});
Responsive Behavior
Basic Responsive Options
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 2, // only used when maintainAspectRatio is true
// Specify container size when responsive is false
width: 500,
height: 300,
// Control responsiveness breakpoints
resizeDelay: 0 // ms to wait before resizing
}
Container Approach
<!-- Responsive container -->
<div style="position: relative; height:40vh; width:80vw">
<canvas id="myChart"></canvas>
</div>
<!-- Fixed size container -->
<div style="position: relative; width:500px; height:300px">
<canvas id="myChart"></canvas>
</div>
Media Queries Approach
const makeResponsive = () => {
if (window.innerWidth < 768) {
// Mobile configuration
chart.options.plugins.legend.position = 'bottom';
chart.options.scales.x.ticks.maxRotation = 90;
} else {
// Desktop configuration
chart.options.plugins.legend.position = 'top';
chart.options.scales.x.ticks.maxRotation = 0;
}
chart.update();
};
window.addEventListener('resize', makeResponsive);
makeResponsive(); // Initial call
Plugins and Extensions
Using Built-in Plugins
const chart = new Chart(ctx, {
options: {
plugins: {
// Configure built-in plugins
legend: { /* legend options */ },
title: { /* title options */ },
tooltip: { /* tooltip options */ },
subtitle: { /* subtitle options */ },
colors: { /* colors options */ }
}
}
});
Creating a Custom Plugin
const myCustomPlugin = {
id: 'myCustomPlugin',
beforeDraw: (chart, args, options) => {
const {ctx, chartArea: {top, bottom, left, right, width, height}} = chart;
ctx.save();
// Draw a background
ctx.fillStyle = options.backgroundColor || '#fcfcfc';
ctx.fillRect(left, top, width, height);
// Add watermark
if (options.watermark) {
ctx.globalAlpha = 0.1;
ctx.font = '30px Arial';
ctx.fillStyle = '#000000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(options.watermark, left + width / 2, top + height / 2);
ctx.globalAlpha = 1;
}
ctx.restore();
}
};
// Register the plugin globally
Chart.register(myCustomPlugin);
// Or use in a specific chart
const chart = new Chart(ctx, {
plugins: [myCustomPlugin],
options: {
plugins: {
myCustomPlugin: {
backgroundColor: '#f0f8ff',
watermark: 'DRAFT'
}
}
}
});
Popular External Plugins
Plugin | Purpose | Source |
---|
chartjs-plugin-datalabels | Displays data labels on your charts | github.com/chartjs/chartjs-plugin-datalabels |
chartjs-plugin-zoom | Zoom and pan functionality | github.com/chartjs/chartjs-plugin-zoom |
chartjs-plugin-annotation | Adds annotations to charts | github.com/chartjs/chartjs-plugin-annotation |
chartjs-plugin-colorschemes | Predefined color schemes | github.com/nagix/chartjs-plugin-colorschemes |
chartjs-plugin-streaming | Real-time streaming data | github.com/nagix/chartjs-plugin-streaming |
Advanced Techniques
Mixed Chart Types
const mixedChart = new Chart(ctx, {
type: 'bar', // Base type
data: {
labels: ['January', 'February', 'March', 'April'],
datasets: [
{
type: 'bar',
label: 'Bar Dataset',
data: [10, 20, 30, 40],
backgroundColor: 'rgba(255, 99, 132, 0.5)'
},
{
type: 'line',
label: 'Line Dataset',
data: [5, 15, 10, 30],
borderColor: 'rgb(54, 162, 235)',
fill: false
}
]
}
});
Working with Date and Time Data
// Include date adapter
import 'chartjs-adapter-date-fns';
const timeSeriesChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Stock Price',
data: [
{x: '2023-01-01', y: 100},
{x: '2023-02-01', y: 120},
{x: '2023-03-01', y: 110},
{x: '2023-04-01', y: 130}
],
borderColor: 'rgb(75, 192, 192)'
}]
},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'month',
displayFormats: {
month: 'MMM yyyy'
}
},
title: {
display: true,
text: 'Month'
}
}
}
}
});
Custom Drawing
const chart = new Chart(ctx, {
// chart configuration...
plugins: [{
id: 'customCanvasBackgroundColor',
beforeDraw: (chart) => {
const {ctx} = chart;
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'lightGreen';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}]
});
Dynamic Chart Updates
// Add new data
function addData(chart, label, data) {
chart.data.labels.push(label);
chart.data.datasets.forEach((dataset, i) => {
dataset.data.push(data[i]);
});
chart.update();
}
// Remove oldest data point
function removeData(chart) {
chart.data.labels.shift();
chart.data.datasets.forEach((dataset) => {
dataset.data.shift();
});
chart.update();
}
// Update specific values
function updateValue(chart, datasetIndex, index, newValue) {
chart.data.datasets[datasetIndex].data[index] = newValue;
chart.update();
}
// Reset entire chart
function resetChart(chart) {
chart.data.labels = [];
chart.data.datasets.forEach((dataset) => {
dataset.data = [];
});
chart.update();
}
// Live data simulation
setInterval(() => {
// Remove oldest data point
myChart.data.labels.shift();
myChart.data.datasets[0].data.shift();
// Add new data point
const newLabel = new Date().toLocaleTimeString();
const newData = Math.floor(Math.random() * 100);
myChart.data.labels.push(newLabel);
myChart.data.datasets[0].data.push(newData);
// Update with animation
myChart.update();
}, 1000);
Common Challenges and Solutions
Challenge | Solution |
---|
Chart doesn’t display | Check if canvas element exists before Chart instantiation; verify data format is correct |
Chart not responsive | Set responsive: true and ensure parent container has dimensions |
Tooltips not showing | Check plugins.tooltip.enabled is true; ensure data points are valid |
Y-axis doesn’t start at zero | Set scales.y.beginAtZero: true |
Legend position wrong | Configure with plugins.legend.position: 'top'/'left'/'right'/'bottom' |
Chart animation too fast/slow | Adjust animation.duration (milliseconds) |
Data labels overlapping | Use datalabels plugin with proper padding, rotation or display conditions |
Inconsistent colors | Define consistent color palette in datasets |
Line chart straight lines | Adjust tension property (0 = straight, 0.4 = curved) |
Chart performance issues | Limit number of data points; disable animations for large datasets |
Best Practices
Performance
- Limit number of data points displayed at once (use aggregation for large datasets)
- Disable animations for charts with more than 1000 data points
- Use simple tooltips for large datasets
- Consider disabling hover interactions for performance-critical applications
- Use requestAnimationFrame for smooth animations when updating dynamically
Accessibility
- Include proper alt text for canvas elements
- Use accessible color schemes with sufficient contrast
- Add aria attributes to the container
- Consider including a data table alternative
- Test with screen readers
Code Organization
- Separate chart configurations into their own files/functions
- Create reusable utility functions for common chart tasks
- Use consistent color schemes across your application
- Document custom plugins and configurations
- Implement proper error handling for data loading
Resources for Further Learning
Official Documentation
Additional Resources
Community