Module 05: Hello Chart.js — Your First Declarative Chart

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 under the hood. Chart.js renders to <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.

1. Including Chart.js

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>

2. Creating a Chart Instance

Every Chart.js chart follows the same two-step pattern:

  1. Add a <canvas> element to your HTML
  2. Create a new 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.

3. The Config Object Anatomy

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

4. Chart Types

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.

5. Datasets in Detail

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'
    }
]

6. Options: Scales

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 matters. If your data ranges from 380 to 420, Chart.js might auto-scale the y-axis to that narrow range, exaggerating differences. Setting beginAtZero: true anchors the axis at zero, giving an honest visual representation.

7. Options: Plugins

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();
                }
            }
        }
    }
}

8. Responsive Behavior

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>

9. The Three Core Charts

Bar Chart

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 } }
    }
});

See the live bar chart demo →

Line Chart

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 →

Doughnut Chart

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 →

10. Declarative vs. Imperative: The Full Tradeoff

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 →

Lines of code is a developer metric, not a user metric. Every dependency you add to a page is a tradeoff: the developer writes less code, but the user downloads more bytes, waits longer for the page to become interactive, and depends on a third-party CDN being available. For a single bar chart, ~70 KB of library overhead is hard to justify. For a dashboard with ten charts, tooltips, legends, and responsive behavior, the library earns its weight. Always count the bytes the user actually receives, not just the lines you wrote.

There are also structural costs beyond bytes:

When is the tradeoff worth it? For a dashboard with 5+ charts, interactive tooltips, legends, date-range filtering, and responsive layout, Chart.js saves thousands of lines of hand-written code — the library overhead is amortized across many charts and features. For a landing page with one or two simple charts, vanilla Canvas (or a server-rendered image from Module 04) delivers the same visual at a fraction of the cost. Choose based on what the user needs, not just what the developer prefers.

11. Full Working Example

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.

12. Key Takeaways

Demo Files in This Module