Data types

Chart data is plain serializable objects — no formatter callbacks and no render props, which is what lets a server component render a chart. Labels are already translated: this package never resolves an i18n key. Type names in the props tables link here.

ChartSeries

Used by line, area, bar, stacked bar, radar and every other multi-series form. Each values entry aligns with the category at the same index, so the array is as long as categories.

tsx
type ChartSeries = {
  /** Stable identity: the React key, and how hover correlates across charts. */
  id: string
  /** Already-translated display name, used by the legend and the tooltip. */
  label: string
  /** One value per category. null is a genuine gap, drawn as one, never as zero. */
  values: (number | null)[]
  /** Palette slot, 1-based. Pin it when the caller can hide series. */
  slot?: number
  /** Overrides the active palette for this series. */
  color?: string
}

An explicit color wins over the provider palette and the CSS variables. Omit slot and a series takes its index — correct for a fixed list, wrong the moment the list can be filtered, because the survivors get repainted and the reader has to re-learn the chart.

tsx
const series: ChartSeries[] = [
  {
    id: 'revenue',
    label: 'Revenue',
    values: [42, 56, null, 72],
    slot: 1,
    color: '#16a34a',
  },
]

ChartDatum

A labelled scalar — the shape part-to-whole and ranked forms consume: pie, funnel, gauge segments, diverging bars.

tsx
type ChartDatum = {
  id: string
  label: string
  value: number
  slot?: number
  color?: string
}

Axes and number formatting

axis is the on/off switch both cartesian axes share; format is one description of a number, applied to axis ticks, tooltips and direct labels alike, so they can never disagree.

tsx
type ChartAxes = {
  x?: boolean
  y?: boolean
}

type ChartNumberFormat = {
  /** Fraction digits. Defaults to 0. */
  decimals?: number
  /** BCP 47 tag for grouping and decimal separators. Defaults to the runtime locale. */
  locale?: string
  /** Rendered before the number — a currency symbol, typically. */
  prefix?: string
  /** Rendered after the number — a unit or a percent sign. */
  suffix?: string
}

ChartLegendItem

What Chart.Legend takes when you place identity yourself. The color is already resolved — a swatch is a color, not a slot to look up.

tsx
type ChartLegendItem = {
  id: string
  label: string
  color: string
}

Specialized chart data

Some forms take a shape-specific contract instead of ChartSeries, because their encoding is not "one value per category". The field names describe the marks directly.

tsx
/** Chart.Radar — one axis per row. Axes are scaled independently. */
type ChartRadarAxis = {
  label: string
  max: number
}

/** Chart.Heatmap — addressed by axis index; null renders empty, not as the ramp's low end. */
type ChartHeatmapCell = {
  columnIndex: number
  rowIndex: number
  value: number | null
}

/** Chart.Dumbbell — a before → after pair per row. */
type ChartDumbbellRow = {
  id: string
  label: string
  before: number
  after: number
}

/** Chart.Boxplot — the five-number summary, plus outliers you have already picked. */
type ChartBoxplotGroup = {
  id: string
  label: string
  min: number
  q1: number
  median: number
  q3: number
  max: number
  outliers?: number[]
}

/** Required, because "Q1" is not a word every reader of your product knows. */
type BoxplotLabels = {
  min: string
  q1: string
  median: string
  q3: string
  max: string
}

/** Chart.Sankey — nodes, and the weighted edges that address them by id. */
type ChartFlowNode = {
  id: string
  label: string
  slot?: number
  color?: string
}

type ChartFlowLink = {
  source: string
  target: string
  value: number
}

/** Chart.Treemap and Chart.Sunburst — leaves carry a value, parents sum their children. */
type ChartHierarchyNode = {
  id: string
  label: string
  value?: number
  children?: ChartHierarchyNode[]
  slot?: number
  color?: string
}

/** Chart.Scatter — an unordered two-measure space. size turns points into bubbles. */
type ChartScatterSeries = {
  id: string
  label: string
  points: Array<{
    x: number
    y: number
    size?: number
    label?: string
  }>
  slot?: number
  color?: string
}

/** Chart.TimeSeries — parallel arrays, because that is what uPlot consumes. */
type ChartTimePoints = {
  /** Unix seconds, strictly ascending. */
  timestamps: number[]
  /** One entry per series, each as long as timestamps. */
  values: (number | null)[][]
}

Candlestick data

One entry per session. volume is what showVolume draws, and timestamp is only needed when something outside the chart has to line up with the session.

tsx
type ChartCandlestickDatum = {
  id: string
  category: string
  open: number
  high: number
  low: number
  close: number
  volume?: number
  /** Unix seconds. */
  timestamp?: number
}

