TanStack
Guides

Large Data

Large-data charting starts with representation, not renderer throughput.

Ask:

  1. What question must the chart answer?
  2. Must a reader inspect every observation?
  3. How many independently useful marks fit in the available pixels?
  4. Which totals, extrema, order, or identities must never be lost?
  5. How frequently does the source update?

Raw output is correct only while independently rendering every observation is both meaningful and affordable.

Count every stage

Track four quantities:

  • source: rows received by the application;
  • represented: source rows accounted for by the encoding;
  • prepared: rows or vertices passed to marks;
  • rendered: scene nodes and path vertices, plus the selected renderer's DOM or draw work.

Do not describe a million-row chart as a million rendered points when the visible result is a few thousand aggregate cells. The bounded output is a feature, but its accounting must be honest.

Bounded representations

ProblemRepresentationInvariant
Dense point cloudFixed density gridCell counts sum to source count
High-rate ordered signalPixel-width first/min/max/last envelopeEvery row belongs to one bucket and global extrema survive
Numeric distributionFixed-boundary histogramBin counts sum to source count
High-cardinality categoriesLeading categories plus OtherLeading and remainder totals sum to source count
Large time range with a viewportDomain-clipped or width-bounded envelopeVisible extrema and boundary continuity survive

Choose bucket count from the task and available resolution. A test fixture's grid or threshold count is not a universal default.

When raw output is still useful

  • One line path can retain many ordered observations without creating one DOM node per row.
  • A scatterplot creates an independently addressable mark for every observation.
  • Stable keys make rolling updates reuse surviving nodes.

Identity reuse reduces allocation and replacement. It cannot make ten thousand independent SVG elements a sensible default when those elements compete for the same pixels.

Switch representations before the raw update cost exceeds the product's frame budget or the output stops being readable.

When Canvas helps

The optional Canvas renderer removes per-node SVG DOM and reconciliation cost. It can be useful when a measured bottleneck is SVG element creation or paint. It does not remove channel materialization, scale and guide work, scene compilation, path construction, or ChartPoint creation.

A dot mark still produces one scene node and one interaction point per observation. Default nearest-point focus still scans those points linearly. Supply a measured spatialIndex when individual points are still the correct pointer target, and use a focus strategy with a bounded navigation order when every observation is not a useful keyboard stop.

Do not treat Canvas as permission to promise a million independently interactive marks. Compare source, represented, prepared, and rendered counts; measure compilation, paint, interaction, and memory; then aggregate, sample, or bound the window when the representation exceeds the product budget.

Transform ownership

Put width-independent aggregation in application code. Memoize it with the framework's computed-state primitive when it should survive a presentation change.

Surface-responsive transforms may use the responsive builder's full scene width. Exact screen-space transforms need the final inner plot bounds and resolved scales, so implement them in a custom mark's render phase or an application-owned overlay. Keep either cost visible rather than hiding it in an unrelated renderer.

ts
function summarizeDensity(rows: Input['rows']) {
  return summarizeSource(rows)
}

function createDensityChart(rows: Input['rows']) {
  const summary = summarizeDensity(rows)

  return defineChart(({ width }) => {
    const thresholds = width < 480 ? 12 : width < 900 ? 24 : 40
    const bins = buildSemanticBins(summary, { thresholds })
    return densitySpec(bins)
  })
}

The full scene dimensions equal final plot dimensions only for a deliberately guide-free, zero-margin chart. See Responsive Charts for the geometry boundary.

Use the official D3 modules linked from Scales and D3 for grouping, binning, summary statistics, spatial indexing, and shape preparation. TanStack Charts consumes the resulting rows and intervals.

Interaction over dense output

First decide whether the encoded mark or the original observation is the focus target.

  • Focus aggregate cells when the chart communicates density.
  • Provide drill-down or a linked table when readers need original rows.
  • Use a spatial index only when individual points remain the correct representation.
  • Keep keyboard navigation bounded to useful targets instead of stepping through every source observation.
  • Keep exact values outside hover-only UI.

An index can accelerate nearest-point lookup. It does not reduce scene size, DOM or draw count, path construction, paint cost, or visual overplotting.

Streaming windows

For a rolling source:

  • store complete history outside the renderer when the product needs it;
  • pass a bounded window or envelope;
  • preserve keys for surviving observations;
  • decide whether the viewport follows the newest data;
  • preserve a locked viewport during offscreen updates;
  • announce meaningful offscreen changes through application UI.

Measure same-key value changes, partial rolls, full replacement, resize, and a sustained update stream. A fast mount does not prove a healthy update path.

Performance measurement

Separate:

  • deterministic source creation;
  • analytical preparation;
  • scene compilation;
  • SVG serialization;
  • DOM reconciliation;
  • Canvas painting;
  • pointer activation and sustained movement;
  • memory after repeated updates and destroy.

Validation hashes and invariant checks belong outside timed preparation. Report failed correctness cells as failures, not as fast results.

The repository's stress harness provides product-shaped and encoded workloads:

shell
pnpm benchmark:stress:quick
pnpm benchmark:stress:standard

Use focused filters while developing, then run the canonical profile before a release. See Bundle Size and Performance for current budgets and import boundaries.

Large-data checklist

  • The representation answers the stated question.
  • Every source row is accounted for or sampling is disclosed.
  • Totals, extrema, ordering, and identity invariants are executable.
  • Source, represented, prepared, and rendered counts are reported separately.
  • Width-dependent preparation invalidates on resize.
  • Output complexity is bounded in its native units.
  • Rolling data preserves surviving keys.
  • Dense focus targets match the represented meaning.
  • Exact values have a table, drill-down, or textual alternative.