Lab-7: The Lobster Quadrille

Fonts and other Wizardy in ggplot

Author

Arvind V

Published

July 21, 2021

Modified

May 21, 2024

Introduction

We will add icing and froth to our vanilla ggplots: fonts, annotations, highlights and even pictures!!

Goals

  • Appreciate that a publication-worth graphic takes a lot of work!!
  • Adding annotations, pictures and references to graphs is necessary for good understanding
  • Judicious use of colour and scales can enhance comprehension.

Pedagogical Note

The method followed will be based on PRIMM:

  • PREDICT Inspect the code and guess at what the code might do, write predictions
  • RUN the code provided and check what happens
  • INFER what the parameters of the code do and write comments to explain. What bells and whistles can you see?
  • MODIFY the parameters code provided to understand the options available. Write comments to show what you have aimed for and achieved.
  • MAKE : take an idea/concept of your own, and graph it.

Let’s load up a few packages that we need to start:

## packages
library(tidyverse)   ## data science package collection (incl. the ggplot2 package)
library(systemfonts) ## use custom fonts (need to be installed on your OS)  
library(paletteer)   ## scico  and many other colour palettes palettes(http://www.fabiocrameri.ch/colourmaps.php) in R 
library(ggtext)      ## add improved text rendering to ggplot2
library(ggforce)     ## add missing functionality to ggplot2
library(concaveman)  ## Needed by ggforce for plot annotation hulls
library(ggdist)      ## add uncertainty visualizations to ggplot2
library(ggformula)   ## Formula interface to ggplot
library(magick)      ## load images into R
library(patchwork)   ## combine outputs from ggplot2
library(palmerpenguins)

library(showtext)   ## add google fonts to plots

knitr::opts_chunk$set(
  error = TRUE,
  comment = NA,
  warning = FALSE,
  errors = FALSE,
  message = FALSE,
  tidy = FALSE,
  cache = FALSE,
  echo = TRUE,
  warning = FALSE,
# from the vignette for the showtext package
  fig.showtext = TRUE,
  fig.retina = 1,
  fig.path = "figs/"
  # fig.height = 3.09,
  # fig.width = 5
)

Using Google Fonts

We will want to add a few new fonts to our graphs. The best way (currently) is to use the showtext package (which we loaded above) to bring into our work fonts from Google. To view and select the fonts you might want to work with, spend some time looking over:

  1. Google Webfonts Helper App

  2. Google Fonts

library(sysfonts)
library(showtext)

sysfonts::font_add_google("Roboto", "roboto")
font_add_google("Noto Sans", "noto")
font_add_google("Open Sans", "open")
font_add_google("Anton", "anton")
font_add_google("Tangerine", "tangerine")

 # set the google fonts as default
showtext::showtext_auto(enable = TRUE)

We will work with a familiar dataset, so that we can concentrate on the chart aesthetics, without having to spend time getting used to the data: the penguins dataset again, from the palmerpenguins package.

ggformula and ggplot worlds do intersect!

It seems we can mix `ggformula` code with `ggtext` code, using the `+` sign!! What joy !!! Need to find out if this works for other `ggplot` extensions as well !!!

Data

Always start your work with a table of the data:

penguins <- penguins %>% drop_na() 
# remove data containing missing data
penguins 

Basic Plot

A basic scatter plot, which we will progressively dress up.

Customized Plot

Let us set some ggplot theme aspects now!! Here is a handy picture showing (most of) the theme-able aspects of a ggplot plot.

Ggplot theme elements

Rosana Ferrero (@RosanaFerrero) on Twitter Sept 11, 2022

For more info, type ?theme in your console.

## change global theme settings (for all following plots)
my_theme <- theme_set(theme_classic(base_size = 12, 
                        base_family = "roboto"
                        )) +  

## modify plot elements globally (for all following plots)
theme_update(
  axis.ticks = element_line(color = "grey92"),
  axis.ticks.length = unit(.5, "lines"),
  panel.grid.minor = element_blank(),
  legend.title = element_text(size = 12),
  legend.text = element_text(color = "grey30"),
  plot.title = element_text(size = 18, face = "bold"),
  plot.subtitle = element_text(size = 12, color = "grey30"),
  plot.caption = element_text(size = 9, margin = margin(t = 15))
)

