The same bar chart, the same data, rendered two ways. The left panel draws every pixel imperatively. The right panel describes the chart declaratively and lets Chart.js do the work. Both produce a nearly identical visual result.
Line counts measure developer effort. But the user does not download your source code — the user downloads every byte the browser needs to render the page. That is the number that matters for load time, mobile data budgets, and performance.
Your ~40 lines of JavaScript. No dependencies. Nothing else to download.
Your ~15 lines of config + the Chart.js library (~68 KB gzipped, ~200 KB uncompressed and parsed).
Transfer size comparison (gzipped). The red bar is the vanilla Canvas approach. The teal bar is Chart.js.
| Factor | Vanilla Canvas | Chart.js |
|---|---|---|
| Developer code | ~40 lines | ~15 lines |
| Transfer size (gzipped) | ~1.5 KB | ~70 KB |
| Parse/compile cost | Negligible | ~200 KB of JavaScript the browser must parse |
| Dependencies | None | chart.js (CDN or bundled) — version pinning, update risk, supply chain trust |
| CDN availability | Not needed | If jsdelivr.net is unreachable, the chart does not render |
| Caching | N/A | Cached after first load if CDN URL is stable |
| Built-in tooltips, legend, resize | You write them yourself (more code) | Included (free) |
| Time to interactive | Immediate | Blocked on library download + parse |
| Control & customization ceiling | Total — you own every pixel | Limited to what the library exposes |
| Long-term maintenance | Your code, your responsibility — but no upstream breakage | Library updates may introduce breaking changes (Chart.js v2→v3→v4 broke configs) |
Developer convenience is real and valuable. But it has a cost, and that cost is paid by every user on every visit. The right choice depends on what you are building:
| Scenario | Better Choice | Why |
|---|---|---|
| 1–2 simple charts on a page | Vanilla Canvas | ~1.5 KB vs. ~70 KB. The library overhead dwarfs the chart code. |
| Dashboard with 5+ charts, tooltips, legends, date pickers | Chart.js | You would end up writing thousands of lines of Canvas code. The library cost amortizes across many charts. |
| Performance-critical page (landing page, mobile-first) | Vanilla Canvas (or server-side image) | Every KB matters. A static chart image from Module 04 costs zero JS. |
| Internal admin tool with known fast connections | Chart.js | Users are on office networks. Development speed matters more than transfer size. |
| Custom visualization that no library provides | D3 or vanilla Canvas/SVG | Libraries constrain you to their chart types. Custom work requires custom code. |
const canvas = document.getElementById('imperativeChart');
const ctx = canvas.getContext('2d');
const data = [
{ page: 'Home', views: 1200 },
{ page: 'About', views: 800 },
{ page: 'Blog', views: 950 },
{ page: 'Contact', views: 400 },
{ page: 'Docs', views: 1100 }
];
const W = canvas.width;
const H = canvas.height;
const padding = { top: 40, right: 20, bottom: 50, left: 60 };
const chartW = W - padding.left - padding.right;
const chartH = H - padding.top - padding.bottom;
const maxVal = Math.max(...data.map(d => d.views));
const barW = chartW / data.length * 0.7;
const gap = chartW / data.length * 0.3;
// Title
ctx.fillStyle = '#333';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Pageviews by Page', W / 2, 25);
// Y-axis gridlines and labels
ctx.font = '12px sans-serif';
ctx.textAlign = 'right';
for (let i = 0; i <= 4; i++) {
const val = (maxVal / 4) * i;
const y = padding.top + chartH - (val / maxVal) * chartH;
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(W - padding.right, y);
ctx.stroke();
ctx.fillStyle = '#666';
ctx.fillText(Math.round(val).toLocaleString(), padding.left - 8, y + 4);
}
// Bars
const colors = ['#16a085','#1abc9c','#2ecc71','#27ae60','#0e7c6b'];
data.forEach((d, i) => {
const barH = (d.views / maxVal) * chartH;
const x = padding.left + i * (barW + gap) + gap / 2;
const y = padding.top + chartH - barH;
ctx.fillStyle = colors[i];
ctx.fillRect(x, y, barW, barH);
// X-axis label
ctx.fillStyle = '#333';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(d.page, x + barW / 2, padding.top + chartH + 20);
});
new Chart(document.getElementById('declarativeChart'), {
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: {
plugins: {
title: {
display: true,
text: 'Pageviews by Page',
font: { size: 16 }
},
legend: { display: false }
},
scales: {
y: { beginAtZero: true }
}
}
});
fillRect() callsWhat the user DID download: ~68 KB (gzipped) of library code to make those 15 lines work.