20

I'm trying to arrange multiple plots using grid.arrange. It does the job by the book, and when calling:

p1 <- ggplot(subset(mtcars, cyl = 4), aes(wt, mpg, colour = cyl)) + geom_point() 
p2 <- ggplot(subset(mtcars, cyl = 8), aes(wt, mpg, colour = cyl)) + geom_point()

grid.arrange(p1, p2, ncol = 2)

I get two nice plots, symmetrical in size:

enter image description here

My graphs refer to different parameters but they do share the same colour coding for groups. So I'd like to remove the legend from all but one and find a nice place for it.

However when I try:

p3 <- ggplot(subset(mtcars, cyl = 8), aes(wt, mpg, colour = cyl)) + geom_point() + guides(colour=FALSE)

grid.arrange(p3, p2, ncol = 2)

The plot without the legend gets (correctly) bigger:

enter image description here

I'd like to keep the size (as a length of x axis) to stay the same across graphs.

I'm aware I could use faceting here, but I'll also need to combine various graphs that (I think) will be hard to implement using facets..

Is it possible to do it with grid.arrange? Any other solutions that could help here?

zx8754
  • 52,746
  • 12
  • 114
  • 209
radek
  • 7,240
  • 8
  • 58
  • 83
  • you can accomplish this without `grid.arrange` by facetting. But there's no `class` column in `mtcars` for me to show that. – Arun May 03 '13 at 21:48
  • @Arun SOrry - my mistake. Switched example to cylinders. As I mentioned, I'm aware of the magic of faceting, however I'd like to get away without using it. – radek May 03 '13 at 21:55

3 Answers3

27

Try this, which uses cbind.gtable:

grid.draw(cbind(ggplotGrob(p3), ggplotGrob(p2), size="last"))

enter image description here

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • 2
    +1 I have about 20 lines of code I was just about to paste to accomplish the same thing with a bit of manual fiddling. I *wish* I'd known about `cbind.gtable` before now. Thanks. – Simon O'Hanlon May 03 '13 at 22:00
  • @Josh Thank you. Great solution indeed. But (same as Simon, I need to ask about aspect ratio - is it possible to preserve? – radek May 03 '13 at 22:10
  • 1
    @SimonO101 -- Good question. Doesn't look like it provides for that. If one of you wants to pursue this, I might suggest looking at `getAnywhere("cbind_gtable")`, which is where the units appear to get set, and where you might want to modify the code. – Josh O'Brien May 03 '13 at 22:17
  • 1
    @JoshO'Brien in that case I might just paste my answer which allows for controlling aspect ratios, but it's a lot more hassle than this! – Simon O'Hanlon May 03 '13 at 22:19
  • 1
    @SimonO101 -- Cool. I thought of asking you to do that, and am glad you will. – Josh O'Brien May 03 '13 at 22:22
  • what about I have different number of elements on x axis? I'e get back error: Error: nrow(x) == nrow(y) is not TRUE. But I still want to have the same size of my objects. do you have any ides? thank you ! – maycca Jun 03 '16 at 02:59
12

Not nearly as elegantly simple as @Josh 's solution, but you can do this with grid.arrange which allows you to preserve or specify the aspect ratio of the plots, but you need to make a tableGrob for your legend. I answered a simillar question here which is where I got the handy code for making a tableGrob from a ggplot2 legend:

## Make a tableGrob of your legend
tmp <- ggplot_gtable(ggplot_build(p2))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]

# Plot objects using widths and height and respect to fix aspect ratios
# We make a grid layout with 3 columns, one each for the plots and one for the legend
grid.newpage()
pushViewport( viewport( layout = grid.layout( 1 , 3 , widths = unit( c( 0.4 , 0.4 , 0.2 ) , "npc" ) ,heights = unit( c( 0.45 , 0.45 , 0.45 ) , "npc" ) , respect = matrix(rep(1,3),1) ) ) ) 
print( p1 + theme(legend.position="none") , vp = viewport( layout.pos.row = 1 , layout.pos.col = 1 ) )
print( p2 + theme(legend.position="none") , vp = viewport( layout.pos.row = 1, layout.pos.col = 2 ) )
upViewport(0)
vp3 <- viewport( width = unit(0.2,"npc") , x = 0.9 , y = 0.5)
pushViewport(vp3)
grid.draw( legend )
popViewport()

enter image description here

Community
  • 1
  • 1
Simon O'Hanlon
  • 58,647
  • 14
  • 142
  • 184
  • Thanks a lot. Although longer solution it does the job. Two (novice) questions: 1) Do the units of width sum to 1? What is the `respect` argument specifying? – radek May 04 '13 at 13:04
  • 2
    I find the respsect argument a bit confusing, but essentially I consider it a matrix of the aspect ratio for each viewport, so we have `1,1,1` because we have 1 row and 3 columns and we want a 1:1 aspect ratio in each. That might not be *quite* how it works, but it helps me!!! The units of width don't *have* to sum to 1. You could squeeze them in in less than the width of the plotting region. "npc" is from 0 to 1, with 0 the left edge or bottom of the plot region and 1 the right edge or top and 0.5 the centre. Scale accordingly.] – Simon O'Hanlon May 04 '13 at 13:10
2

I thought I'd update this thread because grid.arrange now has this functionality:

grid.arrange(p3, p2, ncol = 2, widths = c(1,1.2))

equally sized panels

Community
  • 1
  • 1