Skip to main content
Kinetix Charts is designed to handle large datasets smoothly through intelligent downsampling and efficient canvas-based rendering. You can work with datasets containing tens of thousands or even hundreds of thousands of points without performance degradation.

LTTB downsampling

The library uses the Largest-Triangle-Three-Buckets (LTTB) algorithm to automatically downsample large datasets while preserving their visual shape and important features.

Automatic activation

LTTB downsampling automatically activates when a line series has more than 2,000 data points. This threshold balances visual fidelity with rendering performance.
import { Chart, LineSeries } from 'kinetix-charts';

// Generate 100,000 data points
const largeData = [];
for (let i = 0; i < 100000; i++) {
  largeData.push({ x: i, y: Math.sin(i / 1000) * 50 + 50 });
}

const series = new LineSeries(container, 1);
series.setData(largeData);  // LTTB automatically kicks in
chart.addSeries(series);
The LTTB algorithm is implemented in src/math/LTTB.ts:6 and intelligently selects which points to keep based on triangle areas formed between consecutive data buckets.

How LTTB preserves visual shape

The LTTB algorithm works by:
  1. Always keeping the first and last points to preserve data boundaries
  2. Dividing data into buckets based on the target threshold (default 2,000 points)
  3. Selecting the most visually significant point from each bucket by calculating triangle areas
  4. Preserving peaks, valleys, and trends that would be visible in the full dataset
1

Divide into buckets

The algorithm divides your data into buckets. For 100,000 points downsampled to 2,000, each bucket contains approximately 50 points.
2

Calculate triangle areas

For each bucket, it calculates the area of triangles formed between:
  • The previously selected point
  • Each candidate point in the current bucket
  • The average position of points in the next bucket
3

Select maximum area point

The point that forms the largest triangle is selected, as it represents the most significant visual deviation.
// From src/math/LTTB.ts:59-63
area = Math.abs(
  (pointAX - avgX) * (data[rangeOffs].y - pointAY) -
    (pointAX - data[rangeOffs].x) * (avgY - pointAY)
);
area *= 0.5;

Performance characteristics

Canvas rendering benefits

Kinetix Charts uses HTML5 Canvas for all rendering, which provides significant performance advantages over DOM-based charting libraries:
  • Direct pixel manipulation instead of creating thousands of DOM elements
  • Hardware acceleration for smooth rendering and animations
  • Efficient redraws that only update what’s necessary
  • Layered architecture with separate canvases for different components
// Creating 100k SVG elements
{
  data.map(point => <circle cx={point.x} cy={point.y} />)
}
// Result: Browser struggles with 100k DOM nodes

Memory efficiency

The canvas-based approach combined with LTTB downsampling means:
  • Original data is stored in memory but not all rendered
  • Only ~2,000 points are processed for rendering (after downsampling)
  • Layer system reuses canvas contexts instead of creating new ones
  • Tooltips and interactions still work with the full dataset

Complete 100k point example

Here’s a complete example from the README demonstrating smooth performance with 100,000 points:
import { Chart, LineSeries } from 'kinetix-charts';

const container = document.getElementById('chart');
const chart = new Chart(container, {
  theme: 'dark'
});

// Works smoothly with 100k+ points
const largeData = [];
for (let i = 0; i < 100000; i++) {
  largeData.push({ x: i, y: Math.sin(i / 1000) * 50 + 50 });
}

const series = new LineSeries(container, 1);
series.setScales(chart.xScale, chart.yScale);
series.color = '#6366f1';
series.name = 'Large Dataset';
series.setData(largeData);  // LTTB automatically kicks in

chart.addSeries(series);
Pan and zoom interactions remain smooth even with large datasets because the LTTB downsampling re-calculates for the visible window, ensuring you always see the most relevant 2,000 points for the current view.

Downsampling threshold

The LTTB threshold is currently set at 2,000 points. This value was chosen because:
  • Most displays can’t show more than ~2,000 distinct pixel positions horizontally
  • It provides smooth rendering at 60fps on most hardware
  • Visual fidelity is maintained for nearly all use cases
  • Interactive operations (pan, zoom) remain responsive
// From src/math/LTTB.ts:6-11
export function lttb(data: Point[], threshold: number): Point[] {
  const dataLength = data.length;
  if (threshold === 0) return [];
  if (threshold >= dataLength) {
    return data;  // No downsampling needed
  }
  // ... downsampling logic
}

Bar charts and large datasets

Bar series currently don’t use LTTB downsampling because each bar represents a discrete category or value that shouldn’t be omitted:
// From src/render/BarSeries.ts:13-19
updateVisibleData() {
  // For bars, we usually don't downsample the same way as lines
  // because every bar is significant.
  this.visibleData = this.data;
}
For very large categorical datasets, consider:
  • Using scrollable mode for categorical axes
  • Aggregating data before passing to the chart
  • Using line charts instead if the data is continuous
While LTTB preserves visual shape excellently, it does reduce the number of points. For use cases requiring every single data point to be visible (like regulatory compliance or detailed analysis), consider implementing custom controls to show raw data on demand.

Layer architecture benefits

The chart’s layer system (implemented in src/core/SceneGraph.ts) separates rendering into multiple canvases:
  • Grid layer (z-index: 0): Background grid
  • Series layers (z-index: 1+): Data visualization
  • Axis layer (z-index: 50): Axes and labels
  • Legend layer (z-index: 100): Legend
This separation means:
  • Only layers that change need to be redrawn
  • Interactions can update just the tooltip without redrawing data
  • Animations can run on one layer while others remain static
// Efficient updates - only changed layers redraw
this.sceneGraph.render();  // Renders all layers efficiently

Build docs developers (and LLMs) love