Graphic Design with ggplot2

Working with Labels and Annotations

Cédric Scherer // rstudio::conf // July 2022

Setup

library(tidyverse)

bikes <- readr::read_csv(
  here::here("data", "london-bikes-custom.csv"),
  col_types = "Dcfffilllddddc"
)

#bikes$season <- factor(bikes$season, levels = c("spring", "summer", "autumn", "winter"))
bikes$season <- forcats::fct_inorder(bikes$season)

theme_set(theme_light(base_size = 14, base_family = "Roboto Condensed"))

theme_update(
  panel.grid.minor = element_blank(),
  legend.position = "top"
)

Labels + theme()

Working with Labels

g <- ggplot(
    bikes,
    aes(x = temp_feel, y = count,
        color = season)
  ) +
  geom_point(
    alpha = .5
  ) +
  labs(
    x = "Feels Like temperature (°F)",
    y = "Reported bike shares",
    title = "TfL bike sharing trends",
    subtitle = "Reported bike rents versus Feels Like temperature in London",
    caption = "Data: TfL",
    color = "Season:",
    tag = "1."
  )

g

Customize Labels via `theme()`

g + theme(
  plot.title = element_text(face = "bold"),
  plot.title.position = "plot"
)

Customize Labels via `theme()`

g + theme(
  plot.title = element_text(face = "bold"),
  plot.title.position = "plot",
  axis.text = element_text(
    color = "#28a87d"
  )
)

Customize Labels via `theme()`

g + theme(
  plot.title = element_text(face = "bold"),
  plot.title.position = "plot",
  axis.text = element_text(
    color = "#28a87d",
    family = "Tabular",
    face = "italic",
    hjust = 1,
    vjust = 0,
    angle = 45,
    lineheight = 1.3, ## no effect here
    margin = margin(10, 0, 20, 0)
  )
)

Customize Labels via `theme()`

g + theme(
  plot.title = element_text(face = "bold"),
  plot.title.position = "plot",
  axis.text = element_text(
    color = "#28a87d",
    family = "Tabular",
    face = "italic",
    colour = NULL,
    size = NULL,
    hjust = 1,
    vjust = 0,
    angle = 45,
    lineheight = 1.3, ## no effect here
    margin = margin(10, 0, 20, 0) ## no effect here
  ),
  axis.text.x = element_text(
    margin = margin(10, 0, 20, 0) ## trbl
  )
)

Customize Labels via `theme()`

g + theme(
  plot.title = element_text(face = "bold"),
  plot.title.position = "plot",
  axis.text = element_text(
    color = "#28a87d",
    family = "Tabular",
    face = "italic",
    colour = NULL,
    size = NULL,
    hjust = 1,
    vjust = 0,
    angle = 45,
    lineheight = 1.3, ## no effect here
    margin = margin(10, 0, 20, 0) ## no effect here
  ),
  plot.tag = element_text(
    margin = margin(0, 12, -8, 0) ## trbl
  )
)

Customize Labels via `theme()`

g + theme(
  plot.title = element_text(face = "bold"),
  plot.title.position = "plot",
  axis.text = element_text(
    color = "#28a87d",
    family = "Tabular",
    face = "italic",
    colour = NULL,
    size = NULL,
    hjust = 1,
    vjust = 0,
    angle = 45,
    lineheight = 1.3, ## no effect here
    margin = margin(10, 0, 20, 0), ## no effect here
    debug = TRUE
  ),
  plot.tag = element_text(
    margin = margin(0, 12, -8, 0), ## trbl
    debug = TRUE
  )
)

Labels + scale_*()

Format Labels via `scale_*`

g <- g + labs(tag = NULL, title = NULL, 
              subtitle = NULL)

g +
  scale_y_continuous(
    breaks = 0:4*15000
  )

Format Labels via `scale_*`

g +
  scale_y_continuous(
    breaks = 0:4*15000,
    labels = scales::comma_format()
  )

Format Labels via `scale_*`

g +
  scale_y_continuous(
    breaks = 0:4*15000,
    labels = scales::comma_format(
      suffix = " bikes"
    ),
    name = NULL
  )

Format Labels via `scale_*`

g +
  scale_y_continuous(
    breaks = 0:4*15000,
    labels = scales::comma_format(
      suffix = "\nbikes shared"
    ),
    name = NULL
  ) +
  theme(
    axis.text.y = element_text(
      hjust = .5
    )
  )

Format Labels via `scale_*`

g +
  scale_y_continuous(
    breaks = 0:4*15000,
    labels = scales::comma_format(
      scale = .001
    ),
    name = "Reported bike shares in thousands"
  )

Format Labels via `scale_*`

