TanStack
Catalog

Point estimates with error bars

uncertainty

104 lines · 1 file · 2.9 kB

cases/14-error-bars/example.tsx104 lines · entry
cases/14-error-bars/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'

import { penguins } from '@tanstack/charts-data/penguins'
import type { PenguinsRow } from '@tanstack/charts-data/penguins'
import {
  defineChart,
  deviation,
  dot,
  groupBy,
  link,
  tickY,
} from '@tanstack/charts'
import type { TransformReduceContext } from '@tanstack/charts'
import { scaleBand, scaleLinear } from 'd3-scale'

const estimate = new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 1,
  maximumFractionDigits: 1,
})

export const sampleDeviation = (context: TransformReduceContext<unknown>) =>
  context.values.length < 2 ? 0 : deviation(context)

export const createExampleChart = (input: ChartOptions) => {
  const observations = penguins
    .slice(input.revision * 8)
    .filter(
      (row): row is PenguinsRow & { body_mass_g: number } =>
        row.body_mass_g !== null,
    )
  const rows = groupBy(observations, {
    by: 'species',
    outputs: {
      mean: { value: 'body_mass_g', reduce: 'mean' },
      deviation: { value: 'body_mass_g', reduce: sampleDeviation },
    },
  })

  return defineChart(
    {
      marks: [
        link(rows, {
          id: 'error-interval',
          x1: 'species',
          y1: ({ mean, deviation: spread }) => mean - spread,
          x2: 'species',
          y2: ({ mean, deviation: spread }) => mean + spread,
          stroke: '#2563eb',
          strokeWidth: 1.5,
        }),
        tickY(rows, {
          id: 'error-low',
          x: 'species',
          y: ({ mean, deviation: spread }) => mean - spread,
          stroke: '#2563eb',
          strokeWidth: 1.5,
        }),
        tickY(rows, {
          id: 'error-high',
          x: 'species',
          y: ({ mean, deviation: spread }) => mean + spread,
          stroke: '#2563eb',
          strokeWidth: 1.5,
        }),
        dot(rows, {
          id: 'error-mean',
          x: 'species',
          y: 'mean',
          key: 'species',
          fill: '#2563eb',
          r: 3.5,
        }),
      ],
      x: {
        scale: () => scaleBand<string>().padding(0.22),
      },
      y: { scale: scaleLinear, grid: true, axis: { label: 'Body mass (g)' } },
    },
    {
      keyboard: true,
      tooltip: {
        use: exampleTooltip,
        ...{
          format: (point) =>
            `${point.datum.species} · Mean: ${estimate.format(point.datum.mean)} g · Range: ${estimate.format(point.datum.mean - point.datum.deviation)}${estimate.format(point.datum.mean + point.datum.deviation)} g`,
        },
      },
    },
  )
}
export interface ChartOptions {
  revision: number
}

export const exampleAriaLabel = 'Point estimates with error bars'

export const chart = createExampleChart({
  revision: 0,
})

export default function Example() {
  return <Chart ariaLabel={exampleAriaLabel} definition={chart} height={480} />
}