Optimizing Line Chart Performance with LTTB Algorithm
When working with large datasets, rendering all points in a line chart can cause significant performance issues. For example, plotting 50,000 data points directly can overwhelm the browser and make the chart unresponsive. Tools like amCharts and ApexCharts struggle with such datasets, while ECharts performs better but still isn't optimized for extremely large datasets. To address this, I combined ECharts with the Largest Triangle Three Buckets (LTTB) algorithm. LTTB is a downsampling algorithm that reduces data points while preserving the visual structure of the dataset. In this post, I’ll explain how the LTTB algorithm works step by step with an example of reducing 10 points to 4. For each step, I'll include the relevant code, explanations, and resulting data transformations. LTTB Algorithm Code Here’s the full code for the LTTB algorithm: export function lttb(data: Point[], threshold: number): Point[] { const dataLength = data.length; if (threshold >= dataLength || threshold === 0) { return data; // No downsampling needed } const sampled: Point[] = []; let sampledIndex = 0; const bucketSize = (dataLength - 2) / (threshold - 2); let a = 0; // Start point let nextA = 0; sampled[sampledIndex++] = data[a]; // Add the first point for (let i = 0; i
When working with large datasets, rendering all points in a line chart can cause significant performance issues. For example, plotting 50,000 data points directly can overwhelm the browser and make the chart unresponsive. Tools like amCharts and ApexCharts struggle with such datasets, while ECharts performs better but still isn't optimized for extremely large datasets.
To address this, I combined ECharts with the Largest Triangle Three Buckets (LTTB) algorithm. LTTB is a downsampling algorithm that reduces data points while preserving the visual structure of the dataset.
In this post, I’ll explain how the LTTB algorithm works step by step with an example of reducing 10 points to 4. For each step, I'll include the relevant code, explanations, and resulting data transformations.
LTTB Algorithm Code
Here’s the full code for the LTTB algorithm:
export function lttb(data: Point[], threshold: number): Point[] {
const dataLength = data.length;
if (threshold >= dataLength || threshold === 0) {
return data; // No downsampling needed
}
const sampled: Point[] = [];
let sampledIndex = 0;
const bucketSize = (dataLength - 2) / (threshold - 2);
let a = 0; // Start point
let nextA = 0;
sampled[sampledIndex++] = data[a]; // Add the first point
for (let i = 0; i < threshold - 2; i++) {
let avgX = 0;
let avgY = 0;
let avgRangeStart = Math.floor((i + 1) * bucketSize) + 1;
let avgRangeEnd = Math.floor((i + 2) * bucketSize) + 1;
avgRangeEnd = avgRangeEnd < dataLength ? avgRangeEnd : dataLength;
const avgRangeLength = avgRangeEnd - avgRangeStart;
for (; avgRangeStart < avgRangeEnd; avgRangeStart++) {
avgX += data[avgRangeStart].x;
avgY += data[avgRangeStart].y;
}
avgX /= avgRangeLength;
avgY /= avgRangeLength;
let rangeOffs = Math.floor((i + 0) * bucketSize) + 1;
const rangeTo = Math.floor((i + 1) * bucketSize) + 1;
const pointAX = data[a].x;
const pointAY = data[a].y;
let maxArea = -1;
for (; rangeOffs < rangeTo; rangeOffs++) {
const area = Math.abs(
(pointAX - avgX) * (data[rangeOffs].y - pointAY) -
(pointAX - data[rangeOffs].x) * (avgY - pointAY)
);
if (area > maxArea) {
maxArea = area;
nextA = rangeOffs;
}
}
sampled[sampledIndex++] = data[nextA]; // Add the most important point
a = nextA;
}
sampled[sampledIndex++] = data[dataLength - 1]; // Add the last point
return sampled;
}
Example: Reducing 10 Points to 4 Points
We’ll reduce the following dataset of 10 points to 4 points using the LTTB algorithm:
Original Dataset
const data = [
{ x: 1, y: 10 },
{ x: 2, y: 20 },
{ x: 3, y: 15 },
{ x: 4, y: 25 },
{ x: 5, y: 30 },
{ x: 6, y: 20 },
{ x: 7, y: 40 },
{ x: 8, y: 35 },
{ x: 9, y: 45 },
{ x: 10, y: 50 }
];
const threshold = 4; // Reduce to 4 points
Step 1: Add the First Point
sampled[sampledIndex++] = data[a]; // Add the first point
✅ The algorithm always starts by adding the first data point. This ensures that the downsampled dataset begins correctly.