215

I have two ggplots which I align horizontally with grid.arrange. I have looked through a lot of forum posts, but everything I try seem to be commands that are now updated and named something else.

My data looks like this;

# Data plot 1 
 axis1 axis2 
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.417117 -0.002592
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.186860 -0.203273
# Data plot 2 
 axis1 axis2
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988
#And I run this:
library(ggplot2)
library(gridExtra)
groups=c('group1','group2','group3','group4','group1','group2','group3','group4')
x1=data1[,1]
y1=data1[,2]
x2=data2[,1]
y2=data2[,2]
p1=ggplot(data1, aes(x=x1, y=y1,colour=groups)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
p2=ggplot(data2, aes(x=x2, y=y2,colour=groups)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
#Combine plots
p3=grid.arrange(
p1 + theme(legend.position="none"), p2+ theme(legend.position="none"), nrow=1, widths = unit(c(10.,10), "cm"), heights = unit(rep(8, 1), "cm")))

How would I extract the legend from any of these plots and add it to the bottom/centre of the combined plot?

Henrik
68k15 gold badges153 silver badges166 bronze badges
asked Nov 30, 2012 at 16:48
2
  • 4
    I occasionally have this problem. If you don't want to facet the plot the easiest solution I know is just to save one with a legend then use Photoshop/Ilustrator to paste it onto the blank legend plots. Inelegant I know -- but practical quick and easy. Commented Nov 30, 2012 at 17:04
  • 1
    @StephenHenderson That's an answer. Facet or post-process with gfx editor. Commented Nov 30, 2012 at 18:00

9 Answers 9

204

You may also use ggarrange from ggpubr package and set "common.legend = TRUE":

library(ggpubr)
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data = dsamp, colour = clarity)
p2 <- qplot(cut, price, data = dsamp, colour = clarity)
p3 <- qplot(color, price, data = dsamp, colour = clarity)
p4 <- qplot(depth, price, data = dsamp, colour = clarity) 
ggarrange(p1, p2, p3, p4, ncol=2, nrow=2, common.legend = TRUE, legend="bottom")

enter image description here

answered Oct 27, 2017 at 16:36
Sign up to request clarification or add additional context in comments.

6 Comments

Is it possible that this doesn't work inside a shiny application (or flexdashboard) with renderPlot()? It works perfectly fine in a normal R script with normal plots. But when I do the exact same thing with plots made with renderPlot() in my flexdashboard, nothing appears.
@Tingolfin I have had to sometimes wrap print(ggarrangeobject) around one of my ggarrange objects when I needed it to be plotted by some other function, that may be similar to the solution for your renderPlot()?
common.legend = TRUE is all that I need!
Great. If I wanted to make the common legend a single horizontal line, would that be possible? So instead of having two rows for clarity, just a single one?
How do you make the legend displayed into several rows?
|
130

Update 2021-Mar

This answer has still some, but mostly historic, value. Over the years since this was posted better solutions have become available via packages. You should consider the newer answers posted below.

Update 2015-Feb

See Steven's answer below