type ChartCandlestickStyle = {
  upColor?: string
  downColor?: string
  neutralColor?: string
  /** Draws rising candles as outlines — the convention on most trading desks. */
  hollowUp?: boolean
  candleWidth?: number
  wickWidth?: number
  volumeUpColor?: string
  volumeDownColor?: string
  /** Share of the plot height the volume histogram takes. */
  volumeHeightRatio?: number
}

Axis options

axis switches an axis off; xAxis and yAxis describe one. Both are read by line, area, bar, stacked bar and candlestick — the forms whose readers pin a domain, change the scale or annotate a value. The other forms take the visibility switch only, which is why their props tables list axis alone.

tsx
type ChartAxisOptions = {
  visible?: boolean
  label?: string
  /** "auto" | "category" | "linear" | "log" | "time" */
  scale?: ChartAxisScale
  /** Pins the extent. Omit either end to keep it computed. */
  domain?: { min?: number; max?: number }
  format?: ChartNumberFormat
  grid?: boolean
  gridDash?: number[]
  labelRotation?: number
  /** Which side the axis is drawn on: "start" | "end". */
  position?: ChartAxisPosition
  reversed?: boolean
  /** A hint, not a guarantee — the engine still picks readable ticks. */
  tickCount?: number
  /** Exact ticks, when the reader is looking for specific ones. */
  tickValues?: (number | string)[]
}

Annotations

A union discriminated on type. Coordinates are number | string: a category name on a category axis, a value on a linear or time one.

tsx
type ChartAnnotation =
  | ChartLineAnnotation
  | ChartRangeAnnotation
  | ChartPointAnnotation
  | ChartTextAnnotation

/** A target, a threshold, a launch date. */
type ChartLineAnnotation = {
  type: 'line'
  id: string
  axis: 'x' | 'y'
  value: number | string
  label?: string
  color?: string
  dash?: number[]
}

/** A shaded span — a quarter, an incident window, a tolerance band. */
type ChartRangeAnnotation = {
  type: 'range'
  id: string
  axis: 'x' | 'y'
  start: number | string
  end: number | string
  label?: string
  color?: string
  opacity?: number
}

type ChartPointAnnotation = {
  type: 'point'
  id: string
  x: number | string
  y: number
  label?: string
  color?: string
  symbol?: ChartSymbol
}

type ChartTextAnnotation = {
  type: 'text'
  id: string
  text: string
  x?: number | string
  y?: number
  color?: string
}

Interaction

interaction is what the chart does on its own; onInteraction is how your code hears about it. The event is one flat serializable shape for every form, so a handler written for a bar chart works on a line chart.

tsx
type ChartInteraction = {
  /** "axis" | "nearest" | "series" | "none" */
  hover?: ChartHoverMode
  /** "both" | "x" | "y" | "none" */
  crosshair?: ChartCrosshairMode
  tooltip?: boolean
  /** "single" | "multiple" | "none" */
  selection?: ChartSelectionMode
  pan?: boolean
  zoom?: boolean
  /** How far a hovered mark grows. */
  highlightScale?: number
  /** How far the rest fades while one mark is hovered. */
  dimOpacity?: number
  /** Native only — the web has no honest equivalent. */
  haptics?: boolean
}

type ChartInteractionEvent = {
  seriesId?: string
  category?: string
  value?: number
  x?: number
  y?: number
  /** Unix seconds, on the time-based forms. */
  timestamp?: number
  /** Pointer position in the native view's coordinate space. */
  nativeX?: number
  nativeY?: number
}
A handler is a client boundary. Everything else on a chart is serializable data, so a server component can render it — onInteraction is the one prop that cannot cross, and the file that passes it needs "use client".

Plot, series style and animation

surface is the box the chart sits in; plot is the drawing area inside it. seriesStyles is keyed by ChartSeries.id, so a style survives reordering and filtering the way slot does for color.

tsx
type ChartPlotStyle = {
  backgroundColor?: string
  borderColor?: string
  borderWidth?: number
  borderRadius?: number
  /** Clips marks to the plot area — the honest choice when a domain is pinned. */
  clip?: boolean
  padding?: number | { top?: number; right?: number; bottom?: number; left?: number }
}

type ChartSeriesStyle = {
  color?: string
  strokeWidth?: number
  strokeDash?: number[]
  fillOpacity?: number
  opacity?: number
  /** "circle" | "diamond" | "square" | "triangle" | "none" */
  symbol?: ChartSymbol
  symbolSize?: number
}

type ChartAnimation = {
  enabled?: boolean
  /** The entrance. Turn it off for a chart that re-renders on every keystroke. */
  initial?: boolean
  /** The transition when data changes under a mounted chart. */
  updates?: boolean
  duration?: number
  delay?: number
  /** "linear" | "ease-in" | "ease-out" | "ease-in-out" | "spring" */
  easing?: ChartAnimationEasing
}