---
layout: post
title: "How to create a serperntine bar plot using R and ggplot2 to recreate a DuBois data portrait (CC345)"
blurb: "Recreating Plate 17"
author: "PD Schloss"
date: 2025-02-27 8:00
comments: false
youtube: 0DTVMv_LakQ
start_hash: 
end_hash: 
---

Pat uses R to recreate an iconic bar plot figure that WEB DuBois presented at the 1900 Paris Exposition showing number of Black students in Georgia taking different courses of study using the ggplot2, dplyr, and showtext packages. The functions he uses from these packages include aes, coord_equal, cos, cumsum, diff, element_blank, element_text, filter, font_add, function, geom_polygon, geom_text, ggplot, ggsave, labs, last, library, map, mutate, nest, paste, rep, rev, row_number, seq, showtext_auto, showtext_opts, sin, sqrt, 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)


get_serpentine <- function(start_position, total_length, y_offset,
                           outer = 60, thickness = 15, longest = 1000){
  
  inner <- outer - thickness
  
  data <- tibble(
    x_outer = c(seq(0, longest - outer, length.out = 1000),
          longest - outer + outer * cos(seq(pi/2, -pi/2, length.out = 1000)),
          seq(longest - outer, outer + thickness, length.out = 1000),
          inner * cos(seq(pi/2, 3*pi/2, length.out = 1000)) + outer,
          seq(outer + thickness, longest, length.out = 1000)
          ),
    y_outer = c(rep(0, 1000),
          outer * sin(seq(pi/2, -pi/2, length.out = 1000)) - outer,
          rep(- 2 * outer, length.out = 1000),
          inner * sin(seq(pi/2, 3*pi/2, length.out = 1000)) - 2 * outer - inner,
          rep(-2 * outer - 2 * inner, length.out = 1000)
          ) + y_offset,
    x_inner = c(seq(0, longest - outer, length.out = 1000),
          longest - outer + inner * cos(seq(pi/2, -pi/2, length.out = 1000)),
          seq(longest - outer, outer, length.out = 1000),
          outer * cos(seq(pi/2, 3*pi/2, length.out = 1000)) + outer,
          seq(outer, longest, length.out = 1000)
          ),
    y_inner = c(rep(-thickness, 1000),
          inner * sin(seq(pi/2, -pi/2, length.out = 1000)) - thickness - inner,
          rep(-(2 * inner + thickness), 1000),
          outer * sin(seq(pi/2, 3*pi/2, length.out = 1000)) - (thickness + 2 * inner + outer),
          rep(-(thickness + 2*inner + 2*outer), 1000)) + y_offset,
    length = cumsum(c(0, sqrt(diff(x_outer)^2 + diff(y_outer)^2)))
    ) %>%
    filter(length >= start_position & length <= total_length + start_position)

  tibble(x = c(data$x_outer, last(data$x_outer), rev(data$x_inner)),
         y = c(data$y_outer, last(data$y_inner), rev(data$y_inner))
  )
}

thickness <- 15

basic_data <- tribble(
  ~course, ~n_students, ~start,
  "BUSINESS",       12,   175,
  "CLASSICAL",      98,   200,
  "PROFESSIONAL",  152,   250,
  "SCIENTIFIC",    161,   190,
  "NORMAL",        383,   190,
  "INDUSTRIAL",   2252,   230) %>%
  mutate(y_position = 150 * (9 - row_number()),
         course_n_students = paste(course, n_students, sep = "    "))

basic_data %>%
  nest(data = -course) %>%
  mutate(serpentine = map(data, ~get_serpentine(.x$start,
                                                .x$n_students,
                                                .x$y_position,
                                                thickness = thickness))) %>%
  unnest(c(data, serpentine)) %>%
  ggplot(aes(x = x, y = y, group = course)) +
  geom_polygon(color = "black", fill = "#d2b48c", linewidth = 0.2) +
  geom_text(data = basic_data,
            aes(x = 0, y = y_position - thickness / 2,
                label = course_n_students),
            hjust = 0,
            family = "vasarely", size = 8, size.unit = "pt") +
  coord_equal(expand = FALSE, clip = "off") +
  labs(
    title = "NUMBER OF NEGRO STUDENTS TAKING\nTHE VARIOUS COURSES OF STUDY\nOFFERED IN GEORGIA SCHOOLS ."
  ) +
  theme(
    axis.text = element_blank(),
    axis.ticks = element_blank(),
    axis.title = element_blank(),
    plot.margin = margin(l = 15, r = 15, b = 40),
    plot.title = element_text(hjust = 0.5, family = "b52", size = 12,
                              margin = margin(b = 20)),
    panel.grid = element_blank(),
    panel.background = element_blank()
  )

ggsave("plate_17.png", width = 5, height = 6.31, unit = "in")
```