df1 <- read.table(text="group x y 
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.417117 -0.002592
group1 -0.212201 0.358867
group2 -0.279756 -0.126194
group3 0.186860 -0.203273
group4 0.186860 -0.203273",header=TRUE)
df2 <- read.table(text="group x y 
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988
group1 0.211826 -0.306214
group2 -0.072626 0.104988
group3 -0.072626 0.104988
group4 -0.072626 0.104988",header=TRUE)
library(ggplot2)
library(gridExtra)
p1 <- ggplot(df1, aes(x=x, y=y,colour=group)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8) + theme(legend.position="bottom")
p2 <- ggplot(df2, aes(x=x, y=y,colour=group)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)
#extract legend
#https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
g_legend<-function(a.gplot){
 tmp <- ggplot_gtable(ggplot_build(a.gplot))
 leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
 legend <- tmp$grobs[[leg]]
 return(legend)}
mylegend<-g_legend(p1)
p3 <- grid.arrange(arrangeGrob(p1 + theme(legend.position="none"),
 p2 + theme(legend.position="none"),
 nrow=1),
 mylegend, nrow=2,heights=c(10, 1))

Here is the resulting plot: 2 plots with common legend

answered Nov 30, 2012 at 18:20

5 Comments

both answers point to the same wiki page which can be updated as new versions of ggplot2 break the code.
This may be straightforward for some/most people, but I didn't get it right away so thought I'd comment. If you want the common legend on top of the plot (rather than below), all you need to do is switch the arguments. In the above example, mylegend goes before arrangeGrob(). You also need to reverse the heights (i.e. heights=c(1,10)
Is there a way to center the bottom legend or shift it to the right a bit?
@wolfsatthedoor theme(legend.position="bottom", legend.justification = c(0.75, 0.5)) A value of 0.5 is the center, larger values shift to right/bottom.
I am trying to implement this code but I am getting an error on g_legend() : "Error in tmp$grobs[[leg]] : attempt to select less than one element in get1index". What is the possible issue?
100

A new, attractive solution is to use patchwork. The syntax is very simple:

library(ggplot2)
library(patchwork)
p1 <- ggplot(df1, aes(x = x, y = y, colour = group)) + 
 geom_point(position = position_jitter(w = 0.04, h = 0.02), size = 1.8)
p2 <- ggplot(df2, aes(x = x, y = y, colour = group)) + 
 geom_point(position = position_jitter(w = 0.04, h = 0.02), size = 1.8)
combined <- p1 + p2 & theme(legend.position = "bottom")
combined + plot_layout(guides = "collect")

Created on 2019年12月13日 by the reprex package (v0.2.1)

answered Dec 13, 2019 at 14:32

5 Comments

If you slightly change the order of commands you can do this in one line: combined <- p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = "bottom")
Why won’t patchwork place the legend between the plots now? It seems to have done it with this example but when I try this using the development version of patchwork it keeps placing the legend at the left margin and not the center. Thanks.
Using the latest CRAN version of patchwork (1.1.1), I still get the same output as in the answer here. As for the development version, I am not sure, but I suppose you could open an issue in the repo with a reprex to ask if what you're getting is intended. I don't see anything about this in the NEWS file, so I'm thinking it might not be.
It is possible to combine the x axis labs into a common one for two plot within patchwork?
@Nip: Not in the same simple way as for legends. See this issue.
68

Roland's answer needs updating. See: https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs

This method has been updated for ggplot2 v1.0.0.

library(ggplot2)
library(gridExtra)
library(grid)
grid_arrange_shared_legend <- function(...) {
 plots <- list(...)
 g <- ggplotGrob(plots[[1]] + theme(legend.position="bottom"))$grobs
 legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
 lheight <- sum(legend$height)
 grid.arrange(
 do.call(arrangeGrob, lapply(plots, function(x)
 x + theme(legend.position="none"))),
 legend,
 ncol = 1,
 heights = unit.c(unit(1, "npc") - lheight, lheight))
}
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data=dsamp, colour=clarity)
p2 <- qplot(cut, price, data=dsamp, colour=clarity)
p3 <- qplot(color, price, data=dsamp, colour=clarity)
p4 <- qplot(depth, price, data=dsamp, colour=clarity)
grid_arrange_shared_legend(p1, p2, p3, p4)

Note the lack of ggplot_gtable and ggplot_build. ggplotGrob is used instead. This example is a bit more convoluted than the above solution but it still solved it for me.

Mateusz1981
1,89720 silver badges35 bronze badges
answered Feb 18, 2015 at 21:15

7 Comments

Hi, I have 6 plots, and I would like to arrange the 6 plots as 2 row × 3 col and draw the legend on the top, so how to change the function grid_arrange_shared_legend? Thank you!
@just_rookie did you find a solution how to change the function so that one can use different ncol and nrow arrangements instead of only ncol = 1?
Hi, i tried this solution, it works good, however when printing it, i got 2 pdf pages instead of only 1, the first is blank while the later contains my plot, why i got such behavior ? thanks,
for anyone how get the same isse as me, here is a workaround: stackoverflow.com/questions/12481267/…
Great stuff here. Any idea how one can add a single title for all plots?
|
28

I suggest using cowplot. From their R vignette:

# load cowplot
library(cowplot)
# down-sampled diamonds data set
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
# Make three plots.
# We set left and right margins to 0 to remove unnecessary spacing in the
# final plot arrangement.
p1 <- qplot(carat, price, data=dsamp, colour=clarity) +
 theme(plot.margin = unit(c(6,0,6,0), "pt"))
p2 <- qplot(depth, price, data=dsamp, colour=clarity) +
 theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
p3 <- qplot(color, price, data=dsamp, colour=clarity) +
 theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
# arrange the three plots in a single row
prow <- plot_grid( p1 + theme(legend.position="none"),
 p2 + theme(legend.position="none"),
 p3 + theme(legend.position="none"),
 align = 'vh',
 labels = c("A", "B", "C"),
 hjust = -1,
 nrow = 1
 )
# extract the legend from one of the plots
# (clearly the whole thing only makes sense if all plots
# have the same legend, so we can arbitrarily pick one.)
legend_b <- get_legend(p1 + theme(legend.position="bottom"))
# add the legend underneath the row we made earlier. Give it 10% of the height
# of one plot (via rel_heights).
p <- plot_grid( prow, legend_b, ncol = 1, rel_heights = c(1, .2))
p

combined plots with legend at bottom

answered Jun 26, 2017 at 18:07

1 Comment

This was only way, that maked a possibled to put a manual legend in my plot with annotate_figure(ggarrange()), using a legend_b(). Thank you so much, god blesse you!
14

@Giuseppe, you may want to consider this for a flexible specification of the plots arrangement (modified from here):

library(ggplot2)
library(gridExtra)
library(grid)
grid_arrange_shared_legend <- function(..., nrow = 1, ncol = length(list(...)), position = c("bottom", "right")) {
 plots <- list(...)
 position <- match.arg(position)
 g <- ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs
 legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
 lheight <- sum(legend$height)
 lwidth <- sum(legend$width)
 gl <- lapply(plots, function(x) x + theme(legend.position = "none"))
 gl <- c(gl, nrow = nrow, ncol = ncol)
 combined <- switch(position,
 "bottom" = arrangeGrob(do.call(arrangeGrob, gl),
 legend,
 ncol = 1,
 heights = unit.c(unit(1, "npc") - lheight, lheight)),
 "right" = arrangeGrob(do.call(arrangeGrob, gl),
 legend,
 ncol = 2,
 widths = unit.c(unit(1, "npc") - lwidth, lwidth)))
 grid.newpage()
 grid.draw(combined)
}

Extra arguments nrow and ncol control the layout of the arranged plots:

dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data = dsamp, colour = clarity)
p2 <- qplot(cut, price, data = dsamp, colour = clarity)
p3 <- qplot(color, price, data = dsamp, colour = clarity)
p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend(p1, p2, p3, p4, nrow = 1, ncol = 4)
grid_arrange_shared_legend(p1, p2, p3, p4, nrow = 2, ncol = 2)

