Skip to contents

Pattern fill overlays give choropleth maps a second encoding channel that works independently of colour. The result reads correctly in grayscale, in PDF, and for readers with colour-vision deficiency — three contexts where a colour-only map fails.

North Carolina counties

The North Carolina county dataset bundled with the sf package lets us encode two census variables simultaneously: county area (AREA, continuous) mapped to fill colour, and 1974 birth-rate tertile (BIR74, cut into three equal-count bins) mapped to pattern. Readers can see both dimensions at once — the pattern distinguishes high-birth-rate counties regardless of whether they also happen to be large or small.

library(ggplot2)
library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.4.0; sf_use_s2() is TRUE
library(ggpatchy)

nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)

# Quantile-based tertiles give balanced bin sizes across skewed BIR74 data
nc$birth_bin <- cut(
  nc$BIR74,
  breaks        = quantile(nc$BIR74, probs = c(0, 1/3, 2/3, 1)),
  include.lowest = TRUE,
  labels        = c("Low", "Medium", "High")
)

ggplot(nc) +
  geom_sf_pattern(
    aes(fill = AREA, pattern = birth_bin),
    pattern_colour    = "grey30",
    pattern_linewidth = 0.4,
    pattern_spacing   = 3
  ) +
  scale_pattern_manual(
    values = c(Low = "hatch", Medium = "crosshatch", High = "dots"),
    name   = "Birth rate (1974)"
  ) +
  scale_fill_distiller(palette = "Oranges", name = "Area") +
  labs(
    title    = "North Carolina counties",
    subtitle = "Fill: county area  ·  Pattern: 1974 birth-rate tertile"
  ) +
  theme_minimal() +
  theme(axis.text = element_blank())

US states

The spData package’s us_states dataset maps 2015 population (total_pop_15) to fill colour and US Census region to pattern. Four patterns — one per region — distinguish Midwest, Northeast, South, and West without adding a second colour scale. The combination lets readers ask two questions at once: how populous is this state? (colour) and which region is it in? (pattern). Both questions are answerable from a black-and-white printout.

library(spData)
#> To access larger datasets in this package, install the spDataLarge
#> package with: `install.packages('spDataLarge',
#> repos='https://nowosad.github.io/drat/', type='source')`

# Fix a known typo in the REGION factor level
us <- us_states
levels(us$REGION)[levels(us$REGION) == "Norteast"] <- "Northeast"

ggplot(us) +
  geom_sf_pattern(
    aes(fill = total_pop_15, pattern = REGION),
    pattern_colour    = "grey20",
    pattern_linewidth = 0.45,
    pattern_spacing   = 5
  ) +
  scale_pattern_manual(
    values = c(
      Midwest   = "hatch",
      Northeast = "crosshatch",
      South     = "dots",
      West      = "vertical"
    ),
    name = "Region"
  ) +
  scale_fill_distiller(
    palette   = "Blues",
    direction = 1,
    name      = "Population (2015)",
    labels    = scales::comma
  ) +
  labs(
    title    = "US states",
    subtitle = "Fill: 2015 population  ·  Pattern: Census region"
  ) +
  theme_minimal()