skip to content

Customizing Legend Glyph

4 min read

The role of key glyphs in the ggplot2 universe

1. Where the glyph lives in ggplot2

Every time you add a geom, it registers a draw_key = <function> that tells the legend how to draw itself.

  • GeomPoint$draw_keydraw_key_point()
  • GeomLine$draw_keydraw_key_vpath()

2. How the legend calls it

When a guide is built, it loops over every layer that should appear in the legend and calls

geom$draw_key(data, params, size)

where

  • data one-row data.frame with the mapped aesthetics (colour, fill, size, alpha, shape, linetype …)
  • params list of geom parameters (stroke, linewidth, …)
  • size physical size of the legend key (in npc units)

The function must return a grob; the guide system then places that grob in the legend cell.


Writing your own draw_key_*()

Minimal skeleton

draw_key_myglyph <- function(data, params, size) {
  # data  : 1-row data.frame
  # params: list
  # return: a grob
  grid::pointsGrob(0.5, 0.5,
                   gp = grid::gpar(col = data$colour))
}

Key points

  • Extract aesthetics with data$colour, data$fill, data$alpha, …
  • Convert coordinates to npc (0–1) with unit().
  • Any grob is allowed: pointsGrob, linesGrob, rectGrob, textGrob, gTree, …

Let ggplot2 know about your function

MethodUsage
Function objectgeom_point(key_glyph = draw_key_myglyph)
String shortcutdraw_key_cluster <- … then geom_point(key_glyph = "cluster") ggplot2 looks for draw_key_<name> in an internal cache.
Global overrideGeomPoint$draw_key <- draw_key_cluster permanently swaps the default glyph for that geom.

Complete examples

Scatter with clustered legend icon

library(ggplot2)
library(grid)
 
draw_key_cluster <- function(data, params, size) {
  n <- 4
  set.seed(42)
  x <- runif(n, 0.35, 0.65)
  y <- runif(n, 0.35, 0.65)
 
  pointsGrob(
    x = unit(x, "npc"),
    y = unit(y, "npc"),
    pch = data$shape %||% 16,
    gp = gpar(
      col   = alpha(data$colour, data$alpha),
      fill  = alpha(data$fill,  data$alpha),
      fontsize = (data$size %||% 1.5) * .pt
    )
  )
}
 
ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
  geom_point(key_glyph = "cluster") +
  guides(colour = guide_legend(override.aes = list(size = 1.5)))
cluster legend

Long-dashed line legend

library(ggplot2)
library(grid)
 
draw_long_dashed_line <- function(data, params, size) {
  segmentsGrob(x0 = unit(-1.5, "npc"),
               x1 = unit(0.9,  "npc"),
               y0 = unit(0.5,  "npc"),
               y1 = unit(0.5,  "npc"),
               gp = gpar(col   = alpha(data$colour, data$alpha),
                         lwd   = 1,
                         lty   = "dashed"))
}
 
ggplot(economics, aes(date, unemploy, linetype = "dashed")) +
  geom_line(key_glyph = draw_long_dashed_line) +
  scale_linetype_manual(values = c(dashed = 2)) +
  theme(legend.position = c(0.9, 0.1))
dashed legend

Mini boxplot glyph

draw_key_box <- function(data, params, size) {
  gTree(
    children = gList(
      segmentsGrob(0.35, 0.2,  0.65, 0.2,  gp = gpar(col = data$colour)), # lower whisker
      segmentsGrob(0.35, 0.8,  0.65, 0.8,  gp = gpar(col = data$colour)), # upper whisker
      segmentsGrob(0.35, 0.35, 0.65, 0.35, gp = gpar(col = data$colour)), # lower box
      segmentsGrob(0.35, 0.65, 0.65, 0.65, gp = gpar(col = data$colour)), # upper box
      segmentsGrob(0.35, 0.35, 0.35, 0.2,  gp = gpar(col = data$colour)), # left whisker
      segmentsGrob(0.35, 0.65, 0.35, 0.8,  gp = gpar(col = data$colour)), # left whisker
      segmentsGrob(0.65, 0.35, 0.65, 0.2,  gp = gpar(col = data$colour)), # right whisker
      segmentsGrob(0.65, 0.65, 0.65, 0.8,  gp = gpar(col = data$colour)), # right whisker
      segmentsGrob(0.35, 0.5,  0.65, 0.5,  gp = gpar(col = data$colour, lwd = 2)) # median
    )
  )
}
 
ggplot(mtcars, aes(factor(cyl), mpg, colour = factor(cyl))) +
  geom_boxplot(key_glyph = "box")
box legend

Smiley face glyph

draw_key_smiley <- function(data, params, size) {
  face  <- circleGrob(0.5, 0.5, r = 0.4,
                      gp = gpar(col = data$colour, fill = "yellow"))
  left  <- circleGrob(0.35, 0.6, r = 0.05,
                      gp = gpar(col = NA, fill = "black"))
  right <- circleGrob(0.65, 0.6, r = 0.05,
                      gp = gpar(col = NA, fill = "black"))
  mouth <- linesGrob(x = c(0.3, 0.5, 0.7), y = c(0.35, 0.25, 0.35),
                     gp = gpar(col = "black", lwd = 2))
  gTree(children = gList(face, left, right, mouth))
}
 
ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
  geom_point(size = 4, key_glyph = "smiley") +
  theme(legend.position = "bottom")
smiley legend

Five-point star glyph

draw_key_star <- function(data, params, size) {
  n <- 5
  outer <- 0.4
  inner <- 0.2
  theta <- seq(0, 2 * pi, length.out = 2 * n + 1)[-1]
  r <- rep(c(outer, inner), n)
  x <- 0.5 + r * cos(theta)
  y <- 0.5 + r * sin(theta)
  polygonGrob(x, y,
              gp = gpar(col   = data$colour,
                        fill  = alpha(data$fill %||% data$colour, data$alpha)))
}
 
ggplot(mtcars, aes(wt, mpg, fill = factor(cyl))) +
  geom_point(shape = 21, size = 4, key_glyph = "star")
star legend

Quarter pie-slice glyph

draw_key_pie_slice <- function(data, params, size) {
  theta <- seq(pi/4, pi/2, length = 30)
  x <- c(0.5, 0.5 + 0.4*cos(theta), 0.5)
  y <- c(0.5, 0.5 + 0.4*sin(theta), 0.5)
  polygonGrob(x, y,
              gp = gpar(fill = alpha(data$fill,  data$alpha),
                        col  = alpha(data$colour, data$alpha)))
}
 
ggplot(mtcars, aes(wt, mpg, fill = factor(cyl))) +
  geom_point(shape = 21, size = 6, key_glyph = "pie_slice")
pie slice legend

Enjoy crafting your own legend glyphs!