recipe 2: geom_coords()

Author

Gina Reynolds, Morgan Brown

Published

January 3, 2022

This tutorial is intended for individuals who already have a working knowledge of the grammar of ggplot2, but may like to build a richer vocabulary for themselves via the Stat extension mechanism.

Preview

Our recipes take the form:

  • Step 0. Get the job done with β€˜base’ ggplot2. It’s a good idea to clarify what needs to happen without getting into the extension architecture
  • Step 1. Write a computation function. Wrap the necessary computation into a function that your target geom_*() function will perform. We focus on β€˜compute_group’ computation only in this tutorial.
  • Step 2. Define a ggproto object. ggproto objects allow your extension to work together with base ggplot2 functions! You’ll use the computation function from step 1 to help define it.
  • Step 3. Write your geom function! You’re ready to write your function. You will incorporate the ggproto from step 2 and also define which more primitive geom (point, text, segment etc) you want other behaviors to inherit from.
  • Step 4. Test/Enjoy! Take your new geom for a spin! Check out group-wise computation behavior!

Below, you’ll see a completely worked example (example recipes) and then a invitation to build a related target geom_*().

Example recipe #2: geom_label_id()


Step 0: use base ggplot2 to get the job done

# step 0.a
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
βœ” dplyr     1.1.4     βœ” readr     2.1.5
βœ” forcats   1.0.0     βœ” stringr   1.5.1
βœ” ggplot2   3.5.1     βœ” tibble    3.2.1
βœ” lubridate 1.9.3     βœ” tidyr     1.3.1
βœ” purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
βœ– dplyr::filter() masks stats::filter()
βœ– dplyr::lag()    masks stats::lag()
β„Ή Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
cars %>% 
  mutate(id_number = 1:n()) %>% 
  ggplot() + 
  aes(x = speed, y = dist) + 
  geom_point() + 
  geom_label(aes(label = id_number), 
             hjust = 1.2)

# step 0.b
layer_data(last_plot(), i = 2) %>% 
  head()
  label x  y PANEL group colour  fill size angle hjust vjust alpha family
1     1 4  2     1    -1  black white 3.88     0   1.2   0.5    NA       
2     2 4 10     1    -1  black white 3.88     0   1.2   0.5    NA       
3     3 7  4     1    -1  black white 3.88     0   1.2   0.5    NA       
4     4 7 22     1    -1  black white 3.88     0   1.2   0.5    NA       
5     5 8 16     1    -1  black white 3.88     0   1.2   0.5    NA       
6     6 9 10     1    -1  black white 3.88     0   1.2   0.5    NA       
  fontface lineheight
1        1        1.2
2        1        1.2
3        1        1.2
4        1        1.2
5        1        1.2
6        1        1.2

Step 1: computation

# you won't use the scales argument, but ggplot will later
compute_group_row_number <- function(data, scales){
  
  data %>% 
    # add an additional column called label
    # the geom we inherit from requires the label aesthetic
    mutate(label = 1:n())
  
}

# step 1b test the computation function 
cars %>% 
  # input must have required aesthetic inputs as columns
  rename(x = speed, y = dist) %>% 
  compute_group_row_number() %>% 
  head()
  x  y label
1 4  2     1
2 4 10     2
3 7  4     3
4 7 22     4
5 8 16     5
6 9 10     6

Step 2: define ggproto

StatRownumber <- ggplot2::ggproto(`_class` = "StatRownumber",
                                  `_inherit` = ggplot2::Stat,
                                  required_aes = c("x", "y"),
                                  compute_group = compute_group_row_number)

# test
cars %>% 
  mutate(id_number = 1:n()) %>% 
  ggplot() + 
  aes(x = speed, y = dist) + 
  geom_point() + 
  geom_label(stat = StatRownumber, 
             hjust = 1.2)


Step 3: define geom_* function

geom_label_row_number <- function(mapping = NULL, data = NULL,
                           position = "identity", na.rm = FALSE,
                           show.legend = NA,
                           inherit.aes = TRUE, ...) {
  ggplot2::layer(
    stat = StatRownumber, # proto object from Step 2
    geom = ggplot2::GeomLabel, # inherit other behavior, this time Label
    data = data, 
    mapping = mapping,
    position = position, 
    show.legend = show.legend, 
    inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}

Step 4: Enjoy! Use your function

cars %>% 
  ggplot() + 
  aes(x = speed, y = dist) + 
  geom_point() + 
  geom_label_row_number(hjust = 1.2) # function in action

And check out conditionality!

last_plot() + 
  aes(color = dist > 60) # Computation is within group


Task #2: create geom_text_coordinates()

Using recipe #2 as a reference, can you create the function geom_text_coordinates().

–

  • geom should label point with its coordinates β€˜(x, y)’
  • geom should have behavior of geom_text (not geom_label)

Hint:

paste0("(", 1, ", ",3., ")")
[1] "(1, 3)"