TanStack
Catalog

Ridgeline density comparison

distribution

118 lines · 2 files · 3.0 kB

cases/62-ridgeline-density/example.tsx99 lines · entry
cases/62-ridgeline-density/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'

import { simpsons } from '@tanstack/charts-data/simpsons'
import {
  binX,
  d3Curve,
  defineChart,
  normalize,
  ridgelineY,
  ruleY,
} from '@tanstack/charts'
import { scaleLinear, scalePoint } from 'd3-scale'
import { curveBasis } from 'd3-shape'
import { isRatedEpisode, ratingBoundaries, ridgeSeasons } from './selection'
import type { RatedEpisode } from './selection'

const colors = ['#2563eb', '#0d9488', '#d97706']

export const createExampleChart = (input: ChartOptions) => {
  const seasons = ridgeSeasons(input.revision)
  const episodes = simpsons.filter(
    (row): row is RatedEpisode =>
      isRatedEpisode(row) && seasons.includes(row.season),
  )
  const bins = binX(episodes, {
    value: 'imdb_rating',
    by: 'season',
    thresholds: ratingBoundaries,
    outputs: { count: { reduce: 'count' } },
  })
  const rows = normalize(bins, {
    value: 'count',
    by: 'season',
    basis: 'max',
    as: 'height',
  })
  const overlap = 0.78
  const curve = d3Curve(curveBasis)

  return defineChart(
    {
      marks: [
        ruleY(seasons, {
          id: 'season-guides',
          stroke: '#94a3b8',
          strokeOpacity: 0.5,
        }),
        ridgelineY(rows, {
          id: 'rating-ridges',
          x: 'x',
          y: 'season',
          height: 'height',
          key: (row) => `${row.season}:${row.x}`,
          overlap,
          color: 'season',
          fillOpacity: 0.52,
          strokeWidth: 1.5,
          curve,
        }),
      ],
      scales: {
        x: {
          scale: scaleLinear().domain([4, 10]),
          grid: true,
          axis: { label: 'IMDb rating' },
        },
        y: {
          scale: scalePoint<number>().domain(seasons).padding(overlap),
          reverse: true,
          axis: {
            ticks: {
              values: seasons,
              format: (season) => `Season ${season}`,
            },
          },
        },
      },

      color: {
        range: colors,
      },
    },
    { keyboard: true, tooltip: exampleTooltip },
  )
}
export interface ChartOptions {
  revision: number
}

export const exampleAriaLabel = 'Ridgeline density comparison'

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

export default function Example() {
  return <Chart ariaLabel={exampleAriaLabel} definition={chart} height={480} />
}
cases/62-ridgeline-density/selection.ts19 lines · dependency
cases/62-ridgeline-density/selection.ts
import type { SimpsonsRow } from '@tanstack/charts-data/simpsons'

export type RatedEpisode = SimpsonsRow & {
  readonly imdb_rating: number
}

export const ratingBoundaries = [
  4, 4.25, 4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6.25, 6.5, 6.75, 7, 7.25, 7.5,
  7.75, 8, 8.25, 8.5, 8.75, 9, 9.25, 9.5, 9.75, 10,
] as const

export function isRatedEpisode(row: SimpsonsRow): row is RatedEpisode {
  return row.imdb_rating !== null
}

export function ridgeSeasons(revision: number): readonly number[] {
  const offset = revision % 2
  return [1 + offset, 10 + offset, 20 + offset]
}