g +
  scale_y_continuous(
    breaks = 0:4*15000,
    labels = function(y) y / 1000,
    name = "Reported bike shares in thousands",
  )

Format Labels via `scale_*`

g +
  scale_x_continuous(
    labels = function(y) paste0(y, "°F"),
    name = "Feels Like Temperature"
  )

Format Labels via `scale_*`

ggplot(
    bikes,
    aes(x = season, y = count)
  ) +
  geom_boxplot() +
  scale_x_discrete()

Format Labels via `scale_*`

ggplot(
    bikes,
    aes(x = season, y = count)
  ) +
  geom_boxplot() +
  scale_x_discrete(
    name = NULL,
    labels = stringr::str_to_title
  )

Format Labels via `scale_*`

g +
  scale_color_discrete(
    name = NULL,
    labels = stringr::str_to_title
  )

Labels +
element_markdown

Styling Labels with {ggtext}

# install.packages("ggtext")

g +
  ggtitle("**TfL bike sharing trends by _season_**")

Styling Labels with {ggtext}

# install.packages("ggtext")

g +
  ggtitle("**TfL bike sharing trends by _season_**") +
  theme(
    plot.title = ggtext::element_markdown()
  )

Styling Labels with {ggtext}

# install.packages("ggtext")

g +
  ggtitle("<b style='font-family:tabular;font-size:25pt'>TfL</b> bike sharing trends by <i style='color:#28a87d;'>season</i>") +
  theme(
    plot.title = ggtext::element_markdown()
  )

<b style='font-family:tabular;font-size:15pt;'>TfL</b> bike sharing trends by <i style='color:#28a87d;'>season</i>

Labels + facet_*()

Facet Labellers

g +
  facet_wrap(
    ~ day_night,
    labeller = label_both
  )

Facet Labellers

g +
  facet_wrap(
    ~ is_workday + day_night,
    labeller = label_both
  )

Facet Labellers

g +
  facet_wrap(
    ~ is_workday + day_night,
    labeller = labeller(
      day_night = stringr::str_to_title
    )
  )

Facet Labellers

codes <- c(
  `TRUE` = "Workday",
  `FALSE` = "Weekend or Holiday"
)

g +
  facet_wrap(
    ~ is_workday + day_night,
    labeller = labeller(
      day_night = stringr::str_to_title,
      is_workday = codes
    )
  )

Facet Labellers

codes <- c(
  `TRUE` = "Workday",
  `FALSE` = "Weekend or Holiday"
)

g +
  facet_wrap(
    ~ is_workday + day_night,
    labeller = labeller(
      .default = stringr::str_to_title,
      is_workday = codes
    )
  )

Facet Labeller

g +
  facet_grid(
    day_night ~ is_workday,
    scales = "free",
    space = "free",
    labeller = labeller(
      day_night = stringr::str_to_title,
      is_workday = codes
    )
  )

Handling Long Labels

Handling Long Labels with {stringr}

ggplot(
    bikes,
    aes(x = weather_type,
        y = count)
  ) +
  geom_boxplot()

Handling Long Labels with {stringr}

ggplot(
    bikes,
    aes(x = stringr::str_wrap(weather_type, 6),
        y = count)
  ) +
  geom_boxplot()

Handling Long Labels with {ggtext}

g +
  ggtitle("TfL bike sharing trends in 2015 and 2016 by season for day and night periods") +
  theme(
    plot.title = element_text(size = 20),
    plot.title.position = "plot"
  )

Handling Long Titles

g +
  ggtitle("TfL bike sharing trends in 2015 and 2016 by season for day and night periods") +
  theme(
    plot.title =
      ggtext::element_textbox_simple(size = 20),
    plot.title.position = "plot"
  )

Handling Long Titles

g +
  ggtitle("TfL bike sharing trends in 2015 and 2016 by season for day and night periods") +
  theme(
    plot.title = ggtext::element_textbox_simple(
      margin = margin(t = 12, b = 12),
      lineheight = .9
    ),
    plot.title.position = "plot"
  )

Handling Long Titles

g +
  ggtitle("TfL bike sharing trends in 2015 and 2016 by season for day and night periods") +
  theme(
    plot.title = ggtext::element_textbox_simple(
      margin = margin(t = 12, b = 12),
      fill = "grey90",
      lineheight = .9
    ),
    plot.title.position = "plot"
  )

Handling Long Titles

g +
  ggtitle("TfL bike sharing trends in 2015 and 2016 by season for day and night periods") +
  theme(
    plot.title = ggtext::element_textbox_simple(
      margin = margin(t = 12, b = 12),
      padding = margin(rep(12, 4)),
      fill = "grey90",
      box.color = "grey40",
      r = unit(9, "pt"),
      halign = .5,
      face = "bold",
      lineheight = .9
    ),
    plot.title.position = "plot"
  )

