Handling High-Density Time Series in Chart.js with Data Decimation
Stop lagging dashboards. Learn how to use Chart.js decimation plugins to render massive time-series datasets efficiently without losing the visual shape of your data.
26 Aug 2026, 19:59 UTC

The Browser Rendering Bottleneck
When visualizing time-series data—such as server metrics or sensor logs—it is tempting to pass every single data point to the browser. However, rendering 50,000 points on a canvas that is only 1,000 pixels wide is an inefficient use of resources. The browser spends CPU cycles drawing points that overlap on the same pixel, leading to sluggish interactions, frozen tooltips, and high memory usage.
The solution is decimation: the process of reducing the number of points drawn on the screen while preserving the visual shape and trends of the dataset. Chart.js provides a built-in decimation plugin specifically for this purpose, allowing you to maintain a responsive dashboard without sacrificing the "look" of your data.
How Decimation Works
The decimation plugin intercepts the data before it hits the rendering engine. Instead of drawing every point, it samples the dataset based on a target number of samples. This is not a simple "every nth point" skip; it uses algorithms to ensure that peaks and valleys (the most important parts of a technical graph) remain visible.
Choosing an Algorithm
- LTTB (Largest Triangle Three Buckets): The gold standard for time-series. It uses geometric area to select points that best represent the visual shape of the original line. Use this when visual fidelity is the priority.
- Min-Max: Preserves the absolute minimum and maximum values within each bucket. This is critical for monitoring systems where a single spike (an outlier) must be visible even if it lasts for only one millisecond.
- Average: Smooths the data by averaging points in a bucket. This is useful for removing noise but can hide critical spikes.
Implementation and Configuration
To make decimation work, Chart.js requires specific data structures. It only functions when the x-axis is a linear or time scale and the data is provided as an array of objects (e.g., {x: number, y: number}). To maximize performance, you should also disable parsing, which tells Chart.js that the data is already in the format it needs.
Run the following configuration in your chart initialization script. Ensure you are using Chart.js v3 or v4, as plugin registration varies by version.
const config = {
type: 'line',
data: {
datasets: [{
label: 'System Load',
data: largeDataSet, // Array of {x, y} objects
parsing: false, // Critical for performance with decimation
}]
},
options: {
scales: {
x: { type: 'linear' } // Decimation requires linear or time scales
},
plugins: {
decimation: {
enabled: true,
algorithm: 'lttb', // Options: 'lttb', 'min-max'
samples: 500, // Target number of points to render
}
}
}
};
Engineering Trade-offs
Decimation is a performance optimization, not a magic bullet. It introduces a trade-off between rendering speed and data precision.
| Feature | Full Dataset | Decimated Dataset |
|---|---|---|
| Interaction Speed | Slow/Laggy | Fast/Fluid |
| Tooltip Accuracy | Exact value of every point | Value of the sampled point |
| Visual Fidelity | Perfect | Approximate (Algorithm dependent) |
The most significant limitation is the impact on tooltips. Because the plugin removes points from the render loop, the user cannot hover over a "missing" point to see its exact value. If your application requires an audit-level inspection of every single data point, you should implement a "zoom-to-detail" pattern where decimation is disabled or the sample rate is increased as the user zooms in on a specific time range.
Verifying the Result
To verify that decimation is actually working and not just failing silently, you can inspect the internal dataset metadata. Run this command in your browser console after the chart has rendered:
// Replace 'myChart' with your Chart instance variable
const meta = myChart.getDatasetMeta(0);
console.log(`Points rendered: ${meta.data.length}`);
If the number of rendered points matches your samples configuration (e.g., 500) rather than the length of your raw input array (e.g., 50,000), the plugin is operating correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.