Since we know what the basic plot looks like, let’s add titles, labels and colours. We will also set limits and scales.

Using {ggtext}

From Claus Wilke’s website β†’ www.wilkelab.org/ggtext

The ggtext package provides simple Markdown and HTML rendering for ggplot2. Under the hood, the package uses the gridtext package for the actual rendering, and consequently it is limited to the feature set provided by gridtext.
Support is provided for Markdown both in theme elements (plot titles, subtitles, captions, axis labels, legends, etc.) and in geoms (similar to geom_text()). In both cases, there are two alternatives, one for creating simple text labels and one for creating text boxes with word wrapping.

Working with ggtext

NOTE: on some machines, the ggtext package may not work as expected. In this case, please do as follows, using your Console:

  • remove gridtext: remove.packages(gridtext).
  • Install development version of gridtext: install.packages(remotes) remotes::install_github("wilkelab/gridtext")

Using element_markdown()

We can use our familiar markdown syntax right inside the titles and captions of the plot. element_markdown() is a theme-ing command made available by the ggtext package.

element_markdown() β†’ formatted text elements, e.g. titles, caption, axis text, striptext.

element_markdown() in combination with HTML

This allows us to change fonts in titles, labels, and captions.

Annotations with geom_richtext() and geom_textbox()

Further ggplot annotations can be achieved using geom_richtext() and geom_textbox(). geom_richtext() also allows formatted text labels with 360Β° rotation. One needs to pass a tibble to geom_richtext() giving the location, colour, rotation etc of the label annotation.

Formatted Text boxes on plots

element_textbox() and element_textbox_simple() β†’ formatted text boxes with word wrapping.

Using geom_texbox() for formatted text boxes with word wrapping

Using {ggforce}

From Thomas Lin Pedersen’s website β†’ www.ggforce.data-imaginist.com

ggforce is a package aimed at providing missing functionality to ggplot2 through the extension system introduced with ggplot2 v2.0.0. Broadly speaking ggplot2 has been aimed primarily at explorative data visualization in order to investigate the data at hand, and less at providing utilities for composing custom plots a la D3.js. ggforce is mainly an attempt to address these β€œshortcomings” (design choices might be a better description). The goal is to provide a repository of geoms, stats, etc. that are as well documented and implemented as the official ones found in ggplot2.

We will start with the basic plot, with the ggtext related work done up to now:

## use ggtext rendering for the following plots
theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown()
)

ggplot tricks

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

gg0 <-
  ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(aes(fill = species,
                                 label = species),
                             alpha = .15,
                             show.legend = FALSE) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  
  scale_x_continuous(breaks = seq(25, 65, by = 5), 
                     limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), 
                     limits = c(12, 24)) +
  
  scico::scale_color_scico(palette = "bamako", direction = -1) +
  labs(
    title = "**Bill Dimensions of Brush-Tailed Penguins** (*Pygoscelis*)",
    subtitle = 'A scatter plot of bill depth versus bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "**Bill Length** (mm)",
    y = "**Bill Depth** (mm)",
    color = "**Body mass** (g)"
  )
gg0

Left-Aligned Title

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

(gg1 <- gg0 + theme(plot.title.position = "plot"))

Right-Aligned Caption

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

gg1b <- gg1 +  theme(plot.caption.position = "plot")
gg1b

Legend Design

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

gg1b + theme(legend.position = "top")
#ggsave("06a_legend_position.pdf", width = 9, height = 8, device = cairo_pdf)

gg1b + 
  theme(legend.position = "top") +
  guides(color = guide_colorbar(title.position = "top", 
                                title.hjust = .5, 
                                barwidth = unit(20, "lines"), 
                                barheight = unit(.5, "lines")))

Error in `setup_elements()`:
! Can't merge the `legend.title` theme element.
Caused by error in `merge_element()`:
! Only elements of the same class can be merged.

