Multiple Legends

Gilles Colling

2026-05-07

Overview

When a plot maps multiple aesthetics (colour, size, shape, etc.), ggplot2 creates separate legends for each. ggguides provides functions to control these legends individually:

Example Plot

# Plot with multiple aesthetics
p <- ggplot(mtcars, aes(mpg, wt,
                        color = factor(cyl),
                        size = hp,
                        shape = factor(am))) +
  geom_point() +
  labs(color = "Cylinders", size = "Horsepower", shape = "Transmission")

p

Hiding Legends

Use legend_hide() to remove specific legends while keeping others:

# Hide the size legend
p + legend_hide(size)


# Hide multiple legends
p + legend_hide(size, shape)

Selecting Legends

Use legend_select() to keep only certain legends (inverse of legend_hide()):

# Keep only the colour legend
p + legend_select(colour)


# Keep colour and shape
p + legend_select(colour, shape)

Controlling Legend Order

By default, legends appear in an unspecified order. Use legend_order_guides() to control the display order:

# Default order
p


# Size legend first, then colour, then shape
p + legend_order_guides(size = 1, colour = 2, shape = 3)

Merging and Splitting Legends

ggplot2 automatically merges legends when they have the same title and matching labels. Use legend_merge() and legend_split() to override this behavior.

Forcing Merge

# Plot where colour and fill map to the same variable
p_merge <- ggplot(mtcars, aes(mpg, wt, color = factor(cyl), fill = factor(cyl))) +
  geom_point(shape = 21, size = 4, stroke = 1.5) +
  labs(color = "Cylinders", fill = "Cylinders")

# Legends merge automatically when titles and labels match
p_merge


# Explicitly request merge (reinforces default behavior)
p_merge + legend_merge(colour, fill)

Forcing Split

# Force separate legends even when they could merge
p_merge + legend_split(colour, fill)

Positioning Legends Separately

Position functions (legend_left(), legend_right(), legend_top(), legend_bottom()) accept a by parameter to position specific legends:

# Place colour legend on the left, size legend at bottom
p +
  legend_hide(shape) +
  legend_left(by = "colour") +
  legend_bottom(by = "size")

# Colour legend on top, size on right
p +
  legend_hide(shape) +
  legend_top(by = "colour") +
  legend_right(by = "size")

Styling Legends Separately

Use the by parameter on legend_style() to apply different styles to different legends:

p +
  legend_hide(shape) +
  legend_style(title_face = "bold", background = "grey95", by = "colour") +
  legend_style(size = 10, by = "size")

Combining Multiple Controls

All functions work together:

# Complex example: hide shape, position colour on left with bold title,
# position size at bottom with smaller text
p +
  legend_hide(shape) +
  legend_left(by = "colour") +
  legend_style(title_face = "bold", title_size = 14, by = "colour") +
  legend_bottom(by = "size") +
  legend_style(size = 9, direction = "horizontal", by = "size")

Four Legends, One per Side

When a plot has four legends and you want one on each side (top, bottom, left, right), you can fine-tune each legend along three axes:

  1. Side placementlegend_top/bottom/left/right(by = "<aes>")

  2. Distance from the panellegend_style(by = "<aes>", margin = c(t, r, b, l))

  3. Slide along the sidelegend_style(by = "<aes>", justification = ...)

For top/bottom legends, justification is "left", "center", "right" (or a number in [0, 1]). For left/right legends, it’s "top", "center", "bottom" (or a number).

p4 <- ggplot(mtcars, aes(mpg, wt,
                         colour = factor(cyl),
                         fill   = factor(gear),
                         size   = hp,
                         shape  = factor(am))) +
  geom_point(stroke = 1.2) +
  labs(colour = "Cyl", fill = "Gear", size = "HP", shape = "AM")

p4 +
  # 1. Send each legend to its side
  legend_top   (by = "colour") +
  legend_bottom(by = "fill")   +
  legend_left  (by = "size")   +
  legend_right (by = "shape")  +

  # 2. Slide each legend along its side
  legend_style(by = "colour", justification = "left") +
  legend_style(by = "fill",   justification = "right") +
  legend_style(by = "size",   justification = "top") +
  legend_style(by = "shape",  justification = "bottom") +

  # 3. Nudge each legend toward/away from the panel via margin (cm)
  legend_style(by = "colour", margin = c(0, 0, 0.3, 0)) +
  legend_style(by = "fill",   margin = c(0.3, 0, 0, 0)) +
  legend_style(by = "size",   margin = c(0, 0.3, 0, 0)) +
  legend_style(by = "shape",  margin = c(0, 0, 0, 0.3))

Each legend_style(by = ...) call is additive — you can chain as many as you need to tune one legend at a time without affecting the others.

Six Legends, Stacked per Side

More than one legend can share a side. ggplot2 stacks them in the order it resolves them, and ggguides lets you pick which legend goes where, slide it along the rail, and style it without touching the others. The plot below has six legends: two on top, two on the right, one on the bottom, one on the left.

