Source

130 chart lines · 2 files · 3.5 kBBenchmark harness excluded · shared/mount.ts

U.S. county unemployment3,219 records · CSV · 109.8 kB
@charts-poc/demo-data/us-county-unemployment

Selection: Complete published snapshot

id
number
state
string
county
string
rate
number

U.S. Bureau of Labor Statisticsobservablehq/plot@356f579b1d94 · revision 356f579b1d94 · test/data/us-county-unemployment.csv · Observable Plot repository MIT; upstream source credited · SHA-256 dd3cc76e5e21Pinned snapshot

cases/98-needle-gauge/tanstack.ts104 lines · entry
cases/98-needle-gauge/tanstack.ts
import { usCountyUnemployment } from '@charts-poc/demo-data/us-county-unemployment'
import { defineChart } from '@tanstack/charts'
import {
  polar,
  radialArc,
  radialDot,
  radialRule,
  radialText,
} from '@tanstack/charts/polar'
import { scaleLinear } from 'd3-scale'
import { pie } from 'd3-shape'
import {
  gaugeBands,
  gaugeMaximum,
  gaugeTicks,
  type GaugeBand,
} from './transform'
import { tanstackMount } from '../../shared/mount'
import type { ConformanceInput } from '../../types'

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

const definition = (input: ConformanceInput) => {
  const reading = usCountyUnemployment[(input.revision % 2) * 2]
  if (!reading) {
    throw new Error('County unemployment data is incomplete.')
  }
  const arcs = pieLayout([...gaugeBands])

  return defineChart({
    marks: [
      polar({
        angle: { scale: angleScale },
        radius: { scale: radiusScale },
        startAngle,
        endAngle,
        inset: 0,
        radiusRatio: 0.82,
        marks: [
          radialArc(arcs, {
            startAngle: 'startAngle',
            endAngle: 'endAngle',
            padAngle: 'padAngle',
            innerRadius: ({ radius }) => radius * 0.72,
            color: ({ data }) => data.id,
          }),
          radialRule(gaugeTicks, {
            angle: 'value',
            radius1: 0.76,
            radius2: 0.94,
            stroke: '#ffffff',
            strokeOpacity: 0.85,
            strokeWidth: 2,
          }),
          radialRule([reading], {
            angle: 'rate',
            radius1: 0,
            radius2: 0.64,
            stroke: 'currentColor',
            strokeWidth: 4,
          }),
          radialDot([reading], {
            angle: 'rate',
            radius: 0,
            r: 8,
            fill: 'currentColor',
          }),
          radialText([reading], {
            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,
  })
}

export const mount = tanstackMount(definition, 'County unemployment gauge', {
  format: ({ datum }) => {
    if ('data' in datum) {
      return `${datum.data.label} · ${datum.data.value} percentage-point band`
    }
    return `${datum.county}, ${datum.state} · ${datum.rate}% unemployment`
  },
})
cases/98-needle-gauge/transform.ts26 lines · support
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,
  }),
)