Add Images

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

## read PNG file from web
png <- magick::image_read("images/culmen_depth.png")

## turn image into `rasterGrob`
img <- grid::rasterGrob(png, interpolate = TRUE)

gg5 <- 
  gg1 + 
  annotation_custom(img, ymin = 18, ymax = 28, 
                    xmin = 58, xmax = 65) +
  labs(caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE* &bull; Illustration: Allison Horst")   +
  coord_cartesian(clip = "off") # ensure no clipping of labels near the edge
gg5

Using {patchwork}

The goal of patchwork is to make it ridiculously simple to combine separate ggplots into the same graphic. As such it tries to solve the same problem as gridExtra::grid.arrange() and cowplot::plot_grid but using an API that incites exploration and iteration, and scales to arbitrarily complex layouts.

β†’ https://patchwork.data-imaginist.com/

Let us make two plots and combine them into a single patchwork plot.

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

## calculate bill ratio
penguins_stats <- penguins %>%
  mutate(bill_ratio = bill_length_mm / bill_depth_mm) %>%
  filter(!is.na(bill_ratio))

## create a second chart
gg6 <-
  ggplot(penguins_stats,
         aes(
           y = bill_ratio,
           x = species,
           fill = species,
           color = species
         )) + geom_violin() +
  labs(
    y = "Bill ratio",
    x = "Species",
    subtitle = "",
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE* &bull; Illustration: Allison Horst"
  ) +
  theme(
    panel.grid.major.x = element_line(linewidth = .35),
    panel.grid.major.y = element_blank(),
    axis.text.y = element_text(size = 13),
    axis.ticks.length = unit(0, "lines"),
    plot.title.position = 'plot',
    plot.subtitle = element_text(margin = margin(t = 5, b = 10)),
    plot.margin = margin(10, 25, 10, 25)
  )

Now to combine both plots into one using simple operators:

For the special case of putting plots besides each other or on top of each other patchwork provides 2 shortcut operators. | will place plots next to each other while / will place them on top of each other.

First we stack up the graphs side by side:

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

## combine both plots
gg5 | (gg6 + labs(title = "Bill Ratios of Brush-Tailed Penguins",
                  subtitle = "Violin Plots of Bill Ration versus species"))

We can place them in one column:

theme_set(my_theme)
theme_update(
  plot.title = ggtext::element_markdown(),
  plot.caption = ggtext::element_markdown(),
  axis.title.x = ggtext::element_markdown(),
  axis.title.y = ggtext::element_markdown(),
  legend.title = ggtext::element_markdown()
)

gg5 / (gg6 + labs(title = "Bill Ratios of Brush-Tailed Penguins",
                  subtitle = "Violin Plots of Bill Ration versus species")) +
  plot_layout(heights = c(0.4, 0.4))

References


1. Thomas Lin Pedersen, https://www.data-imaginist.com/. The creator of ggforce, and patchwork packages.
2. Claus Wilke, cowplot – Streamlined plot theme and plot annotations for ggplot2, https://wilkelab.org/cowplot/index.html
3. Claus Wilke, Spruce up your ggplot2 visualizations with formatted text, https://clauswilke.com/talk/rstudio_conf_2020/. Slides, Code, and Video !
4. Robert Kabacoff, ggplot theme cheatsheet, https://rkabacoff.github.io/datavis/modifyingthemes.pdf
5. Zuguang Gu, Circular Visualization in R, https://jokergoo.github.io/circlize_book/book/

R Package Citations
Package Version Citation
ggdist 3.3.2 @ggdist2024a; @ggdist2024b
ggforce 0.4.2 @ggforce
ggtext 0.1.2 @ggtext
grid 4.4.0 @grid
magick 2.8.3 @magick
paletteer 1.6.0 @paletteer
patchwork 1.2.0 @patchwork
scico 1.5.0 @scico
showtext 0.9.7 @showtext
sysfonts 0.8.9 @sysfonts
systemfonts 1.1.0 @systemfonts
Back to top