TanStack
Catalog

Annual gasoline-price waterfall

composition

109 lines · 1 file · 3.1 kB

cases/29-waterfall/example.tsx109 lines · entry
cases/29-waterfall/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'

import {
  barY,
  colorLegend,
  defineChart,
  delta,
  ruleY,
  rollingWindow,
  type ChartPoint,
} from '@tanstack/charts'
import { waterfall } from '@tanstack/charts/transform/waterfall'
import { decorative } from '@tanstack/charts/mark/decorative'
import type { WaterfallKind } from '@tanstack/charts/transform/waterfall'
import { scaleBand, scaleLinear } from 'd3-scale'
import { driving } from '@tanstack/charts-data/driving'

const kinds = [
  'increase',
  'decrease',
  'total',
] satisfies readonly WaterfallKind[]
const colors = ['#10b981', '#ef4444', '#2563eb']
const signedAmount = new Intl.NumberFormat('en-US', {
  signDisplay: 'always',
  style: 'currency',
  currency: 'USD',
  maximumFractionDigits: 2,
})

const observations = driving.filter((row) => row.year >= 2004)
const firstYear = observations[0]?.year
const lastYear = observations.at(-1)?.year
const totalLabel =
  firstYear === undefined || lastYear === undefined
    ? 'Total'
    : `${firstYear}${String(lastYear).slice(-2)}`

export const yearlyChanges = rollingWindow(observations, {
  orderBy: 'year',
  size: 2,
  partial: false,
  outputs: {
    delta: { value: 'gas', reduce: delta },
  },
})

export const waterfallRows = waterfall(yearlyChanges, {
  value: 'delta',
  orderBy: 'year',
  total: true,
})

export const createExampleChart = () =>
  defineChart(
    ({ width }) => {
      return {
        marks: [
          barY(waterfallRows, {
            id: 'waterfall-bars',
            x: (datum) =>
              datum.kind === 'total' ? totalLabel : `${datum.year}`,
            y1: 'start',
            y2: 'end',
            color: 'kind',
            key: (datum) =>
              datum.kind === 'total' ? 'net-total' : `${datum.year}`,
            inset: 1,
          }),
          decorative(ruleY([0], { stroke: '#64748b', strokeOpacity: 0.6 })),
        ] as const,
        x: {
          scale: () => scaleBand<string>().padding(0.14),
          axis: { tickLabels: { rotate: width < 560 ? -32 : 0 } },
        },
        y: {
          scale: scaleLinear,
          grid: true,
          axis: { label: 'Change in gasoline price (USD per gallon)' },
        },
        color: {
          domain: kinds,
          range: colors,
          legend: colorLegend({ label: 'Change' }),
        },
      }
    },
    {
      keyboard: true,
      tooltip: {
        use: exampleTooltip,
        format: ({ datum }: ChartPoint<(typeof waterfallRows)[number]>) =>
          datum.kind === 'total'
            ? `${totalLabel} · ${signedAmount.format(datum.end)} net change`
            : `${datum.year} · ${signedAmount.format(
                datum.end - datum.start,
              )} · ${signedAmount.format(datum.end)} running change`,
      },
    },
  )

export const exampleAriaLabel = 'Annual changes in U.S. gasoline prices'

export const chart = createExampleChart()

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