Plot composition

Thomas Lin Pedersen

Key takeaway today

Composing plots are no harder than adding a new layer to your plot

Today

  • Introduce patchwork for plot composition
  • From simple to complex layouts
    • Key design principles
    • Operators
    • Escaping the grid
  • The non-panel stuff
    • Annotation
    • Guide handling
  • Object support
  • Interactivity

Patchwork

  • Composition should have a composable API
  • The API should feel natural and easy to reason about in order to invite experimentation
  • Assume alignment but give escape hatch

Patchwork in a nutshell

p1 <- ggplot(penguins) + 
  geom_point(aes(x = flipper_len, y = bill_len))

p2 <- ggplot(penguins) + 
  geom_histogram(aes(flipper_len))

p1 + p2

Patchwork in a nutshell

p3 <- ggplot(penguins) + 
  geom_boxplot(aes(species, bill_len))

p1 | (p2 / p3)

Patchwork in a nutshell

ggplot(penguins) + 
  geom_point(aes(x = flipper_len, y = bill_len)) + 
  ggplot(penguins) + 
  geom_histogram(aes(flipper_len))

Patchwork in a nutshell

p1 + p2 +
  geom_density(
    aes(flipper_len, after_stat(count))
  )

Patchwork in a nutshell

p1 + p2 &
  theme_minimal()

Patchwork layout model

Patchwork layout model

Patchwork layout model

Patchwork layout model

Patchwork layout model

More layout options

p4 <- ggplot(penguins) + 
  geom_bar(
    aes(species, fill = sex), 
    position = "dodge"
  )

wrap_plots(p1, p2, p3, p4, widths = c(1, 2))

More layout options

p1 + p2 + p3 + p4 + 
  plot_layout(widths = c(1, 2))

More layout options

layout <- "
A#B
#CC
DD#
"
p1 + p2 + p3 + p4 + 
  plot_layout(design = layout)

More layout options

layout <- c(
  area(1, 1, 2, 3),
  area(2, 2, 3, 3)
)
p1 + p2 + 
  plot_layout(design = layout)

Exercise 5.1

05:00

Layout modifiers

  • The composition consists of a grid

  • Panels are placed in one or more neighboring cells

  • Everything outside the panels are placed in the gutter which expands to contain all

  • What if you don’t want that?

Layout modifiers - Insets

p1 + inset_element(
  p2, 
  left = 0.5, 
  bottom = 0.5, 
  right = unit(1, "npc") - unit(6, "mm"),
  top = unit(1, "npc") - unit(6, "mm"),
)

Layout modifiers - Free

p3a <- p3 + 
  guides(x = guide_axis(angle = 45))
p1 + p3a

Layout modifiers - Free

p1 + free(p3a, side = "b")

Layout modifiers - Free

free(p1, "label", side = "b") + p3a

Exercise 5.2

05:00

Annotations

  • Once composed, the parts will constitute a new graphic
  • Like the individual plots, this graphic can be annotated
p1 + p2 + plot_annotation(
  title = "The _full_ story of penguins",
  subtitle = "*You won't believee what you'll find*",
  caption = "source: The `penguins` dataset in R",
  theme = marquee::marquefy_theme(theme_gray())
)

Annotations

  • Once composed, the parts will constitute a new graphic
  • Like the individual plots, this graphic can be annotated
p1 + 
  labs(title = "This is a plot title") + 
  p2 + 
  plot_annotation(
    title = "The _full_ story of penguins",
    subtitle = "*You won't believee what you'll find*",
    caption = "source: The `penguins` dataset in R",
    theme = marquee::marquefy_theme(theme_gray())
  )

Tagging

With multi-panel graphics you often need a way to refer to the subgraphics

p123 <- (p1 | (p2 / p3)) & theme(
  plot.tag = element_text(size = 8)
)

p123 + 
  plot_annotation(
    tag_levels = "1",
    tag_suffix = ")"
  )

Tagging

It knows about nesting as well

p123[[2]] <- p123[[2]] + 
  plot_layout(tag_level = "new")

p123 + 
  plot_annotation(
    tag_levels = c("1", "a"),
    tag_suffix = ")"
  )

Exercise 5.3

05:00

Guide handling

Guides are often global instead of linked to a single subplot

p1 / p4

Guide handling

Use guides = "collect" to fetch all guides from subplots and place them at the top level

(p1 / p4) + 
  plot_layout(guides = "collect")

Guide handling

p1a <- ggplot(penguins) + 
  geom_point(
    aes(x = flipper_len, y = bill_len, shape = species)
  )

p1a | p4

Guide handling

(p1a | p4) + 
  plot_layout(guides = "collect")

Guide handling

Duplicate guides are automatically removed

p2a <- ggplot(penguins) + 
  geom_histogram(
    aes(flipper_len, fill = sex)
  )

(p2a | p4) + 
  plot_layout(guides = "collect")

Guide handling

You can place guides inside the composition as well.

p2a <- ggplot(penguins) + 
  geom_histogram(
    aes(flipper_len, fill = sex)
  )

p2a + p4 + p3 + guide_area() + 
  plot_layout(guides = "collect")

Guide handling

Axes are guides too

(p3 / p4) + plot_layout(
  axes = "collect",
  guides = "collect"
)

Exercise 5.4

05:00

Other objects

  • Sometimes you need something other than a ggplot (🤯)
  • patchwork supports a range of different object types

Other objects: gt

table <- gt::gt(
  penguins[
    sample(nrow(penguins), 10), 
    c("species", "flipper_len", "bill_len")
  ]
)
p1 + table

Other objects: gt

p1 + 
  wrap_table(
    table, 
    panel = "full", 
    space = "fixed"
  )

Other objects: images

logo <- system.file("help/figures/logo.png", package = "patchwork")
logo <- png::readPNG(logo, native = TRUE)

p1 + logo

Other objects: grobs

grob <- marquee::marquee_grob(
  textshaping::lorem_text("kana"),
  marquee::classic_style(base_size = 10)
)

p1 + grob

Other objects: base graphics

p1 + 
  ~plot(
    penguins$flipper_len, 
    penguins$bill_len
  )

Other objects: base graphics

par(mar = c(0, 2, 0, 0), bg = NA)
p1 + 
  wrap_elements(
    panel = ~plot(
      penguins$flipper_len, 
      penguins$bill_len
    ), 
    clip = FALSE
  )

Exercise 5.5

05:00

ggiraph integration

  • ggiraph is a package that allows certain interactivity to ggplots
  • You create your plots as you always do, but add specific interactive geoms
  • If you combine them with patchwork, the interaction is linked

ggiraph integration

library(ggiraph)

p1 <- ggplot(penguins) + 
  geom_point_interactive(
    aes(
      x = flipper_len, 
      y = bill_len, 
      tooltip = species,
      data_id = species
    )
  ) + 
  theme_grey(base_size = 24)

girafe(ggobj = p1)

ggiraph integration

p2 <- ggplot(penguins) + 
  geom_bar_interactive(
    aes(
      x = species,
      data_id = species
    )
  ) + 
  theme_grey(base_size = 24)

girafe(ggobj = p2)

ggiraph integration

girafe(
  ggobj = p1 | p2
)

Next session: Creating extensions or Spice up your plot