p6 <- ggplot(mtcars, aes(mpg, wt)) +
  geom_smooth(aes(linetype = factor(vs)), method = "lm", se = FALSE,
              colour = "grey40") +
  geom_point(aes(colour = factor(cyl),
                 fill   = factor(gear),
                 size   = hp,
                 alpha  = qsec,
                 shape  = factor(am)),
             stroke = 1.2) +
  scale_shape_manual(values = c(21, 24)) +
  labs(colour = "Cyl", fill = "Gear", size = "HP",
       alpha = "QSec", shape = "AM", linetype = "VS")

p6 +
  # 1. Side placement — two legends share the top, two share the right
  legend_top   (by = "colour")   +
  legend_top   (by = "fill")     +
  legend_right (by = "size")     +
  legend_right (by = "alpha")    +
  legend_bottom(by = "shape")    +
  legend_left  (by = "linetype") +

  # 2. Slide each legend along its rail
  #    top/bottom rails: "left" / "center" / "right" (or a number in [0,1])
  #    left/right rails: "top"  / "center" / "bottom"
  legend_style(by = "colour",   justification = "left")   +
  legend_style(by = "fill",     justification = "right")  +
  legend_style(by = "size",     justification = "top")    +
  legend_style(by = "alpha",    justification = "bottom") +
  legend_style(by = "shape",    justification = "center") +
  legend_style(by = "linetype", justification = "center") +

  # 3. Appearance — per-legend title weight, key size, direction, text size
  legend_style(by = "colour",   title_face = "bold",
               key_width = 0.4, key_height = 0.4) +
  legend_style(by = "fill",     title_face = "bold",
               key_width = 0.4, key_height = 0.4) +
  legend_style(by = "size",     title_size = 9, size = 8)  +
  legend_style(by = "alpha",    title_size = 9, size = 8)  +
  legend_style(by = "shape",    direction = "horizontal")  +
  legend_style(by = "linetype", direction = "vertical")    +

  # 4. Nudge each legend toward/away from the panel via margin (cm)
  #    order is c(top, right, bottom, left)
  legend_style(by = "colour",   margin = c(0, 0, 0.2, 0)) +
  legend_style(by = "fill",     margin = c(0, 0, 0.2, 0)) +
  legend_style(by = "size",     margin = c(0, 0, 0, 0.3)) +
  legend_style(by = "alpha",    margin = c(0, 0, 0, 0.3)) +
  legend_style(by = "shape",    margin = c(0.3, 0, 0, 0)) +
  legend_style(by = "linetype", margin = c(0, 0.3, 0, 0))
#> `geom_smooth()` using formula = 'y ~ x'

What each step is doing

Step 1, side placement. legend_<side>(by = "<aes>") sends one legend to one side. Call it twice with different aesthetics and both legends land on that side. In the example, colour and fill share the top rail, size and alpha share the right rail, and shape and linetype each get a side of their own.

Step 2, slide along the rail. legend_style(by = "<aes>", justification = ...) repositions a single legend without affecting its neighbours. The keyword interpretation depends on which rail the legend sits on:

The per-guide form is what you want when different legends need different alignments. For a single alignment applied to every legend at once, use legend_style(justification = ...) without by.

Step 3, appearance. Anything legend_style() accepts (title_face, title_size, size for label text, key_width, key_height, direction, background, and so on) can be scoped to a single legend by adding by = "<aes>". Each call tunes one legend and leaves the others alone, so chaining several short calls is the intended shape. In the example the top legends get bold titles and smaller keys, the right legends get smaller text, shape is forced horizontal so it fits the bottom rail, and linetype stays vertical for the left rail.

Step 4, padding. margin = c(top, right, bottom, left) (in cm) moves a single legend toward or away from the panel. Pad on the side that faces the panel: a bottom legend uses c(top, 0, 0, 0), a left legend uses c(0, right, 0, 0). Without padding, legends often end up pressed against the axis text; a few tenths of a centimetre usually fixes it.

Why one line per parameter

legend_style(by = "<aes>", ...) calls are additive. You can fold every style argument for colour into a single call, but splitting by concern (position, alignment, appearance, padding) keeps each parameter on a line that is easy to locate. When a reviewer asks to move the colour legend half a centimetre down, the edit is one number on one line.

Summary

Function Purpose Parameters
legend_hide() Hide specific legends Aesthetic names (unquoted)
legend_select() Keep only specific legends Aesthetic names (unquoted)
legend_order_guides() Control legend display order Named args: aes = order
legend_merge() Force legends to merge Aesthetic names (unquoted)
legend_split() Force legends to stay separate Aesthetic names (unquoted)
legend_left(by=) Position one legend on left by = "aesthetic"
legend_style(by=) Style one legend by = "aesthetic" + style args

Learn more: