React Simple Maps

<-Examples
US counties choropleth map

Unemployment rate by US county012345678%+
Examples

US counties choropleth map

The US counties choropleth map example shows how to implement a threshold choropleth map with React Simple Maps. This is a complete example showing robust data fetching, color scale configuration, legend design, and map implementation. This example is an evolution of the basic working with data tutorial in the docs.

You can download the counties TopoJSON and the sample unemployment data below.

Note that for this example we are using swr for data fetching. You can load your data asynchronously whichever way you want. There is a basic version of this in the working with data tutorial. From React Simple Maps, you only need the react-simple-maps/core library to render this map.

"use client"

import * as React from "react"
import useSWR from "swr"
import { csvParse } from "d3-dsv"
import { scaleQuantize } from "d3-scale"
import { ComposableMap, Geographies, Geography } from "react-simple-maps/core"

const geoUrl = "/maps/us-counties-2025.json"
const dataUrl = "/data/unemployment-by-county-2025.csv"

/**
 * Hook to load data asynchronously
 *
 */
function useUnemploymentData(src: string): Map<string, number> {
  // You can fetch data manually, but `swr` provides a more
  // robust framework for data fetching and edge case handling
  const { data } = useSWR(src, (url) =>
    fetch(url).then((res) => {
      if (!res?.ok) throw new Error(`Failed to fetch ${src}: ${res.status}`)
      return res.text()
    })
  )

  return React.useMemo(() => {
    if (!data) return new Map([])
    const rows = csvParse(data, ({ id, rate }) => ({ id, rate: +rate }))
    return new Map(rows.map((d) => [d.id, d.rate]))

    // NOTE: `data` can be used in the dependency array because it is
    // a simple value `string | undefined`
  }, [data])
}

/**
 * Choropleth map
 *
 */
export default function UsCountiesChoroplethMap() {
  const width = 800
  const height = 450

  const rates = useUnemploymentData(dataUrl)

  /**
   * Define color scale
   *
   */

  const noDataFill = "#EEEEEE"

  const colorScale = React.useCallback(
    scaleQuantize<string>()
      .domain([0, 9])
      .range([
        "#ffedea",
        "#ffcec5",
        "#ffad9f",
        "#ff8a75",
        "#ff5533",
        "#e2492d",
        "#be3d26",
        "#9a311f",
        "#782618",
      ]),
    []
  )

  /**
   * Create legend boxes
   *
   */
  const boxHeight = 8
  const boxWidth = 24

  const colors = colorScale.range()

  const steps = colors.map((color, i) => {
    const extent = colorScale.invertExtent(color)
    const value = extent[0]
    const label = i === colors.length - 1 ? `${value}%+` : `${value}`
    return { color, label, x: i * boxWidth }
  })

  /**
   * Legend placement
   *
   */
  const legendWidth = steps.length * boxWidth
  const legendX = width / 2 - legendWidth / 2

  return (
    <ComposableMap
      width={width}
      height={height}
      projection="geoAlbersUsa"
      projectionConfig={{ scale: 800 }}
    >
      <g fontSize={10} transform={`translate(${legendX} 32)`}>
        <text alignmentBaseline="baseline" y={-6}>
          {"Unemployment rate by US county"}
        </text>
        {steps.map(({ x, color, label }) => {
          return (
            <g key={label} transform={`translate(${x} 0)`}>
              <rect height={boxHeight} width={boxWidth} fill={color} />
              <text alignmentBaseline="hanging" x={-2} y={boxHeight + 4}>
                {label}
              </text>
            </g>
          )
        })}
      </g>

      <Geographies geography={geoUrl}>
        {({ geographies }) =>
          geographies.map((geo) => {
            const rate = rates.get(geo.properties?.id) || null
            return (
              <Geography
                key={geo.rsmKey}
                geography={geo}
                fill={rate == null ? noDataFill : colorScale(rate)}
              />
            )
          })
        }
      </Geographies>
    </ComposableMap>
  )
}