Annotations with annotate()

Add Single Text Annotations

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext"
  )

Style Text Annotations

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext",
    size = 6,
    color = "firebrick",
    fontface = "bold",
    lineheight = .9
  )

Add Multiple Text Annotations

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = c(90, 50),
    y = c(27.5, 3.5),
    label = c("Text A", "Text B"),
    color = c("black", "firebrick"),
    size = c(5, 10),
    fontface = c("plain", "bold")
  )

Add Boxes (Rectangles)

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "rect",
    xmin = -Inf,
    xmax = 60,
    ymin = 20,
    ymax = Inf,
    fill = "#663399"
  )

Add Boxes (Rectangles)

ggplot(bikes, aes(humidity, temp)) +
  annotate(
    geom = "rect",
    xmin = -Inf,
    xmax = 60,
    ymin = 20,
    ymax = Inf,
    fill = "#663399"
  ) +
  geom_point(size = 2, color = "grey")

Add Lines (Segments)

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext",
    size = 6,
    lineheight = .9
  ) +
  annotate(
    geom = "segment",
    x = 90, xend = 82,
    y = 25, yend = 18.5
  )

Add Curves

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext",
    size = 6,
    lineheight = .9
  ) +
  annotate(
    geom = "curve",
    x = 90, xend = 82,
    y = 25, yend = 18.5
  )

Add Arrows

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext",
    size = 6,
    lineheight = .9
  ) +
  annotate(
    geom = "curve",
    x = 90, xend = 82,
    y = 25, yend = 18.5,
    curvature = -.3,
    arrow = arrow()
  )

Add Arrows

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext",
    size = 6,
    lineheight = .9
  ) +
  annotate(
    geom = "curve",
    x = 90, xend = 82,
    y = 25, yend = 18.5,
    curvature = -.3,
    arrow = arrow(
      length = unit(10, "pt"),
      type = "closed",
      ends = "both"
    )
  )

Add Arrows

ggplot(bikes, aes(humidity, temp)) +
  geom_point(size = 2, color = "grey") +
  annotate(
    geom = "text",
    x = 90,
    y = 27.5,
    label = "Some\nadditional\ntext",
    size = 6,
    lineheight = .9
  ) +
  annotate(
    geom = "curve",
    x = 94, xend = 82,
    y = 26, yend = 18.5,
    curvature = -.8,
    angle = 140,
    arrow = arrow(
      length = unit(10, "pt"),
      type = "closed"
    )
  )

Annotations with geom_*()

Highlight Hot Periods

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5)

Annotations with `geom_text()`

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  geom_text(
    aes(label = season),
    nudge_x = .3,
    hjust = 0
  )

Annotations with `geom_label()`

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  geom_label(
    aes(label = season),
    nudge_x = .3,
    hjust = 0
  )

Annotations with {ggrepel}

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  ggrepel::geom_text_repel(
    aes(label = season)
  )

Annotations with {ggrepel}

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp,
        color = season == "summer")
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  ggrepel::geom_text_repel(
    aes(label = str_to_title(season))
  ) +
  scale_color_manual(
    values = c("firebrick", "black"),
    guide = "none"
  )

Annotations with {ggrepel}

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp,
        color = season == "summer")
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  ggrepel::geom_text_repel(
    aes(label = str_to_title(season)),
    ## space between points + labels
    box.padding = .4,
    ## always draw segments
    min.segment.length = 0
  ) +
  scale_color_manual(
    values = c("firebrick", "black"),
    guide = "none"
  )

Annotations with {ggrepel}

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp,
        color = season == "summer")
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  ggrepel::geom_text_repel(
    aes(label = str_to_title(season)),
    ## force to the right
    xlim = c(NA, 35), hjust = 1
  ) +
  scale_color_manual(
    values = c("firebrick", "black"),
    guide = "none"
  ) +
  xlim(25, NA)

Annotations with {ggrepel}

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp,
        color = season == "summer")
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  ggrepel::geom_text_repel(
    aes(label = str_to_title(season)),
    ## force to the right
    xlim = c(NA, 35),
    ## style segment
    segment.curvature = .01,
    arrow = arrow(length = unit(.02, "npc"), type = "closed")
  ) +
  scale_color_manual(
    values = c("firebrick", "black"),
    guide = "none"
  ) +
  xlim(25, NA)

Annotations with {ggrepel}

