---
layout: post
title: "How to recreate DuBois's iconic spiral plot from the 1900 Paris Exposition using R and ggplot2 (CC344)"
blurb: "Recreating Panel 25"
author: "PD Schloss"
date: 2025-02-24 8:00
comments: false
youtube: Tr2uvgpL1kU
start_hash: 
end_hash: 
---

Pat uses R to recreate an iconic spiral plot figure that WEB DuBois presented at the 1900 Paris Exposition showing the accumulation of wealth by Black Georgians using tools from the ggplot2, dplyr, and showtext packages. The functions he uses from these packages include aes, annotate, as.character, bind_rows, coord_equal, cos, cumsum, diff, filter, font_add, format, function, geom_polygon, geom_text, ggplot, ggsave, if_else, labs, library, map, max, mutate, nest, paste0, pull, rev, row_number, scale_fill_manual, select, seq, showtext_auto, showtext_opts, sin, sqrt, summarize, theme, tibble, tribble, and unnest. 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](https://amzn.to/4heJOWZ). A great set of talks about the DuBois data portraits is [available here](https://www.youtube.com/watch?v=ZZST1AZj-dQ&ab_channel=Tableau). The Anthony Starks GitHub repository can be [found here](https://github.com/ajstarks/dubois-data-portraits/). 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!

<iframe style="margin: 0 auto;display:block;" width="560" height="315" src="https://www.youtube.com/embed/{{ page.youtube }}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Code

```R
library(tidyverse)
library(showtext)

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

generate_spiral <- function(i_radius, f_radius,
                            i_theta = 9 * pi / 2, f_theta = 3 * pi / 4,
                            resolution = 1000) {
  
  radii <- seq(i_radius, f_radius, length.out = resolution)
  thetas <- seq(i_theta, f_theta, length.out = resolution)
  
  tibble(
    x = radii * cos(thetas),
    y = radii * sin(thetas),
    length = cumsum(c(0, sqrt(diff(x)^2 + diff(y)^2)))
  ) 
}

i_radius <- 15
f_radius <- 9
thickness <- 0.6

value_data <- tribble(
  ~value,~year,
  21186,1875,
  498532,1880,
  736160,1885,
  1173624,1890,
  1322694,1895,
  1434975,1899) %>%
  mutate(fraction = value / max(value),
         position = thickness * (row_number() - 1),
         pretty_value = format(value, big.mark = ",", trim = TRUE),
         pretty_value = if_else(year == 1875,
                                paste0("$ ", pretty_value),
                                pretty_value),
         pretty_value = if_else(year == 1880,
                                paste0("$  ", pretty_value),
                                pretty_value))


spiral_data <- value_data %>%
  mutate(outer_spiral = map(position,
                            ~generate_spiral(i_radius - .x, f_radius - .x)),
         inner_spiral = map(position,
                            ~generate_spiral(i_radius - .x - thickness,
                                             f_radius - .x - thickness))
         ) %>%
  unnest(c(outer_spiral, inner_spiral), names_sep = "_")

reference_length <- spiral_data %>%
  filter(year == 1899) %>%
  summarize(max = max(outer_spiral_length)) %>%
  pull(max)

spiral_data %>%
  mutate(max_length = reference_length * fraction) %>%
  filter(outer_spiral_length <= max_length) %>%
  mutate(inner_spiral_x = rev(inner_spiral_x),
         inner_spiral_y = rev(inner_spiral_y), .by = year) %>%
  nest(spiral = -c(year, value, fraction, position)) %>%
  mutate(x_y = map(spiral,
                   ~bind_rows(select(.x, x = outer_spiral_x, y = outer_spiral_y),
                              select(.x, x = inner_spiral_x, y = inner_spiral_y)
                             ))) %>%
  unnest(x_y) %>%
  ggplot(aes(x = x, y = y, group = year, fill = as.character(year))) +
  geom_polygon(color = "black", linewidth = 0.2,
               show.legend = FALSE) +
  geom_text(data = value_data,
            aes(x = -0.5, y = i_radius - position - thickness / 2,
                label = pretty_value),
            family = "vasarely", size = 7, size.unit = "pt", hjust = 1,
            inherit.aes = FALSE) +
  geom_text(data = value_data,
            aes(x = -6, y =  i_radius - position - thickness / 2,
                label = year),
            family = "vasarely", size = 7, size.unit = "pt", hjust = 0.5,
            inherit.aes = FALSE) +
  annotate(geom = "segment",
           x = -5.1,
           xend = c(-3, rep(-3.9, 5)),
           y = seq(i_radius,
                   i_radius - thickness * 5,
                   -thickness) - thickness/2,
           linewidth = 0.1) +
  annotate(geom = "text",
           label = c("", "", rep('"', 4)),
           x = -3.65,
           y = seq(i_radius,
                   i_radius - thickness * 5,
                   -thickness) - thickness/2,
           family = "vasarely", size = 7, size.unit = "pt"
  ) +
  coord_equal(expand = FALSE, clip = "off") +
  scale_fill_manual(breaks = as.character(c(seq(1875, 1895, 5), 1899)),
                    values = c("#ffc0cb", "#4682b4", "#d2b48c", "#ffd700",
                               "gray80", "#dc143c")) +
  labs(
    title = "ASSESSED VALUE OF HOUSEHOLD AND KITCHEN FURNITURE\nOWNED BY GEORGIA NEGROES.",
    x = NULL,
    y = NULL
  ) +
  theme(
    axis.text = element_blank(),
    axis.ticks = element_blank(),
    panel.grid = element_blank(),
    panel.background = element_blank(),
    plot.title = element_text(family = "b52", size = 12, hjust = 0.5,
                              margin = margin(t = 20, b = 40)),
    plot.margin = margin(b = 80, l = 30, r = 30)
  )

ggsave("plate_25.png", width = 5, height = 6.41, unit = "in")
```