enter image description here enter image description here

answered Jul 17, 2016 at 11:25

3 Comments

Same as for the other solution, i tried it, it works good, however when printing it, i got 2 pdf pages instead of only 1, the first is blank while the later contains my plot, why i got such behavior ? thanks,
for anyone how get the same isse as me, here is a workaround: stackoverflow.com/questions/12481267/…
Can someone explain me the solution ? How can i a place the legend at the top instead of the bottom ? Thanks
10

If you are plotting the same variables in both plots, the simplest way would be to combine the data frames into one, then use facet_wrap.

For your example:

big_df <- rbind(df1,df2)
big_df <- data.frame(big_df,Df = rep(c("df1","df2"),
times=c(nrow(df1),nrow(df2))))
ggplot(big_df,aes(x=x, y=y,colour=group)) 
+ geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8) 
+ facet_wrap(~Df)

Plot 1

Another example using the diamonds data set. This shows that you can even make it work if you have only one variable common between your plots.

diamonds_reshaped <- data.frame(price = diamonds$price,
independent.variable = c(diamonds$carat,diamonds$cut,diamonds$color,diamonds$depth),
Clarity = rep(diamonds$clarity,times=4),
Variable.name = rep(c("Carat","Cut","Color","Depth"),each=nrow(diamonds)))
ggplot(diamonds_reshaped,aes(independent.variable,price,colour=Clarity)) + 
geom_point(size=2) + facet_wrap(~Variable.name,scales="free_x") + 
xlab("")

Plot 2

Only tricky thing with the second example is that the factor variables get coerced to numeric when you combine everything into one data frame. So ideally, you will do this mainly when all your variables of interest are the same type.

Prradep
5,7566 gold badges46 silver badges85 bronze badges
answered Dec 15, 2017 at 20:07

Comments

5

@Guiseppe:

I have no idea of Grobs etc whatsoever, but I hacked together a solution for two plots, should be possible to extend to arbitrary number but its not in a sexy function:

plots <- list(p1, p2)
g <- ggplotGrob(plots[[1]] + theme(legend.position="bottom"))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
lheight <- sum(legend$height)
tmp <- arrangeGrob(p1 + theme(legend.position = "none"), p2 + theme(legend.position = "none"), layout_matrix = matrix(c(1, 2), nrow = 1))
grid.arrange(tmp, legend, ncol = 1, heights = unit.c(unit(1, "npc") - lheight, lheight))
answered Jan 16, 2016 at 11:41

Comments

4

If the legend is the same for both plots, there is a simple solution using grid.arrange(assuming you want your legend to align with both plots either vertically or horizontally). Simply keep the legend for the bottom-most or right-most plot while omitting the legend for the other. Adding a legend to just one plot, however, alters the size of one plot relative to the other. To avoid this use the heights command to manually adjust and keep them the same size. You can even use grid.arrange to make common axis titles. Note that this will require library(grid) in addition to library(gridExtra). For vertical plots:

y_title <- expression(paste(italic("E. coli"), " (CFU/100mL)"))
grid.arrange(arrangeGrob(p1, theme(legend.position="none"), ncol=1), 
 arrangeGrob(p2, theme(legend.position="bottom"), ncol=1), 
 heights=c(1,1.2), left=textGrob(y_title, rot=90, gp=gpar(fontsize=20)))

Here is the result for a similar graph for a project I was working on: enter image description here

kangaroo_cliff
6,2523 gold badges33 silver badges45 bronze badges
answered Jul 21, 2020 at 14:26

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.