@tanstack/charts
Version:
A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.
215 lines (153 loc) • 6.3 kB
Markdown
---
name: prepare-chart-data
description: >
Order, aggregate, derive, and transform application rows for TanStack
Charts while preserving semantic units and lineage. Load for stacks, bins,
groups, rolling windows, ranks, regression, hierarchy, missing values, or
reactive transform work.
metadata:
type: core
library: '@tanstack/charts'
library_version: '0.9.0'
sources:
- 'TanStack/charts:docs/guides/transforms-and-reactivity.md'
- 'TanStack/charts:docs/reference/transforms.md'
- 'TanStack/charts:docs/reference/marks/line-and-area.md'
---
# Prepare Chart Data
Use **trigger → inspect → decide → build → verify**. Keep analytical preparation outside responsive layout and preserve the row lineage needed by tooltips, selection, tables, and drill-down.
## Setup
Prepare semantic rows once, then pass them to marks:
```ts
import { groupBy, lineY, rollingWindow } from '@tanstack/charts'
interface Order {
day: string
region: string
amount: number
}
export function prepareRevenue(orders: readonly Order[]) {
const daily = groupBy(orders, {
by: { region: 'region', day: 'day' },
outputs: {
revenue: { value: 'amount', reduce: 'sum' },
orders: { reduce: 'count' },
},
})
return rollingWindow(daily, {
by: 'region',
orderBy: 'day',
size: 7,
partial: false,
outputs: { revenue7d: { value: 'revenue', reduce: 'sum' } },
})
}
const prepared = prepareRevenue([
{ day: '2026-08-01', region: 'West', amount: 120 },
{ day: '2026-08-02', region: 'West', amount: 160 },
])
export const revenueMark = lineY(prepared, {
x: 'day',
y: 'revenue7d',
z: 'region',
})
```
## Core Patterns
### Choose the owner by scope
| Work | Owner |
| ---------------------------------------- | ------------------------------------------ |
| Calculation from one row | channel accessor |
| Reusable cross-row result | eager data transform |
| Geometry meaningful only inside one mark | `layout: stack()` or `layout: group()` |
| Product-specific enrichment or filtering | ordinary application function |
| Pixel-space work after margins resolve | resolved-layout mark, not a data transform |
### Compose structural and analytical transforms
```ts
import { normalize, select } from '@tanstack/charts'
import { fold } from '@tanstack/charts/transform/fold'
const services = [
{ service: 'api', latency: 180, throughput: 820 },
{ service: 'worker', latency: 240, throughput: 510 },
]
const folded = fold(services, {
fields: ['latency', 'throughput'] as const,
as: { key: 'metric', value: 'measurement' },
})
const normalized = normalize(folded, {
by: 'metric',
value: 'measurement',
basis: 'extent',
as: 'relativeMeasurement',
})
export const baselines = select(normalized, {
by: 'metric',
select: 'first',
})
```
Each stage should have one semantic responsibility and a named output.
### Order path data before mark construction
```ts
import { lineY } from '@tanstack/charts'
const rows = [
{ date: new Date('2026-08-03'), value: 14 },
{ date: new Date('2026-08-01'), value: 10 },
{ date: new Date('2026-08-02'), value: 12 },
]
const ordered = [...rows].sort((left, right) => +left.date - +right.date)
export const trend = lineY(ordered, { x: 'date', y: 'value' })
```
Scale domains do not reorder line or area topology.
### Preserve direct lineage
TanStack transforms record their immediate input in `source`. If application code creates additional derived rows, preserve equivalent references when focus, drill-down, or audit must reach the original observations.
Read [the transform decision table](references/transforms.md) before combining transform families.
## Common Mistakes
### CRITICAL Sorting after creating the mark
Wrong:
```ts
const mark = lineY(rows, { x: 'date', y: 'value' })
rows.sort((left, right) => +left.date - +right.date)
```
Correct:
```ts
const ordered = [...rows].sort((left, right) => +left.date - +right.date)
const mark = lineY(ordered, { x: 'date', y: 'value' })
```
Line and area marks capture input order as path order.
Source: `docs/reference/marks/line-and-area.md`
### HIGH Running eager transforms in responsive builders
Wrong:
```ts
defineChart(({ width }) => ({
marks: [lineY(rollingWindow(rows, options), channels)],
}))
```
Correct:
```ts
const prepared = rollingWindow(rows, options)
defineChart(({ width }) => ({ marks: [lineY(prepared, channels)] }))
```
Responsive builders may rerun for size and layout changes while source rows remain unchanged.
Source: `docs/guides/transforms-and-reactivity.md`; `API-FRICTION.md` F-128
### HIGH Manually accumulating ordinary stacks
Wrong: maintain application running totals for each x value and series.
Correct:
```ts
barY(rows, {
x: 'quarter',
y: 'revenue',
z: 'product',
layout: stack(),
})
```
Native stack layout owns missing series, negative values, order, and updates. Fixed in the current API, but agents trained on early examples may still generate manual accumulation.
Source: GitHub issue 9; `API-FRICTION.md` F-163
### HIGH Flattening derived rows without lineage
Wrong: replace aggregated rows with unlabeled numeric tuples.
Correct: keep named outputs, grouping fields, and direct source references through each application-owned step.
Tooltips, selection, and drill-down otherwise lose the records that contributed to the value.
Source: `docs/reference/transforms.md`; `docs/guides/transforms-and-reactivity.md`
### HIGH Tension: analytical honesty versus visual simplicity
Aggregation and normalization reduce visual noise but can hide denominators, sample sizes, uncertainty, and missing-value policy. Prepare the evidence required by `design-a-chart` before reducing row detail.
See also: `design-a-chart/SKILL.md` § Common Mistakes
## References
- [Transform selection and output contracts](references/transforms.md)
See also: `configure-scales-guides-color/SKILL.md`, `build-chart-interactions/SKILL.md`, and `coordinate-charts-with-tanstack/SKILL.md` — derived values determine domains, legends, tooltip content, available lineage, and shared chart-grid projections.