rggplot2grob

How to make a legend for geom_grob shapes?


I'm trying to define custom shapes to use in ggplot2 by passing grobs created with the grid package to geom_grob. So, for example, I might create the following two grobs (a square and a triangle):

library(grid)
library(ggplot2)
library(dplyr)
library(ggpp)

devratio <- dev.size()[2]/dev.size()[1]

square_ang <- seq(pi/4, 9*pi/4, length.out = 5)
square <- grid.polygon(
  x = unit(0.5 + 0.2 * devratio * sin(square_ang), 'npc'),
  y = unit(0.5 + 0.2 * cos(square_ang), 'npc'),
  gp = gpar(lwd = 2, fill = 'blue', col = 'blue')
  )

triangle_ang <- seq(0, 2*pi, length.out = 4)
triangle <- grid.polygon(
  x = unit(0.5 + 0.18 * devratio * sin(triangle_ang), 'npc'),
  y = unit(0.5 + 0.18 * cos(triangle_ang), 'npc'),
  gp = gpar(lwd = 2, fill = 'blue', col = 'blue')
  )

Within geom_grob there is an argument show.legend, but after setting that to TRUE nothing shows up.

ggplot(tibble(x = 1:6, y = 1:6, 
       shape = rep(list(square, triangle), each = 3))) +
  geom_grob(aes(x, y, label = shape), show.legend = TRUE)

plot1

I thought maybe ggplot2 doesn't consider label to be an aesthetic, so I tried adding an artificial color specification. That got the legend to appear, but the legend key is blank.

ggplot(tibble(x = 1:6, y = 1:6, 
       shape = rep(list(square, triangle), each = 3),
       col = rep(c('col1', 'col2'), each = 3))) +
  geom_grob(aes(x, y, label = shape, color = col), 
            show.legend = TRUE)

plot2

Is it possible to have a legend showing the custom grobs?


Solution

  • At least for your shapes I think the easiest approach would be to go on with your second approach and fake a legend using the color or ... aesthetic. The reason that no keys show up is that default draw_key function of geom_grob or GeomGrob returns a nullGrob, i.e. display nothing. But you can override that using the key_glyph argument:

    library(grid)
    library(ggplot2)
    library(ggpp)
    library(dplyr)
    library(gtable)
    
    ggplot(tibble(
      x = 1:6, y = 1:6,
      shape = rep(list(square, triangle), each = 3),
      col = rep(c("col1", "col2"), each = 3)
    )) +
      geom_grob(aes(x, y, label = shape, color = col),
        show.legend = TRUE, key_glyph = "point"
      ) +
      guides(
        color = guide_legend(
          override.aes = list(
            shape = c(15, 17),
            color = "blue",
            size = 4
          )
        )
      )
    

    For more general shapes, a second option (and alternative to the approach by @M--) would be to manually add a legend using guide_custom introduced in ggplot2 >= 3.5.0 where I use gtable to setup the layout for the legend grobs:

    square_text <- textGrob(
      x = unit(0, "npc"),
      label = "Square",
      gp = gpar(fontsize = 8),
      hjust = 0
    )
    triangle_text <- textGrob(
      x = unit(0, "npc"),
      label = "Triangle",
      gp = gpar(fontsize = 8),
      hjust = 0
    )
    
    legend_key_height <- 20
    # Setup the gtable layout
    legend <- gtable(
      # Widths of columns.
      widths = unit(
        c(legend_key_height / devratio, 1.5), c("pt", "cm")
      ),
      heights = unit(rep(legend_key_height, 2), "pt")
    )
    
    # Add the grobs
    legend <- gtable_add_grob(
      legend,
      grobs = list(square, triangle, square_text, triangle_text),
      t = c(1, 2, 1, 2),
      l = c(1, 1, 2, 2),
      clip = "off"
    )
    
    ggplot(tibble(
      x = 1:6, y = 1:6,
      shape = rep(list(square, triangle), each = 3)
    )) +
      geom_grob(aes(x, y, label = shape)) +
      guides(
        custom = guide_custom(
          legend
        )
      ) +
      theme(
        legend.box.spacing = unit(0, "pt")
      )