Pass a D3 scale factory when its domain should come from mark channels. Pass a scale instance when the domain is fixed application state. TanStack Charts copies the resolved scale, assigns its responsive pixel range, and uses that copy for marks, ticks, and interaction. A supplied instance is never mutated.
For the architectural boundary and guidance on selecting granular D3 modules, read Scales and D3. This page documents only the TanStack Charts contract around those primitives.
The common path passes the D3 factory directly:
import { scaleLinear, scaleUtc } from 'd3-scale'
const x = {
scale: scaleUtc,
nice: true,
label: 'Date',
}
const y = {
scale: scaleLinear,
nice: true,
label: 'Revenue',
grid: true,
}The chart creates a fresh scale for each layout, derives its domain from every materialized channel bound to that axis, applies nice, and assigns the responsive range. Bar and area baselines contribute zero when their baseline is implicit. Empty channels retain the D3 factory's native domain.
Return a configured scale from a zero-argument factory for options that should be applied before domain inference:
const x = {
scale: () => scaleBand<string>().padding(0.2),
}nice is an axis option because it must run after the inferred domain exists.
Pass a scale instance when the domain is semantic application state:
const y = {
scale: scaleLinear().domain([0, 100]),
}Instances retain:
TanStack Charts owns:
Scale copies make one configured scale safe to reuse across responsive scenes.
interface ChartAxisOptions<TValue extends ChartValue> {
scale: ChartScale | ConfiguredScaleLike<TValue> | ChartScaleFactory<TValue>
nice?: boolean | number
guide?: boolean
ticks?: number
format?: (value: TValue) => string
grid?: boolean
label?: string
reverse?: boolean
tickRotate?: number
labelOffset?: number
}| Option | Default | Meaning |
|---|---|---|
| scale | Required | D3 factory, configured instance, or advanced ChartScale. |
| nice | false | Nice the resolved domain using the responsive or supplied tick count. |
| guide | true for a non-null axis | Renders the axis, ticks, title, and requested grid. |
| ticks | Responsive target | Suggested tick count passed to the scale. It is not a guaranteed count. |
| format | Scale formatter, then string conversion | Formats tick values. Its value type is inferred from the marks. |
| grid | false for x; true for y | Draws grid rules at tick positions. |
| label | None | Axis title. |
| reverse | false | Reverses the responsive pixel range without changing the caller's scale. |
| tickRotate | 0 | Rotates tick labels in degrees. |
| labelOffset | Automatic | Overrides the title's distance from the axis. |
Set an axis to null only when no mark uses that positional scale. To keep the scale while hiding its guide, use guide: false.
Without an explicit ticks, the responsive target is clamp(2, floor(chart.width / 92), 8) for x and clamp(2, floor(chart.height / 48), 7) for y. The configured scale may return a different number of ticks.
When a margin side is omitted, the scene compiler measures:
The DOM host measures the inherited container font and relayouts after web fonts load. Static compilation uses deterministic estimates unless measureText is supplied.
interface ChartTextMeasurer {
(
text: string,
options: {
fontSize: number
fontWeight?: number
anchor: 'start' | 'middle' | 'end'
baseline: 'auto' | 'middle' | 'hanging'
},
): {
x: number
y: number
width: number
height: number
}
}The returned x and y locate the painted glyph box relative to the requested anchor and baseline. Pass the measurer through a host, adapter, runtime render, or createChartScene layout options.
ChartScale is the unchecked extension boundary for a nonstandard positional mapping:
interface ChartScale {
id: string
resolve(context: ChartScaleResolveContext): ResolvedScale
}
type ChartScaleResolver = (context: ChartScaleResolveContext) => ResolvedScaleThe resolver context contains id, all materialized values, the responsive range, the axis options, a target tickCount, and includeZero. It must return a complete ResolvedScale:
interface ResolvedScale {
id: string
type: string
domain: readonly ChartValue[]
map(value: unknown): number
ticks: readonly { value: ChartValue; label: string; position: number }[]
bandwidth: number
}Prefer a configured scale when it can express the mapping. A custom scale owns correct domains, finite mapping, ticks, formatting, bandwidth, and response to the supplied range.
Marks with a z or color channel contribute values to one chart-level color scale.
interface ConfiguredColorScaleLike<TValue extends ChartKey, TOutput> {
(value: TValue): TOutput
copy: () => ConfiguredColorScaleLike<TValue, TOutput>
domain?: () => readonly TValue[]
range?: () => readonly TOutput[]
}
interface ChartColorOptions {
scale?: ConfiguredColorScaleLike<any, any> | ChartColorScaleFactory<any, any>
type?: ChartColorScale
domain?: readonly ChartKey[]
range?: readonly string[]
nice?: boolean | number
legend?: ChartColorLegend
}| Option | Default | Meaning |
|---|---|---|
| scale | None | D3 factory with inference or configured instance with a fixed domain |
| type | None | Custom color-scale resolver |
| domain | Observed channel values | Domain hint for factory, built-in, or custom resolution |
| range | Resolved theme palette | Range hint for the built-in or custom resolver |
| nice | false | Nice a factory or configured continuous color scale |
| legend | None | Legend layout and scene renderer shown above the inner chart |
Resolution order:
A color-scale factory infers a continuous extent or distinct categorical domain from color channels. Multi-stop continuous ranges receive evenly spaced domain stops across that extent. A supplied scale instance retains its domain. Outputs are converted to strings. Optional domain() and range() methods provide legend metadata; when either method is absent, supply the corresponding domain or range option if a legend needs it. A custom ChartColorScale receives observed values, optional domain and range, and the resolved theme, then returns:
interface ResolvedColorScale {
type: string
domain: readonly ChartKey[]
range: readonly string[]
map(value: ChartKey | null | undefined): string
}ChartKey is string | number. On the built-in and configured-scale paths, a null group maps to the first range color or currentColor. A custom ChartColorScale owns its null mapping through ResolvedColorScale.map.
import { colorLegend } from '@tanstack/charts/legend'
colorLegend({
label: 'Package',
itemWidth: 120,
})interface ColorLegendOptions {
label?: string
itemWidth?: number
}itemWidth defaults to 110 and is clamped to a minimum of 64. Items wrap to responsive columns above the chart. Labels are derived from the color-scale domain.
import { colorGradientLegend } from '@tanstack/charts/legend'
colorGradientLegend({
label: 'Density',
steps: 48,
width: 240,
format: (value) => value.toFixed(1),
})interface ColorGradientLegendOptions {
label?: string
steps?: number
width?: number
format?: (value: number) => string
}steps defaults to 32 and is clamped to at least 2. width defaults to 240, uses at least 80 when space permits, and is finally capped by the inner chart width. The legend requires a numeric first and last color-domain value and throws for a nonnumeric domain.
ChartColorLegend separates layout from rendering:
interface ChartColorLegend {
height(itemCount: number, width: number): number
render(context: ChartColorLegendContext): SceneNode
}height reserves space before chart bounds are finalized. render receives the resolved colors, chart bounds, theme, and full width. Return one keyed scene node.
The built-in theme is described in Chart spec. Chart gradients are independent SVG resources:
interface ChartLinearGradient {
id: string
x1?: number
y1?: number
x2?: number
y2?: number
stops: readonly {
offset: number
color: string
opacity?: number
}[]
}Coordinates and offsets are normalized from 0 to 1 by the resource-aware renderer. Omitted coordinates form a vertical gradient from bottom to top. Use renderChartSvgWithResources and an idPrefix; see Rendering and export.