In the first four modules you drew charts pixel by pixel. You calculated bar widths, positioned axis labels, mapped data values to coordinates, and handled every rendering detail yourself. That is imperative charting — you tell the computer how to draw.
Now we flip the model. With Chart.js, you describe what you want — chart type, data, colors, options — and the library handles the math, the drawing, and even the user interactions. That is declarative charting.
<canvas>, just like Module 01. The difference: you describe the chart declaratively, and Chart.js handles the math, drawing, and interactions. Everything you learned about the Canvas coordinate system still applies — Chart.js simply automates the tedious parts.
The fastest way to get started is a single CDN script tag. No build tools, no npm install, no bundler configuration:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
This loads the latest Chart.js (v4.x as of writing) with all chart types and plugins registered automatically. For production, you may want to pin a specific version:
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
Every Chart.js chart follows the same two-step pattern:
<canvas> element to your HTMLnew Chart(ctx, config) in JavaScript<canvas id="myChart"></canvas>
<script>
const ctx = document.getElementById('myChart');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Home', 'About', 'Blog', 'Contact', 'Docs'],
datasets: [{
label: 'Pageviews',
data: [1200, 800, 950, 400, 1100],
backgroundColor: '#16a085'
}]
}
});
</script>
That is it. No fillRect() calls, no scale calculations, no axis drawing. Chart.js reads your config and does all the rendering.
The config you pass to new Chart() has three top-level keys:
{
type: 'bar', // Chart type
data: { // What to plot
labels: [...], // Category labels (x-axis for bar/line)
datasets: [...] // One or more data series
},
options: { // How it looks and behaves
scales: {...},
plugins: {...},
responsive: true // Default: true
}
}
| Key | Purpose | Examples |
|---|---|---|
type |
Which chart to draw | 'bar', 'line', 'doughnut', 'pie', 'radar', 'polarArea' |
data.labels |
Category labels | ['Jan', 'Feb', 'Mar'] |
data.datasets |
Array of data series | Each has label, data, backgroundColor, borderColor |
options |
Appearance and behavior | Scales, plugins (title, legend, tooltip), responsive settings |
Chart.js includes these built-in chart types:
| Type String | Chart | Best For |
|---|---|---|
'bar' |
Bar chart | Comparing categories (pageviews by page, errors by type) |
'line' |
Line chart | Time series (daily pageviews, performance over time) |
'doughnut' |
Doughnut chart | Part-of-whole (browser share, traffic sources) |
'pie' |
Pie chart | Same as doughnut (no center hole) |
'radar' |
Radar chart | Multi-axis comparison (performance scores) |
'polarArea' |
Polar area chart | Categories with magnitude (like radar, circular) |
'scatter' |
Scatter plot | Correlation between two variables |
'bubble' |
Bubble chart | Scatter with a third dimension (size) |
In this module we focus on the three most common for analytics dashboards: bar, line, and doughnut.
Each entry in the datasets array describes one data series:
datasets: [{
label: 'Pageviews', // Legend label
data: [1200, 800, 950, 400, 1100], // Values (same order as labels)
backgroundColor: '#16a085', // Fill color (bars, doughnut slices)
borderColor: '#0e7c6b', // Outline color (line stroke)
borderWidth: 2, // Outline thickness
fill: false // For line charts: fill area below?
}]
You can include multiple datasets to overlay series. For example, this month vs. last month:
datasets: [
{
label: 'This Month',
data: [1200, 800, 950, 400, 1100],
backgroundColor: '#16a085'
},
{
label: 'Last Month',
data: [1000, 750, 900, 350, 1050],
backgroundColor: '#bdc3c7'
}
]
The options.scales object controls axes. Each axis has an ID (x and y by default):
options: {
scales: {
y: {
beginAtZero: true, // Don't let y-axis start at a non-zero value
title: {
display: true,
text: 'Pageviews' // Axis label
},
ticks: {
callback: function(value) {
return value.toLocaleString(); // Format numbers
}
}
},
x: {
title: {
display: true,
text: 'Page'
}
}
}
}
beginAtZero: true anchors the axis at zero, giving an honest visual representation.
Chart.js plugins control titles, legends, and tooltips:
options: {
plugins: {
title: {
display: true,
text: 'Pageviews by Page',
font: { size: 18 }
},
legend: {
position: 'bottom' // 'top', 'bottom', 'left', 'right'
},
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' +
context.parsed.y.toLocaleString();
}
}
}
}
}
By default, responsive: true is already set. Chart.js watches the canvas container's size and redraws when it changes. Two key options:
| Option | Default | Effect |
|---|---|---|
responsive |
true |
Chart resizes with its container |
maintainAspectRatio |
true |
Keeps the original width/height ratio. Set to false to let the chart fill its container height. |
A common pattern for dashboard panels: wrap the canvas in a fixed-height container and set maintainAspectRatio: false:
<div style="height: 300px;">
<canvas id="dashChart"></canvas>
</div>
<script>
new Chart(document.getElementById('dashChart'), {
type: 'bar',
data: { /* ... */ },
options: {
maintainAspectRatio: false
}
});
</script>
The bar chart maps categories to bar heights. This is the most common chart in analytics dashboards.
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Home', 'About', 'Blog', 'Contact', 'Docs'],
datasets: [{
label: 'Pageviews',
data: [1200, 800, 950, 400, 1100],
backgroundColor: [
'#16a085', '#1abc9c', '#2ecc71', '#27ae60', '#0e7c6b'
]
}]
},
options: {
scales: { y: { beginAtZero: true } }
}
});
The line chart shows values changing over time. Use fill: true for an area chart:
new Chart(ctx, {
type: 'line',
data: {
labels: ['Day 1', 'Day 2', 'Day 3', /* ... */],
datasets: [{
label: 'Daily Pageviews',
data: [450, 520, 480, /* ... */],
borderColor: '#16a085',
backgroundColor: 'rgba(22, 160, 133, 0.1)',
fill: true,
tension: 0.3, // Smooth curves
pointRadius: 4,
pointBackgroundColor: '#16a085'
}]
}
});
See the live line chart demo →
The doughnut chart shows proportions of a whole. Each slice gets its own color:
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Chrome', 'Safari', 'Firefox', 'Edge', 'Other'],
datasets: [{
data: [59, 24, 10, 5, 2],
backgroundColor: [
'#4285F4', '#34A853', '#FF6D01', '#0078D4', '#999'
]
}]
},
options: {
plugins: {
legend: { position: 'bottom' }
}
}
});
See the live doughnut chart demo →
The comparison demo renders the exact same bar chart two ways:
The visual result is nearly identical. The developer writes less code with Chart.js. But the user downloads roughly 47 times more bytes.
See the full side-by-side comparison →
There are also structural costs beyond bytes:
Here is a complete, self-contained HTML file that creates a bar chart:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chart.js Bar Chart</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div style="max-width: 700px; margin: 40px auto;">
<canvas id="myChart"></canvas>
</div>
<script>
new Chart(document.getElementById('myChart'), {
type: 'bar',
data: {
labels: ['Home', 'About', 'Blog', 'Contact', 'Docs'],
datasets: [{
label: 'Pageviews',
data: [1200, 800, 950, 400, 1100],
backgroundColor: '#16a085'
}]
},
options: {
scales: {
y: { beginAtZero: true }
},
plugins: {
title: {
display: true,
text: 'Pageviews by Page'
}
}
}
});
</script>
</body>
</html>
Copy that into a file, open it in a browser, and you have a working chart. No server needed.
type: 'bar' to type: 'line' and the same data renders differently.<canvas> — your Module 01 knowledge still applies.