Large-data charting starts with representation, not renderer throughput.
Ask:
Raw output is correct only while independently rendering every observation is both meaningful and affordable.
Track four quantities:
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.
| Problem | Representation | Invariant |
|---|---|---|
| Dense point cloud | Fixed density grid | Cell counts sum to source count |
| High-rate ordered signal | Pixel-width first/min/max/last envelope | Every row belongs to one bucket and global extrema survive |
| Numeric distribution | Fixed-boundary histogram | Bin counts sum to source count |
| High-cardinality categories | Leading categories plus Other | Leading and remainder totals sum to source count |
| Large time range with a viewport | Domain-clipped or width-bounded envelope | Visible 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.
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.
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.
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.
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.
First decide whether the encoded mark or the original observation is the focus target.
An index can accelerate nearest-point lookup. It does not reduce scene size, DOM or draw count, path construction, paint cost, or visual overplotting.
For a rolling source:
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.
Separate:
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:
pnpm benchmark:stress:quick
pnpm benchmark:stress:standardUse focused filters while developing, then run the canonical profile before a release. See Bundle Size and Performance for current budgets and import boundaries.