TanStack
Catalog

Two-period slopegraph

change

120 lines · 2 files · 3.3 kB

cases/30-slopegraph/example.tsx113 lines · entry
cases/30-slopegraph/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'

import { citywages } from '@tanstack/charts-data/citywages'
import { defineChart, dot, lineY, select, text } from '@tanstack/charts'
import { fold } from '@tanstack/charts/transform/fold'
import { scaleBand, scaleLinear } from 'd3-scale'
import { wageFields, wageYear } from './selection'

const colors = [
  '#2563eb',
  '#f97316',
  '#10b981',
  '#8b5cf6',
  '#ec4899',
  '#06b6d4',
  '#ca8a04',
  '#64748b',
]
const previewLabelOffsets = new Map([
  ['Houston', 7],
  ['New York', 12],
  ['Los Angeles', 18],
  ['Washington, D.C.', 12],
  ['Dallas', 6],
  ['Chicago', 0],
  ['Philadelphia', 12],
  ['Miami', 24],
])

export const createExampleChart = (input: ChartOptions) => {
  const source = citywages.slice(input.revision * 4, input.revision * 4 + 8)
  const rows = fold(source, {
    fields: wageFields,
    as: { key: 'wageField', value: 'inequality' },
  })
  const labels = select(rows, {
    by: 'Metro',
    select: 'last',
  })

  return defineChart(
    {
      marks: [
        lineY(rows, {
          id: 'metro-lines',
          x: ({ wageField }) => wageYear(wageField),
          y: 'inequality',
          color: 'nyt_display',
          key: ({ Metro, wageField }) => `${Metro}:${wageField}`,
        }),
        dot(rows, {
          id: 'metro-points',
          x: ({ wageField }) => wageYear(wageField),
          y: 'inequality',
          color: 'nyt_display',
          key: ({ Metro, wageField }) => `${Metro}:${wageField}`,
          r: 3,
        }),
        text(labels, {
          id: 'endpoint-labels',
          x: ({ wageField }) => wageYear(wageField),
          y: 'inequality',
          text: 'nyt_display',
          color: 'nyt_display',
          key: ({ Metro, wageField }) => `${Metro}:${wageField}`,
          dx: ({ wageField }) =>
            input.preview === true && wageField === wageFields[1] ? -6 : 6,
          anchor: ({ wageField }) =>
            input.preview === true && wageField === wageFields[1]
              ? 'end'
              : 'start',
          dy: ({ nyt_display }) =>
            input.preview === true
              ? (previewLabelOffsets.get(nyt_display) ?? 0)
              : 0,
          fontSize: input.preview === true ? 9 : 12,
        }),
      ],
      scales: {
        x: {
          scale: () => scaleBand<string>().paddingInner(0.2).paddingOuter(0.08),
        },
        y: {
          scale: scaleLinear,
          grid: true,
          axis: { label: '90th/10th percentile wage ratio' },
        },
      },

      color: {
        range: colors,
      },
      margin: { right: 76 },
    },
    { keyboard: true, tooltip: exampleTooltip },
  )
}
export interface ChartOptions {
  revision: number
  preview?: boolean
}

export const exampleAriaLabel = 'Metropolitan wage inequality, 1980 to 2015'

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

export default function Example() {
  return <Chart ariaLabel={exampleAriaLabel} definition={chart} height={480} />
}
cases/30-slopegraph/selection.ts7 lines · dependency
cases/30-slopegraph/selection.ts
export const wageFields = ['R90_10_1980', 'R90_10_2015'] as const

export type WageField = (typeof wageFields)[number]

export function wageYear(field: WageField): '1980' | '2015' {
  return field === 'R90_10_1980' ? '1980' : '2015'
}