Building a Responsive Chart.js 4.x Line Chart with Dynamic Point Radius and Click Handling
Step‑by‑step guide to create a Chart.js 4.x line chart that animates point radius, resizes responsively, and captures click events, plus a verification checklist and common pitfalls.
19 Feb 2026, 20:52 UTC

Problem & Takeaway
You need a line chart that animates each point’s radius based on its value, resizes cleanly when the browser window changes, and lets you react to clicks on individual data points. Chart.js 4.x supports all three, but the APIs differ from v3 and a few CSS quirks can break responsiveness. This guide walks through a minimal, production‑ready implementation and a verification checklist you can run in any modern browser.
Prerequisites
- Node ≥ 18 (or any environment that can serve static files).
- Chart.js 4.x installed:
npm i chart.js@^4(run in your project root, requires write permission tonode_modules). - A basic HTML page that loads the Chart.js bundle via a script tag or ES‑module import.
Step 1: HTML Canvas
Give the canvas an explicit ID and wrap it in a block‑level container with a defined width/height. Inline or zero‑size containers are the most common cause of broken responsiveness.
<div id="chart-container" style="width:100%; max-width:800px; height:400px;">
<canvas id="myLineChart"></canvas>
</div>
Run a quick sanity check: open the page, open DevTools → Console, and confirm no 404 for chart.js.
Step 2: Chart Configuration
Create the Chart instance after the DOM is ready. The configuration object holds data, options, and the animation overrides.
// chart-init.js
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
const ctx = document.getElementById('myLineChart').getContext('2d');
const data = {
labels: ['Jan','Feb','Mar','Apr','May','Jun'],
datasets: [{
label: 'Revenue',
data: [12, 19, 3, 5, 2, 3],
// metadata used by the radius function
meta: [10, 20, 5, 8, 3, 7],
borderColor: 'rgb(75, 192, 192)',
tension: 0.3,
fill: false
}]
};
const options = {
responsive: true,
maintainAspectRatio: true,
animation: {
// animate pointRadius per frame
duration: 800,
easing: 'easeOutQuart'
},
plugins: {
tooltip: {
callbacks: {
label: ctx => `${ctx.dataset.label}: ${ctx.raw} (meta: ${ctx.dataset.meta[ctx.dataIndex]})`
}
}
}
};
const chart = new Chart(ctx, { type: 'line', data, options });
// expose for click handler
export { chart };
Place this script as a module (<script type="module" src="chart-init.js"></script>) so the chart instance can be imported elsewhere.
Step 3: Dynamic Point Radius Animation
Chart.js lets you supply a function for pointRadius that receives the dataset and the data index. The function runs on every animation frame, giving a smooth radius transition.
// add to the dataset object in chart-init.js
pointRadius: (ctx) => {
const meta = ctx.dataset.meta[ctx.dataIndex];
// scale meta (0‑20) to a radius range 4‑12
return 4 + (meta / 20) * 8;
},
pointHoverRadius: (ctx) => {
const base = 4 + (ctx.dataset.meta[ctx.dataIndex] / 20) * 8;
return base + 4; // enlarge on hover
}
Validation: Reload the page, hover a point, and watch the radius grow/shrink in the console‑logged animation frames (add console.log(ctx) temporarily if you need to see the values).
Step 4: Responsive Resizing
Responsive mode is on by default, but it only works when the canvas’s parent has a concrete size. The container in Step 1 uses width:100% and a fixed height, which satisfies the requirement. If you need a fluid height, set the container’s height via CSS (e.g., aspect-ratio: 2 / 1) and keep maintainAspectRatio: true.
Diagnostic: Resize the browser window. The chart should redraw without distortion. If it collapses to 0 × 0, inspect the container’s computed width/height in DevTools → Elements → Styles.
Step 5: Click Event Handling
Attach a click listener to the canvas, then use chart.getElementsAtEventForMode (Chart.js 4.x) to map the mouse position to data elements.
// click-handler.js
import { chart } from './chart-init.js';
const canvas = document.getElementById('myLineChart');
canvas.addEventListener('click', (evt) => {
const points = chart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, false);
if (points.length) {
const idx = points[0].index;
const value = chart.data.datasets[0].data[idx];
const meta = chart.data.datasets[0].meta[idx];
console.log('Clicked point', { index: idx, value, meta });
// → integrate with your app (e.g., open a detail view)
}
});
Import this module after chart-init.js (or place it in the same file after the chart variable is created).
Verification Checklist
- Chart renders – open the page, confirm a line with six points appears.
- Radius animation – hover each point; radius should reflect the
metavalues (larger meta → larger radius). - Responsive resize – drag the window edge; chart width/height adjust while preserving aspect ratio.
- Click handling – click a point; console logs an object with
index,value, andmeta. - Cross‑browser – repeat steps 1‑4 in Chrome, Firefox, Safari, Edge. No console errors should appear.
Common Pitfalls & Recovery
| Symptom | Root Cause | Fix |
|---|---|---|
Chart throws TypeError: Cannot read property 'meta' of undefined | Dataset missing meta array or length mismatch. | Validate data.datasets[0].meta length equals data.labels length before creating the chart. |
| Chart stays 0 × 0 after resize | Container has display:inline or no explicit dimensions. | Force block layout: display:block or give the container a CSS width/height. |
| Click listener returns empty array | Listener attached before chart finishes first render. | Attach listener after chart.render() resolves (Chart.js 4 returns a promise) or defer via requestAnimationFrame. |
| Animation stutters on low‑end devices | Large duration or complex radius function. | Reduce animation.duration to 400 ms or simplify the radius calculation. |
Limitations
- The dynamic radius function only has access to the current dataset; cross‑dataset comparisons require a plugin.
- Responsive behavior relies on CSS layout; frameworks that manipulate container size via JavaScript (e.g., React portals) may need a manual
chart.resize()call. - Click detection uses
intersect: true; points with very small radius may be hard to hit. AdjustpointHitRadiusin the dataset if needed.
Final Check
Run the verification checklist in each target browser. If all five items pass, the chart is ready for production. Keep the chart-init.js and click-handler.js modules version‑controlled; any future Chart.js upgrade (e.g., 5.x) will only require updating the import path and checking the migration guide for breaking changes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.