Preserve proportion of graphs using grid.arrange
Asked Answered
L

3

21

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?

Lungki answered 3/5, 2013 at 21:32 Comment(2)
you can accomplish this without grid.arrange by facetting. But there's no class column in mtcars for me to show that.Daleth
@Daleth 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.Lungki
E
27

Try this, which uses cbind.gtable:

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

enter image description here

Espousal answered 3/5, 2013 at 21:58 Comment(6)
+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.Furrow
@Josh Thank you. Great solution indeed. But (same as Simon, I need to ask about aspect ratio - is it possible to preserve?Lungki
@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.Acanthous
@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!Furrow
@SimonO101 -- Cool. I thought of asking you to do that, and am glad you will.Acanthous
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 !Exhibitionist
M
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

Manche answered 3/5, 2013 at 22:22 Comment(2)
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?Lungki
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.]Furrow
L
3

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

Loree answered 27/3, 2020 at 9:56 Comment(2)
Thanks! How did you arrive at width values? Is that approximate?Lungki
I estimated the relative proportions until it looked right.Loree

© 2022 - 2024 — McMap. All rights reserved.