TanStack
Catalog

County unemployment gauge

polar

156 lines · 2 files · 4.0 kB

cases/98-needle-gauge/example.tsx130 lines · entry
cases/98-needle-gauge/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'

import { usCountyUnemployment } from '@tanstack/charts-data/us-county-unemployment'
import { defineChart } from '@tanstack/charts'
import {
  pie,
  polar,
  radialArc,
  radialDot,
  radialRule,
  radialText,
} from '@tanstack/charts/polar'
import { scaleLinear } from 'd3-scale'
import {
  gaugeBands,
  gaugeMaximum,
  gaugeTicks,
  type GaugeBand,
} from './transform'

const startAngle = -Math.PI / 2
const endAngle = Math.PI / 2
const angleScale = scaleLinear().domain([0, gaugeMaximum])
const radiusScale = scaleLinear().domain([0, 1])
const arcs = pie(gaugeBands, {
  value: 'value',
  startAngle,
  endAngle,
})
const bandIds: readonly GaugeBand['id'][] = ['low', 'elevated', 'high']
const bandColors = ['#22c55e', '#f59e0b', '#ef4444']

export const createExampleChart = (input: ChartOptions) => {
  const reading = usCountyUnemployment[(input.revision % 2) * 2]
  if (!reading) {
    throw new Error('County unemployment data is incomplete.')
  }

  return defineChart(
    {
      marks: [
        polar({
          angle: { scale: angleScale },
          radius: { scale: radiusScale },
          startAngle,
          endAngle,
          radiusRatio: 0.82,
          marks: [
            radialArc(arcs, {
              id: 'gauge-bands',
              key: 'id',
              innerRadius: ({ radius }) => radius * 0.72,
              color: 'id',
            }),
            radialRule(gaugeTicks, {
              id: 'gauge-ticks',
              key: 'id',
              angle: 'value',
              radius1: 0.76,
              radius2: 0.94,
              stroke: '#ffffff',
              strokeOpacity: 0.85,
              strokeWidth: 2,
            }),
            radialRule([reading], {
              id: 'gauge-needle',
              key: 'id',
              angle: 'rate',
              radius1: 0,
              radius2: 0.64,
              stroke: 'currentColor',
              strokeWidth: 4,
            }),
            radialDot([reading], {
              id: 'gauge-hub',
              key: 'id',
              angle: 'rate',
              radius: 0,
              r: 8,
              fill: 'currentColor',
            }),
            radialText([reading], {
              id: 'gauge-value',
              key: 'id',
              angle: 'rate',
              radius: 0,
              text: (row) => `${row.rate}%`,
              dy: 34,
              anchor: 'middle',
              baseline: 'middle',
              fill: 'currentColor',
              fontSize: 18,
              fontWeight: 700,
            }),
          ],
        }),
      ],
      color: { domain: bandIds, range: bandColors },
      margin: 0,
    },
    {
      keyboard: true,
      tooltip: {
        use: exampleTooltip,
        ...{
          format: ({ datum }) => {
            if ('label' in datum) {
              return `${datum.label} · ${datum.value} percentage-point band`
            }
            return `${datum.county}, ${datum.state} · ${datum.rate}% unemployment`
          },
        },
      },
    },
  )
}
export interface ChartOptions {
  revision: number
}

export const exampleAriaLabel = 'County unemployment gauge'

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

export default function Example() {
  return <Chart ariaLabel={exampleAriaLabel} definition={chart} height={480} />
}
cases/98-needle-gauge/transform.ts26 lines · dependency
cases/98-needle-gauge/transform.ts
export interface GaugeBand {
  id: 'low' | 'elevated' | 'high'
  label: string
  value: number
}

export interface GaugeTick {
  id: string
  value: number
}

export const gaugeMaximum = 30

export const gaugeBands: readonly GaugeBand[] = [
  { id: 'low', label: 'Below 8%', value: 8 },
  { id: 'elevated', label: '8–15%', value: 7 },
  { id: 'high', label: 'Above 15%', value: 15 },
]

export const gaugeTicks: readonly GaugeTick[] = Array.from(
  { length: 11 },
  (_, index) => ({
    id: `tick-${index}`,
    value: index * 3,
  }),
)