Managing Real-Time Data Streams in Chart.js without UI Lag
Learn how to implement high-performance real-time updates in Chart.js using the update() method and sliding window logic to avoid UI lag and memory leaks.
05 Sept 2026, 13:20 UTC

The Performance Gap in Live Dashboards
The most common mistake when building real-time dashboards with Chart.js is destroying and recreating the chart instance every time a new data point arrives. This approach causes a visible flicker, resets the user's zoom or hover state, and puts unnecessary pressure on the browser's garbage collector.
The efficient alternative is direct mutation of the data object followed by a call to the update() method. This allows Chart.js to calculate the difference between the current state and the new state, animating the transition smoothly rather than redrawing the entire canvas from scratch.
Implementing the Sliding Window Effect
For time-series data, showing an infinite line is impractical. You need a \"sliding window\"—a fixed number of data points where the oldest value is removed as the newest is added. This keeps the X-axis stable and the memory footprint constant.
To achieve this, you must modify both the labels array and the datasets[n].data array. If you only update the data values without updating the labels, the chart will either stop advancing or create a mismatch between the data point and its axis label.
Worked Example: Real-Time Metric Monitor
This example assumes you are using Chart.js v4.x. The logic should run in your client-side JavaScript file. Ensure you have a <canvas id=\"realtimeChart\"> element in your HTML.
// Initialize chart with empty data
const ctx = document.getElementById('realtimeChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'System CPU Load',
data: [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
scales: {
y: { beginAtZero: true, max: 100 }
}
}
});
// Function to simulate receiving a data point from a WebSocket or API
function addDataPoint(timestamp, value) {
const MAX_POINTS = 20;
// 1. Add new label and data point
myChart.data.labels.push(timestamp);
myChart.data.datasets[0].data.push(value);
// 2. Maintain sliding window: remove oldest if limit is reached
if (myChart.data.labels.length > MAX_POINTS) {
myChart.data.labels.shift();
myChart.data.datasets[0].data.shift();
}
// 3. Trigger the render update
// Using 'none' mode disables animation for high-frequency updates
myChart.update('none');
}
// Simulation: Update every 1 second
setInterval(() => {
const now = new Date().toLocaleTimeString();
const randomValue = Math.floor(Math.random() * 100);
addDataPoint(now, randomValue);
}, 1000);
Execution Details
- Permissions: Standard client-side JS execution.
- Placeholders: Replace
MAX_POINTSwith your desired window size. - Risk: If
update()is called faster than the browser's refresh rate (typically 60Hz), the main thread will block, causing the rest of the page to freeze.
Balancing Animation and Performance
Chart.js provides different modes for the update() method that significantly impact CPU usage:
| Update Mode | Visual Result | Performance Cost | Best Use Case |
|---|---|---|---|
myChart.update() |
Smooth transition | Moderate | Updates every 2+ seconds |
myChart.update('none') |
Instant jump | Low | Updates every 100-500ms |
myChart.update('active') |
Updates active elements | Lowest | Hover/Tooltip changes |
Limitations and Verification
One critical limitation is that update() still requires a full re-calculation of the scale. If you are plotting tens of thousands of points in a single dataset, even update('none') will eventually stutter. For massive datasets, consider using a specialized plugin like chartjs-plugin-streaming or switching to a WebGL-based library.
How to verify the result: Open your browser's DevTools Performance tab. Record a 5-second slice of your chart updating. If you see long yellow bars (Scripting) exceeding 16ms per frame, you are blocking the main thread. Switch your update mode to 'none' or increase the setInterval delay to resolve the lag.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.