text annotates scaled positions, frame paints the resolved inner chart bounds, and facet composes complete child specs into responsive small multiples.
import { facet, facetChart, frame, lineY, text } from '@tanstack/charts'text(rows, {
x: 'date',
y: 'value',
text: 'label',
z: 'series',
dy: -8,
})function text<TDatum>(
source: Iterable<TDatum>,
options?: TextOptions<TDatum>,
): ChartMark<TDatum, InferredX, InferredY>| Option | Type | Default | Meaning |
|---|---|---|---|
| id | string | Layer-derived | Stable mark ID |
| x | Channel<TDatum, ChartValue?> | Row index | Horizontal anchor |
| y | Channel<TDatum, ChartValue?> | Numeric datum | Vertical anchor |
| text | Channel<TDatum, string | number?> | String form of datum | Label content |
| z | Channel<TDatum, ChartKey?> | No group | Interaction group and default fill |
| key | Channel<TDatum, ChartKey> | Unique id, then index | Stable identity |
| fill | VisualChannel<TDatum, string> | Theme foreground without z; resolved color with z | Label fill |
| fontSize | number | Inherited SVG font size | Font size |
| fontWeight | number | Inherited weight | Numeric font weight |
| anchor | VisualChannel<TDatum, TextAnchor> | 'middle' | 'start', 'middle', or 'end' |
| rotate | VisualChannel<TDatum, number> | No transform | Rotation in degrees around the final label origin |
| dx | VisualChannel<TDatum, number> | 0 | Horizontal pixel offset |
| dy | VisualChannel<TDatum, number> | 0 | Vertical pixel offset |
Labels use a middle baseline. Null or undefined text skips the row; the default for a null datum is an empty string. Invalid x/y values also skip the row.
The interaction point is at the offset label origin. Its semantic values remain the original x/y channels. Use a stable key when labels enter, exit, or reorder.
frame draws a background, border, or both around the final inner chart bounds:
frame({
fill: 'color-mix(in srgb, currentColor 3%, transparent)',
strokeOpacity: 0.25,
radius: 8,
})function frame(options?: FrameOptions): ChartMark<never, never, never>| Option | Type | Default | Meaning |
|---|---|---|---|
| id | string | Layer-derived | Stable mark ID |
| fill | string | 'none' | Constant fill |
| fillOpacity | number | SVG default | Fill opacity |
| stroke | string | Theme foreground | Constant stroke |
| strokeOpacity | number | 0.35 | Stroke opacity |
| strokeWidth | number | 1 | Stroke width |
| inset | number | 0 | Pixels removed from all chart-bound edges; clamped to at least zero |
| radius | number | None | Corner radius |
frame materializes no scale channels and emits no interaction points. Put it before data marks when it should paint behind them.
facet groups source rows by a key and renders one complete child chart spec per group inside the parent chart bounds.
facet(rows, {
by: 'region',
columns: 3,
minWidth: 220,
gap: 16,
label: (region) => `Region: ${region}`,
chart(groupRows) {
return {
marks: [lineY(groupRows, { x: 'date', y: 'value' })],
x: { scale: makeXScale(groupRows) },
y: { scale: makeYScale(groupRows), grid: true },
}
},
})function facet<TDatum>(
source: Iterable<TDatum>,
options: FacetOptions<TDatum>,
): ChartMark<TDatum>| Option | Type | Default | Meaning |
|---|---|---|---|
| id | string | Layer-derived | Stable outer mark ID |
| by | Channel<TDatum, ChartKey> | Required | String or number grouping key |
| chart | (data: readonly TDatum[], key: ChartKey) => ChartSpec | Required | Builds one static child spec per group |
| columns | number | Automatic | Requested column count, floored and clamped to 1..groupCount |
| minWidth | number | 220 | Target minimum cell width used for automatic columns |
| gap | number | 16 | Gap between rows and columns, clamped to at least zero |
| label | boolean | ((key) => string) | true | Shows default key labels, formats them, or disables labels |
| axes | 'outer' | 'cell' | 'outer' where shareable | Shared outside axes or independent axes in every cell |
Facet preserves first-seen group order and original row order within each group. Non-string and non-number by results are skipped.
Automatic columns are:
floor((availableWidth + gap) / (minWidth + gap))with a minimum of one. Rows are then derived from group count. Visible facet labels reserve 22 pixels above every child plot.
With more than one child and guides enabled, the default axes: 'outer' renders y ticks on the first column, x ticks on the final occupied row, and one shared title per dimension.
Outer axes require compatible cells:
An incompatible facet throws with guidance to use axes: 'cell'. This fail-fast behavior prevents a shared axis from implying a comparison the child scales do not support.
When every child sets guides: false, or there is only one child, facet uses the cell rendering path even if axes is omitted.
Set axes: 'cell' when groups need independent scales, guide options, margins, or legends:
facet(rows, {
by: 'region',
axes: 'cell',
chart: (groupRows) => buildIndependentSpec(groupRows),
})Every child compiles at its own cell dimensions with the parent's text measurement. Compilation errors are wrapped with the facet and cell key.
Child ChartPoint coordinates are offset into the parent scene. Point keys are prefixed with facet and group identity, so equal child keys remain unique across cells. Datum, semantic x/y values, group, group label, and paint are preserved.
The facet mark itself has intentionally opaque positional types because each child may return a different mark composition. Its child scale domains do not participate in the parent x/y scales.
facetChart wraps a facet mark in a complete static definition:
const definition = facetChart(rows, {
by: 'region',
chart: (groupRows) => buildSpec(groupRows),
})function facetChart<TDatum>(
source: Iterable<TDatum>,
options: FacetOptions<TDatum>,
): StaticChartDefinition<TDatum>The wrapper sets:
const specification = {
marks: [facet(source, options)],
guides: false,
margin: 0,
x: null,
y: null,
}Use facet directly when the small multiples must be layered with other parent-scene marks. Use facetChart for the usual standalone chart.