How to recreate DuBois's bullseye plot from the 1900 Paris Exposition using R and ggplot2 (CC343)

February 20, 2025 • PD Schloss • 12 min read

Pat uses R to recreate an iconic bullseye plot figure that WEB DuBois presented at the 1900 Paris Exposition showing the accumulation of Black Georgians using tools from the ggplot2, dplyr, and showtext packages. The functions he uses from these packages include aes, c, case_when, coord_radial, factor, font_add, format, geom_area, geom_text, ggplot, ggsave, labs, library, max, mutate, paste0, pivot_longer, scale_fill_manual, scale_x_continuous, scale_y_continuous, showtext_auto, showtext_opts, theme, tibble, and tribble. Pat’s newsletter describing how he would go about generating the OK GO figure can be found here. The book Pat mentions by Whitney Battle-Baptiste and Britt Rusert, titled “W.E.B. Du Bois’s Data Portraits: Visualizing Black America” is available at Amazon. A great set of talks about the DuBois data portraits is available here. The Anthony Starks GitHub repository can be found here. If you have a figure that you would like to see me discuss in a future newsletter and episode of Code Club, email me at pat@riffomonas.org!

Code

library(tidyverse)
library(showtext)

font_add("b52", regular = "B52-ULC W00 ULC.ttf")
font_add("vasarely", regular = "vasarely-light.otf")
showtext_auto()
showtext_opts(dpi = 300)

fractions <- tribble(
  ~year, ~valuation, ~year_pos, ~wedge_pos, ~label_pos,
  1875, 5393885, 0.36, 1, 0,
  1880, 5764293, 0.415, 625, 0.33,
  1885, 8153390, 0.58, 375, 0.4,
  1890, 12322003, 0.88, 250, 0.6,
  1895, 12941230, 0.94, 125, 0.78,
  1900, 13447423, 0.98, 875, 0.81
) %>%
  mutate(fraction = valuation / max(valuation),
         pretty_value = paste0("$", 
                               format(valuation, big.mark = ",", trim = TRUE)))

tibble(
  x = 1:1000,
  `1875` = case_when(x > 100 & x < 150 ~ 0.2 + 0.0002 * (x - 125)^2,
                     x > 225 & x < 275 ~ 0.2 + 0.0002 * (x - 250)^2,
                     x > 350 & x < 400 ~ 0.2 + 0.0002 * (x - 375)^2,
                     x > 600 & x < 650 ~ 0.2 + 0.0002 * (x - 625)^2,
                     x > 850 & x < 900 ~ 0.2 + 0.0002 * (x - 875)^2,
                     TRUE ~ 0.401),
  `1880` = case_when(x > 105 & x < 145 ~ 0.02 + 0.0002 * (x - 125)^2,
                     x > 230 & x < 270 ~ 0.02 + 0.0002 * (x - 250)^2,
                     x > 355 & x < 395 ~ 0.02 + 0.0002 * (x - 375)^2,
                     x > 855 & x < 895 ~ 0.02 + 0.0002 * (x - 875)^2,
                     TRUE ~ 0.429 - `1875`),
  `1885` = case_when(x > 110 & x < 140 ~ 0.02 + 0.0002 * (x - 125)^2,
                     x > 235 & x < 265 ~ 0.02 + 0.0002 * (x - 250)^2,
                     x > 860 & x < 890 ~ 0.02 + 0.0002 * (x - 875)^2,
                     TRUE ~ 0.606 - `1880` - `1875`), 
  `1890` = case_when(x > 112 & x < 138 ~ 0.02 + 0.0001 * (x - 125)^2,
                     x > 862 & x < 888 ~ 0.02 + 0.0001 * (x - 875)^2,
                     TRUE ~ 0.916 - `1885` - `1880` - `1875`),
  `1895` = case_when(x > 863 & x < 887 ~ 0.02 + 0.0001 * (x - 875)^2,
                     TRUE ~ 0.962 - `1890` - `1885` - `1880` - `1875`),
  `1900` = 1 - `1895` - `1890` - `1885` - `1880` - `1875`
  ) %>% 
  pivot_longer(-x, names_to = "year", values_to = "fraction") %>%
  mutate(year = factor(year, levels = seq(1900, 1875, -5))) %>%
  ggplot(aes(x = x, y = fraction, fill = year)) + 
  geom_area(show.legend = FALSE) +
  geom_text(data = fractions,
            aes(x = 500, y = year_pos, label = year), inherit.aes = FALSE,
            color = c("white", "black", "white", "black", "black", "black"),
            family = c("b52", "vasarely", "b52",
                       "vasarely", "vasarely", "vasarely"),
            size.unit = "pt",
            size = c(6, 4, 6, 6, 5, 5) ) +
  geom_text(data = fractions,  inherit.aes = FALSE,
            aes(x = wedge_pos, y = label_pos, label = pretty_value),
            family = c("b52", "vasarely", "b52",
                       "vasarely", "vasarely", "vasarely"),
            size = c(6, 4, 6, 7, 7, 7), size.unit = "pt",
            angle = c(0, 45, -45, 0, 45, -45),
            color = c("white", "black", "white", "black", "black", "black")) +
  coord_radial(expand = FALSE) + 
  scale_x_continuous(limits = c(1, 1000)) +
  scale_y_continuous(limits = c(0, 1.00001)) +
  scale_fill_manual(breaks = seq(1875, 1900, 5),
                    values = c("#000000", "#d2b48c", "#4682b4", "#ffd700",
                               "gray70", "#dc143c")) +
  labs(title = "ASSESSED VALUATION OF ALL TAXABLE PROPERTY
       OWNED BY GEORGIA NEGROES.") +
  theme(
    plot.title = element_text(hjust = 0.5, family = "b52", size = 12),
    plot.margin = margin(t = 20, b = 30),
    axis.text = element_blank(),
    axis.ticks = element_blank(),
    axis.title = element_blank()
  )


ggsave("panel_22.png", width = 5, height = 6)