ggplot(
    filter(bikes, temp >= 27),
    aes(x = humidity, y = temp,
        color = season == "summer")
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point(size = 2.5) +
  ggrepel::geom_text_repel(
    aes(label = str_to_title(season)),
    ## force to the right
    xlim = c(NA, 35),
    ## style segment
    segment.curvature = .001,
    segment.inflect = TRUE
  ) +
  scale_color_manual(
    values = c("firebrick", "black"),
    guide = "none"
  ) +
  xlim(25, NA)

Annotations with {ggforce}

ggplot(
    filter(bikes, temp > 20 & season != "summer"),
    aes(x = humidity, y = temp,
        color = season)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point() +
  scale_color_brewer(
    palette = "Dark2",
    guide = "none"
  )

Annotations with {ggforce}

ggplot(
    filter(bikes, temp > 20 & season != "summer"),
    aes(x = humidity, y = temp,
        color = season)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point() +
  ggforce::geom_mark_rect(
    aes(label = str_to_title(season))
  ) +
  scale_color_brewer(
    palette = "Dark2",
    guide = "none"
  )

Annotations with {ggforce}

ggplot(
    filter(bikes, temp > 20 & season != "summer"),
    aes(x = humidity, y = temp,
        color = season)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point() +
  ggforce::geom_mark_rect(
    aes(label = str_to_title(season))
  ) +
  scale_color_brewer(
    palette = "Dark2",
    guide = "none"
  ) +
  ylim(NA, 35)

Annotations with {ggforce}

ggplot(
    filter(bikes, temp > 20 & season != "summer"),
    aes(x = humidity, y = temp,
        color = season)
  ) +
  geom_point(
    data = bikes,
    color = "grey65", alpha = .3
  ) +
  geom_point() +
  ggforce::geom_mark_rect(
    aes(label = str_to_title(season)),
    expand = unit(5, "pt"),
    radius = unit(0, "pt"),
    con.cap = unit(0, "pt"),
    label.buffer = unit(15, "pt"),
    con.type = "straight",
    label.fill = "transparent"
  ) +
  scale_color_brewer(
    palette = "Dark2",
    guide = "none"
  ) +
  ylim(NA, 35)

Annotations with {ggforce}

ggplot(
    bikes,
    aes(x = humidity, y = temp,
        color = season == "summer")
  ) +
  geom_point(alpha = .4) +
  ggforce::geom_mark_hull(
    aes(label = str_to_title(season),
        filter = season == "summer",
        description = "June to August"),
    expand = unit(10, "pt")
  ) +
  scale_color_manual(
    values = c("grey65", "firebrick"),
    guide = "none"
  )

Adding Images

Load and Modify Image

url <- "https://d33wubrfki0l68.cloudfront.net/dbb07b06a7b3fe056db386fef0b158cc2fd33cb9/8b491/assets/img/2022conf/logo-rstudio-conf.png"
img <- magick::image_read(url)
img <- magick::image_negate(img)

img

Add Background Image

ggplot(bikes, aes(date, temp_feel)) +
  annotation_custom(
    grid::rasterGrob(
      image = img
    )
  ) +
  geom_point(color = "#71a5d4")

Adjust Position

ggplot(bikes, aes(date, temp_feel)) +
  annotation_custom(
    grid::rasterGrob(
      image = img,
      x = .5,
      y = .9,
      width = .9
    )
  ) +
  geom_point(color = "#71a5d4") +
  ylim(NA, 37)

Place Image Outside

ggplot(bikes, aes(date, temp_feel)) +
  annotation_custom(
    grid::rasterGrob(
      image = img,
      x = .47,
      y = 1.15,
      width = .9
    )
  ) +
  geom_point(color = "#71a5d4") +
  coord_cartesian(clip = "off") +
  theme(
    plot.margin = margin(90, 10, 10, 10)
  )

Recap

  • style labels such as title, axis and legend texts with theme()
  • format data-related labels with the labels argument of scale_*()
  • adjust strip text with the facet_*(labeller) functionality
  • add data-related annotations with geom_text|label()
  • … and data-unrelated annotations with annotate()
  • annotation_custom(rasterGrob()) is a basic way to add images
  • {ggtext} allows to render labels with markdown and basic html
  • {ggtext} also allows to add dynamic linebreaks and images
  • {ggrepel} ensures clever placement of annotations
  • ggforce::geom_mark_*() provide a set of advanced annotations

Exercises

Exercise 1

  • {ggtext} also comes with some new geom’s. Explore those and other options on the package webpage: wilkelab.rg/ggtext.
  • Create the following visualization, as close as possible, with the penguins dataset which is provided by the {palmerpenguins} package.
    • For the species labels, you likely have to create a summary data set.
    • Use the {ggtext} geometries and theme elements to format the labels.
    • Also, make use of the other components such as scales, complete themes, and theme customization.

Exercise 2

  • Create this logo with the image file exercises/img/rstudioconf-washington-bg.png for the skyline: