ggplot2 - How to add unique legend for multiple plots with grid.arrange?

天大地大妈咪最大 提交于 2021-02-07 08:12:37

问题


I have a plot with 4 panels inside made with ggplot2 and grid.arrange. Each of the panel has a legend which is the same for all of them.

How can I remove the 4 legends and create a unique one at the bottom of the plot?

Here my sample data and plot:

set.seed(100)
df_1 = data.frame(lat = rnorm(20), 
                  lon = rnorm(20), 
                  cor = c(rep('positive', 7), rep('negative', 13)), 
                  sign = c(rep(99, 5), rep(95, 6), rep(90,9)))

lst_df = list(df_1, df_1, df_1, df_1)


library(ggplot2)
library(gridExtra)
library(grid)

for (i in 1:length(lst_df)) {
p[[i]] = ggplot() +

    geom_point(data=lst_df[[i]], aes(x=lon, y=lat, size=sign, colour = cor), alpha = 0.5) +

    scale_color_manual(values=c("blue", "orange"),
                       name='col', 
                       labels = c('neg', 'pos'),
                       guide = guide_legend(override.aes = list(alpha = 1, size = 3))) +

    scale_size(range = c(1,3), 
               breaks = c(90, 95, 99),
               labels = c(1, 5, 10),
               name = 'test',
               guide = guide_legend(override.aes = list(colour = 'black', 
                                                        alpha = 1)))
}


grid.arrange(p[[1]], p[[2]], p[[3]], p[[4]], 
             ncol=2, nrow=2,
             top=textGrob(expression(bold("test")), gp=gpar(fontsize=25, face= 'bold')))

Any suggestion? Thanks


回答1:


Here's a workflow with cowplot, which provides some neat functions for putting grobs together and extracting elements like legends. They have a detailed vignette on creating grids of plots with shared legends like you're looking for. Similarly, the vignette on plot annotations goes over cowplot functions for creating and adding labels--they function much like the other plot elements and can be used in cowplot::plot_grid.

The process is basically

  1. Creating a list of plots without legends, using lapply (could instead be a loop)
  2. Extracting one of the legends--doesn't matter which since they're all the same--that's been set to position at the bottom
  3. Creating a text grob for the title
  4. Creating the grid from the list of legendless plots
  5. Creating a grid from the title, the legendless plots grid, and the legend

As an aside, loading cowplot lets it set its default ggplot theme, which I don't particularly like, so I use cowplot::function notation instead of library(cowplot).

You can tweak the relative heights used to make the final grid--this was the first ratio that worked well for me.

Should it come up, I posted a question a few months ago on making the draw_label grob take theme guidelines like you would expect in normal ggplot elements like titles; answers from the package author and my specialty function are here.

library(ggplot2)
...

p_no_legend <- lapply(p, function(x) x + theme(legend.position = "none"))
legend <- cowplot::get_legend(p[[1]] + theme(legend.position = "bottom"))

title <- cowplot::ggdraw() + cowplot::draw_label("test", fontface = "bold")

p_grid <- cowplot::plot_grid(plotlist = p_no_legend, ncol = 2)
cowplot::plot_grid(title, p_grid, legend, ncol = 1, rel_heights = c(0.1, 1, 0.2))

Created on 2018-09-11 by the reprex package (v0.2.0).




回答2:


As camille pointed out, cowplot has a really easy way to do this. You can also do it by manually arranging your grobs. This works especially well if you have plots with different widths, scales, and breaks. One example here.

The benefit of doing this manually, unless cowplot can, is that you can specify which plots get x-axis labels, and y-axis titles/labels, so in the end you can only have x-axis titles/labels on your bottom grids, and y-axis titles/labels on your left grids. The downside, being it's tedious to define multiple plotting functions if you have several plots.

