Source

91 chart lines · 1 file · 2.5 kBBenchmark harness excluded · shared/mount.ts

1983 ASA automobile data406 records · CSV · 17.4 kB
@charts-poc/demo-data/cars

Selection: Complete published snapshot

name
string
economy (mpg)
number | null
cylinders
number
displacement (cc)
number
power (hp)
number | null
weight (lb)
number
0-60 mph (s)
number
year
number

1983 ASA Data Exposition@observablehq/sample-datasets@1.0.1 · revision 732c0148de74 · cars.csv · ISC distribution; upstream source credited · SHA-256 eaad8395d37ePinned snapshot

cases/43-hexbin-density/tanstack.ts91 lines · entry
cases/43-hexbin-density/tanstack.ts
import { cars } from '@charts-poc/demo-data/cars'
import { defineChart, hexagon } from '@tanstack/charts'
import { hexbin } from 'd3-hexbin'
import { scaleLinear, scaleThreshold } from 'd3-scale'
import { tanstackMount } from '../../shared/mount'
import type { CarsRow } from '@charts-poc/demo-data/cars'
import type { ConformanceInput } from '../../types'

type CompleteCar = CarsRow & {
  readonly 'economy (mpg)': number
}

interface HexbinCell {
  id: string
  x: number
  y: number
  count: number
}

const margin = { top: 20, right: 20, bottom: 40, left: 48 } as const
const colors = ['#dbeafe', '#93c5fd', '#3b82f6', '#1d4ed8']
const coordinate = new Intl.NumberFormat('en-US', {
  maximumFractionDigits: 1,
})

const definition = (input: ConformanceInput) => {
  const rows = cars
    .filter((row): row is CompleteCar => row['economy (mpg)'] !== null)
    .slice(input.revision * 8, input.revision * 8 + 360)

  return defineChart(({ width, height }) => {
    const xScale = scaleLinear()
      .domain([1500, 5500])
      .range([margin.left, width - margin.right])
    const yScale = scaleLinear()
      .domain([5, 50])
      .range([height - margin.bottom, margin.top])
    const bins = hexbin<CompleteCar>()
      .x((row) => xScale(row['weight (lb)']))
      .y((row) => yScale(row['economy (mpg)']))
      .radius(24 / Math.sqrt(3))
      .extent([
        [margin.left, margin.top],
        [width - margin.right, height - margin.bottom],
      ])(rows)
    const cells: readonly HexbinCell[] = bins.map((bin) => ({
      id: `${bin.x}:${bin.y}`,
      x: xScale.invert(bin.x),
      y: yScale.invert(bin.y),
      count: bin.length,
    }))

    return {
      marks: [
        hexagon(cells, {
          x: 'x',
          y: 'y',
          color: 'count',
          r: 11,
          stroke: '#ffffff',
          strokeWidth: 0.75,
        }),
      ],
      x: {
        scale: scaleLinear().domain([1500, 5500]),
        grid: true,
        axis: { label: 'Weight (lb)' },
      },
      y: {
        scale: scaleLinear().domain([5, 50]),
        grid: true,
        axis: { label: 'Fuel economy (mpg)' },
      },
      color: {
        scale: scaleThreshold<number, string>,
        domain: [5, 12, 24],
        range: colors,
      },
      margin,
    }
  })
}

export const mount = tanstackMount(
  definition,
  'Hexagonally binned point density',
  {
    format: (point) =>
      `Bin center: ${coordinate.format(point.datum.x)} lb, ${coordinate.format(point.datum.y)} mpg · Cars: ${point.datum.count}`,
  },
)