# Chart types (/docs/chart/types)





Every example below is still [`Chart`](/docs/chart) — TanStack's real grammar, plus the kit's theming. Grouped by what the data needs to say, not by how simple the code is: pick the section that matches your data's job, then adapt the mark. All of it wires TanStack's own [tooltip extension](https://tanstack.com/charts) (`@tanstack/charts/tooltip`).

## Magnitude by category [#magnitude-by-category]

`barY` compares categories vertically; `barX` swaps the axes for a ranking with labels too long to fit under vertical bars.

<Preview title="Commits per month" align="block" code="&#x22;const chart = defineChart({\n  marks: [barY(commitsByMonth, { x: 'month', y: 'commits', inset: 2 })],\n  scales: {\n    x: { scale: () => scaleBand().padding(0.24) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Commits' } },\n  },\n  tooltip,\n});\n\n<Chart definition={chart} ariaLabel=\&#x22;Commits per month\&#x22; height={280} color=\&#x22;blue\&#x22; />&#x22;">
  <CommitsByMonthDemo />
</Preview>

<Preview title="Lines changed by contributor" align="block" code="&#x22;const chart = defineChart({\n  marks: [barX(linesChangedByContributor, { y: 'contributor', x: 'lines', inset: 2 })],\n  scales: {\n    y: { scale: () => scaleBand().padding(0.24) },\n    x: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Lines changed' } },\n  },\n  tooltip,\n});&#x22;">
  <HorizontalBarDemo />
</Preview>

## Change over time [#change-over-time]

`lineY` for a trend, `areaY` when the magnitude *building up* matters, and `areaY`'s `y1`/`y2` (instead of `y`) for an interval band — with a second mark composed on top, since a chart is just marks layered in one `defineChart`.

<Preview title="Weekly active users" align="block" code="&#x22;const chart = defineChart({\n  marks: [lineY(weeklyActiveUsers, { x: 'week', y: 'users', points: true })],\n  scales: {\n    x: { scale: () => scalePoint().padding(0.5) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Weekly active users' } },\n  },\n  tooltip,\n});&#x22;">
  <WeeklyActiveUsersDemo />
</Preview>

<Preview title="Cumulative signups this quarter" align="block" code="&#x22;const chart = defineChart({\n  marks: [\n    areaY(cumulativeSignups, { x: 'week', y: 'total', fillOpacity: 0.35, strokeWidth: 2 }),\n    ruleY([0]),\n  ],\n  scales: {\n    x: { scale: () => scalePoint().padding(0.5) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Cumulative signups' } },\n  },\n  tooltip,\n});&#x22;">
  <CumulativeAreaDemo />
</Preview>

<Preview title="Response time, P50 line inside a P50-P99 band" align="block" code="&#x22;const chart = defineChart({\n  marks: [\n    areaY(responseTimeBand, { x: 'day', y1: 'p50', y2: 'p99', fillOpacity: 0.28 }),\n    lineY(responseTimeBand, { x: 'day', y: 'p50', strokeWidth: 2 }),\n  ],\n  scales: {\n    x: { scale: () => scalePoint().padding(0.5) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Response time (ms)' } },\n  },\n  tooltip,\n});&#x22;">
  <RangeAreaDemo />
</Preview>

## Relationship [#relationship]

`dot` plots raw x/y pairs — a scatter for two quantitative variables, or a bubble once a third joins as a `r` (radius) channel.

<Preview title="Review time vs. PR size" align="block" code="&#x22;const chart = defineChart({\n  marks: [dot(prSamples, { x: 'linesChanged', y: 'reviewHours' })],\n  scales: {\n    x: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Lines changed' } },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Review time (hours)' } },\n  },\n  tooltip,\n});&#x22;">
  <ScatterDemo />
</Preview>

<Preview title="The same data, with comment count as a third dimension" align="block" code="`dot(prSamples, { x: 'linesChanged', y: 'reviewHours', r: 'comments', fillOpacity: 0.7 })`">
  <BubbleDemo />
</Preview>

## Distribution [#distribution]

`binX` groups raw observations into bins (`thresholds` picks roughly how many) for `rect` to draw as a histogram — no dedicated histogram mark, a transform plus a mark. `boxY` takes raw per-observation rows directly and summarizes each category into a Tukey box (quartiles, whiskers, outliers) itself, no pre-aggregation.

<Preview title="PR size distribution" align="block" code="&#x22;const bins = binX(prSizes.map((size) => ({ size })), { value: 'size', thresholds: 10 });\n\nconst chart = defineChart({\n  marks: [rect(bins, { x1: 'x1', x2: 'x2', y1: () => 0, y2: 'value', inset: 1 })],\n  scales: {\n    x: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Lines changed' } },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'PRs' } },\n  },\n  tooltip,\n});&#x22;">
  <HistogramDemo />
</Preview>

<Preview title="Review turnaround by team" align="block" code="&#x22;const chart = defineChart({\n  marks: [boxY(reviewTurnaround, { x: 'team', y: 'hours' })],\n  scales: {\n    x: { scale: () => scaleBand().domain(['Platform', 'Web', 'Mobile']).padding(0.35) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Hours' } },\n  },\n  tooltip,\n});&#x22;">
  <BoxplotDemo />
</Preview>

## Composition [#composition]

Multiple series first, then stacked, then stacked *and* normalized. A `color` channel on a mark gets TanStack's own implicit categorical scale — [`Chart`'s `colors` prop](/docs/chart#series-color) themes that via `--ts-chart-N`, with zero change to how the chart itself is built. For a specific series order or a legend, build the color scale explicitly instead: `chartColors()` resolves the kit's palette to literal values for `color.range`, paired with `colorLegend()` (TanStack's own).

<Preview title="Commits by contributor" align="block" code="&#x22;const chart = defineChart({\n  marks: [\n    lineY(commitsByContributor, { x: 'month', y: 'commits', color: 'contributor', points: true }),\n  ],\n  scales: {\n    x: { scale: () => scalePoint().padding(0.5) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Commits' } },\n  },\n  tooltip,\n});\n\n<Chart definition={chart} ariaLabel=\&#x22;Commits by contributor\&#x22; height={320} colors={['blue', 'green', 'red']} />&#x22;">
  <CommitsByContributorDemo />
</Preview>

<Preview title="Issues by status, per month (stacked)" align="block" code="&#x22;const chart = defineChart({\n  marks: [\n    areaY(issuesByStatus, {\n      x: 'month',\n      y: 'count',\n      color: 'status',\n      layout: stack({ order: ['Open', 'In progress', 'Closed'] }),\n      fillOpacity: 0.85,\n    }),\n    ruleY([0]),\n  ],\n  scales: {\n    x: { scale: () => scalePoint().padding(0.15) },\n    y: { scale: scaleLinear, nice: true, grid: true, axis: { label: 'Issues' } },\n  },\n  color: {\n    domain: ['Open', 'In progress', 'Closed'],\n    range: chartColors(['orange', 'blue', 'green']),\n    legend: colorLegend({ label: 'Status' }),\n  },\n  tooltip,\n});&#x22;">
  <IssuesByStatusDemo />
</Preview>

<Preview title="Traffic source share by quarter (normalized)" align="block" code="&#x22;const chart = defineChart({\n  marks: [\n    barY(trafficShareByQuarter, {\n      x: 'quarter',\n      y: 'sessions',\n      color: 'channel',\n      layout: stack({ order: ['Search', 'Direct', 'Referral', 'Social'], offset: 'normalize' }),\n      inset: 2,\n    }),\n  ],\n  scales: {\n    x: { scale: () => scaleBand().padding(0.24) },\n    y: {\n      scale: scaleLinear,\n      grid: true,\n      axis: { label: 'Share of sessions', ticks: { format: (v) => `${Math.round(v * 100)}%` } },\n    },\n  },\n  color: {\n    domain: ['Search', 'Direct', 'Referral', 'Social'],\n    range: chartColors(['blue', 'green', 'orange', 'purple']),\n    legend: colorLegend({ label: 'Channel' }),\n  },\n  tooltip,\n});&#x22;">
  <NormalizedStackDemo />
</Preview>

## Comparison across groups [#comparison-across-groups]

TanStack has its own `facet` mark for small multiples; the simpler, always-safe option is just rendering N ordinary `Chart`s side by side — no new API to learn, and each panel is an ordinary chart you could style or interact with independently.

<Preview title="PRs merged per month, per team" align="block" code="&#x22;{Object.entries(teamChartDefinitions).map(([team, definition]) => (\n  <div key={team}>\n    <p>{team}</p>\n    <Chart definition={definition} ariaLabel={`PRs merged per month — ${team}`} height={160} />\n  </div>\n))}&#x22;">
  <SmallMultiplesDemo />
</Preview>

## Putting it together [#putting-it-together]

A single chart rarely stays inside one "job" — a real dashboard layers several. This one composes four banded `areaY` regions (an expected-pace corridor), a `lineY` split into a solid historical segment and a dashed projected one, `dot` for per-point pacing color, and `ruleX`/`text` for milestone and "now" annotations — eleven ordinary marks in one `defineChart`, nothing dashboard-specific about any of them.

<Preview title="Goal progress against a target corridor" align="block" code="&#x22;<MilestoneProgressDemo />&#x22;">
  <MilestoneProgressDemo />
</Preview>

<Alert
  type="info"
  title="More in TanStack's own docs"
  description="
  <>
    This page is a sample, not the ceiling — TanStack Charts also covers node-link diagrams, geo projections, hexbin/contour
    density, sunburst/treemap hierarchies, and more. See its own{&#x22; &#x22;}
    <a href=&#x22;https://tanstack.com/charts&#x22;>example gallery</a> for the rest.
  </>
"
/>
