TanStack logo

Skill

prepare-chart-data

prepare data for TanStack Charts

Covers Data Analysis Charts Data Cleaning TanStack

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.

SKILL.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:

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

WorkOwner
Calculation from one rowchannel accessor
Reusable cross-row resulteager data transform
Geometry meaningful only inside one marklayout: stack() or layout: group()
Product-specific enrichment or filteringordinary application function
Pixel-space work after margins resolveresolved-layout mark, not a data transform

Compose structural and analytical transforms

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

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 before combining transform families.

Common Mistakes

CRITICAL Sorting after creating the mark

Wrong:

const mark = lineY(rows, { x: 'date', y: 'value' })
rows.sort((left, right) => +left.date - +right.date)

Correct:

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:

defineChart(({ width }) => ({
  marks: [lineY(rollingWindow(rows, options), channels)],
}))

Correct:

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:

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

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.

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.