df_plots <- lapply(lst_df, function(x) {
ggplot() +    
geom_point(data=lst_df[[i]], aes(x=lon, y=lat, size=sign, colour = cor), alpha = 0.5) +
scale_color_manual(values=c("blue", "orange"),
                   name='col', 
                   labels = c('neg', 'pos'),
                   guide = guide_legend(override.aes = list(alpha = 1, size = 3))) +
scale_size(range = c(1,3), 
           breaks = c(90, 95, 99),
           labels = c(1, 5, 10),
           name = 'test',
           guide = guide_legend(override.aes = list(colour = 'black', 
                                                    alpha = 1)))
})

df_leg <-  get_legend(df_plots[[1]]) # get legend

df_plots_noleg <- lapply(lst_df, function(x) { # make plots with no legends, however you want to do this
ggplot() +

geom_point(data=lst_df[[i]], aes(x=lon, y=lat, size=sign, colour = cor), alpha = 0.5) +

scale_color_manual(values=c("blue", "orange"),
                   name='col', 
                   labels = c('neg', 'pos'),
                   guide = guide_legend(override.aes = list(alpha = 1, size = 3))) +
theme(legend.position = "none")
})

If you have plots that start to vary in widths, this is important to align your axes. Since in this example they're all the same, this doesn't matter.

#maxWidth_df_plots <- grid::unit.pmax(df_plots_noleg1$widths[2:5], df_plots_noleg2$widths[2:5], df_plots_noleg3$widths[2:5], df_plots_noleg4$widths[2:5]) #get your grob widths

#df_plots_noleg1$widths[2:5] <- as.list(maxWidth1Page1)
#df_plots_noleg2$widths[2:5] <- as.list(maxWidth1Page1)
#df_plots_noleg3$widths[2:5] <- as.list(maxWidth1Page1)
#df_plots_noleg4$widths[2:5] <- as.list(maxWidth1Page1)

df_plots1 <- ggplotGrob(df_plots_noleg[[1]])
df_plots2 <- ggplotGrob(df_plots_noleg[[2]])
df_plots3 <- ggplotGrob(df_plots_noleg[[3]])
df_plots4 <- ggplotGrob(df_plots_noleg[[4]])

df_plot_arranged <- grid.arrange(arrangeGrob(df_plots1, df_plots2, df_plots3, df_plots4, nrow = 2),
                             arrangeGrob(nullGrob(), df_leg, nullGrob(), nrow = 1), ncol = 1, heights = c(4,0.5),
                             top = textGrob("Title Text Holder Here", gp = gpar(fotsize = 12, font = 2)))

The benefit that I mentioned is you can control what graphs get what labeling. This is where controlling your widths would help, as you can see in the image the right plots are wider than the left, and the ones on top are taller than the ones on the bottom.

df_plots1 <- df_plots_noleg[[1]] + theme(axis.title.x = element_blank(), axis.text.x = element_blank())

df_plots2 <- df_plots_noleg[[2]] + theme(axis.title.x = element_blank(), axis.title.y = element_blank(),
                               axis.text.x = element_blank(), axis.text.y = element_blank())

df_plots4 <- df_plots_noleg[[4]] + theme(axis.title.y = element_blank(), axis.text.y = element_blank())

df_plots1 <- ggplotGrob(df_plots1)
df_plots2 <- ggplotGrob(df_plots2)
df_plots3 <- ggplotGrob(df_plots_noleg[[3]])
df_plots4 <- ggplotGrob(df_plots4)

df_plot_arranged <- grid.arrange(arrangeGrob(df_plots1, df_plots2, df_plots3, df_plots4, nrow = 2),
                             arrangeGrob(nullGrob(), df_leg, nullGrob(), nrow = 1), ncol = 1, heights = c(4,0.5),
                             top = textGrob("Title Text Holder Here", gp = gpar(fotsize = 12, font = 2)))



来源:https://stackoverflow.com/questions/52278623/ggplot2-how-to-add-unique-legend-for-multiple-plots-with-grid-arrange

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!