rggplot2plotshapefilecowplot

Can't add legend to a cowplot multiplot


I've created a map whereby each quarter has its own histogram. The histograms share a legend, thus I'd like a legend to be placed horizontally underneath the map, which I've tried to do with get_legend but I keep getting the warning, and no legend is plotted.

In get_plot_component(plot, "guide-box") :
 Multiple components found; returning the first one. To return all, use `return_all = TRUE`.'

So I'm providing the original code hoping someone can tell me how to create the desired plot. Furthermore, if anyone has tips on how to ggsave the final object without the positions of each subplot moving from how it looks if I open the plot in a new window from R using 'zoom', that would be helpful too. I always have to play around with the width and the height to reproduce that.

Here is what the map looks like now, in plot zoom: enter image description here

Here is the code to create it. I can't provide the original object because the dput() would be extremely long: enter image description here

# Reshape the data from wide to long format
quarts_long <- tidyr::pivot_longer(quarts, cols = c("Mammals", "Birds", "Amphibians", "Reptiles"), names_to = "Class", values_to = "Count")

# Calculate centroids of each quarter
quarts_centroids <- st_centroid(quarts)

# Create histograms for each quarter
hist_plots <- list()
for (i in 1:nrow(quarts)) {
  hist_plots[[i]] <- ggplot(quarts_long[quarts_long$Quarter == quarts$Quarter[i],]) +
    geom_col(aes(x = Class, y = Count, fill = Class)) +  
    scale_y_continuous(limits = c(0, 300), breaks = c(0, 150, 300)) +
    labs(y = "", x = "") +  # Remove axis labels
    theme_void() +  # Use minimal theme
    theme(axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0)),  # Adjust y-axis label position
          axis.text.x = element_blank(),  # Remove x-axis text
          axis.text.y = element_text(size = 10),
          legend.position = "none")  # Adjust y-axis text size
}

# Create the quarters map
map_plot <- ggplot() +
  geom_sf(data = quarts, fill = alpha("white", 0)) +
  labs(x = NULL, y = NULL) +
  theme_void() +
  theme(legend.position = "none")  # Move legend to bottom

# Define shared legend
shared_legend <- get_legend(hist_plots[[1]])

# Draw the final plot with legend
p <- ggdraw() +
  draw_plot(hist_plots[[1]], 0.48, 0.63, 0.17, 0.15) +  # Draw the northeast
  draw_plot(hist_plots[[2]], 0.28, 0.63, 0.17, 0.15) +  # Draw the northwest
  draw_plot(hist_plots[[3]], 0.48, 0.38, 0.17, 0.15) +  # Draw the southeast
  draw_plot(hist_plots[[4]], 0.28, 0.38, 0.17, 0.15) +  # Draw the southwest
  draw_plot(map_plot, 0.05, 0.05, 0.95, 0.95)  + # Draw the quarters map
  draw_plot(shared_legend, 0.2, 0.05, 0.6, 0.1)


Solution

  • You have at least one and possibly two issues. To start, here's a simpler reprex that produces the same problem:

    library(ggplot2)
    library(cowplot)
    
    p <- ggplot(mpg, aes(cty, hwy)) +
      geom_point(aes(color = drv)) +
      theme(legend.position = "none")
    
    shared_legend <- get_legend(p)
    #> Warning in get_plot_component(plot, "guide-box"): Multiple components found;
    #> returning the first one. To return all, use `return_all = TRUE`.
    
    ggdraw() +
      draw_plot(p, x = 0, width = .45) +
      draw_plot(p, x = .45, width = .45) +
      draw_plot(shared_legend, x = .85, width = .2)
    

    1. Don't remove the legend before getting it

    Your first problem is that you're specifying legend.position = "none" in theme(). This removes the legend, so there's no legend for get_legend() to "get." Instead, remove legend.position = "none" from your original plot spec, then add it after getting the legend:

    library(ggplot2)
    library(cowplot)
    
    p <- ggplot(mpg, aes(cty, hwy)) +
      geom_point(aes(color = drv))
    
    shared_legend <- get_legend(p)
    #> Warning in get_plot_component(plot, "guide-box"): Multiple components found;
    #> returning the first one. To return all, use `return_all = TRUE`.
    
    p <- p + theme(legend.position = "none")
    
    ggdraw() +
      draw_plot(p, x = 0, width = .45) +
      draw_plot(p, x = .45, width = .45) +
      draw_plot(shared_legend, x = .85, width = .2)
    

    2. Avoid get_legend() bug

    This will solve the issue as long as legend.position = "right", which is the default. But otherwise, get_legend() won't return the legend, which is a known issue.

    As a workaround, you can use get_plot_component(plot, "guide-box", return_all = TRUE). You'll need to inspect this to see what position the legend is in, and index into the result accordingly. e.g., for legend.position = "bottom":

    p <- ggplot(mpg, aes(cty, hwy)) +
      geom_point(aes(color = drv)) +
      theme(legend.position = "bottom")
    
    # when position = "bottom," legend is the third grob in the list
    shared_legend <- cowplot::get_plot_component(p, "guide-box", return_all = TRUE)[[3]]
    
    p <- p + theme(legend.position = "none")
    
    ggdraw() +
      draw_plot(p, x = 0, y = .1, width = .5, height = .9) +
      draw_plot(p, x = .5, y = .1, width = .5, height = .9) +
      draw_plot(shared_legend, x = 0, y = 0, height = .2)
    

    Created on 2024-04-11 with reprex v2.1.0