6

Since I have updated to ggplot2 2.0.0, I cannot arrange charts propperly using gridExtra. The issue is that the faceted charts will get compressed while other will expand. The widths are basically messed up. I want to arrange them similar to the way these single facet plots are: left align two graph edges (ggplot)

I put a reproducible code

library(grid) # for unit.pmax()
library(gridExtra)

plot.iris <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) + 
  geom_point() + 
  facet_grid(. ~ Species) + 
  stat_smooth(method = "lm")

plot.mpg <- ggplot(mpg, aes(x = cty, y = hwy, colour = factor(cyl))) + 
  geom_point(size=2.5)

g.iris <- ggplotGrob(plot.iris) # convert to gtable
g.mpg <- ggplotGrob(plot.mpg) # convert to gtable

iris.widths <- g.iris$widths # extract the first three widths, 
mpg.widths <- g.mpg$widths # same for mpg plot
max.widths <- unit.pmax(iris.widths, mpg.widths)

g.iris$widths <- max.widths # assign max. widths to iris gtable
g.mpg$widths <- max.widths # assign max widths to mpg gtable

grid.arrange(g.iris,g.mpg,ncol=1)

enter image description here

As you will see, the top chart, the first facet is expanded while the other 2 get compressed at the right. Bottom chart does not cover all width.

Could it be that the new ggplot2 version is messing with the gtable widths?

Anyone know a workaround?

Thank you very much

EDIT: Added picture of chart

I'm looking for something like:

enter image description here

Community
  • 1
  • 1
Jordi Vidal
  • 439
  • 1
  • 6
  • 10
  • g.iris$widths and g.iris$widths are different lengths so (i think) you cant directly apply unit.pmax across them. So just try applying it to the widths for the part to the left and right of the panels. `max.widths <- unit.pmax(iris.widths[1:3], mpg.widths[1:3]); g.iris$widths[1:3] <- max.widths; g.mpg$widths[1:3] <- max.widths ; g.iris$widths[9] <- unit.c(g.mpg$widths[5]+ g.mpg$widths[6])` – user20650 Jan 14 '16 at 20:15
  • Thanks for the suggestion! I also tried subsetting the widths, but did not work either. the last part of the code I haven't seen it before, but when I do apply it, I get an error from grid.arrange (and grid.draw) `Error in unit(widths, default.units) : (list) object cannot be coerced to type 'double'`. I have also tried to search what each number in gtable widths mean, but I haven't managed to get that info – Jordi Vidal Jan 14 '16 at 21:13
  • For your example above, it should run without error - does for me (whether its the best way to do it is another question ;)). I replaced your three lines `max.widths <- unit.pmax(iris.widths, mpg.widths) ; ;g.iris$widths <- max.widths ; g.mpg$widths <- max.widths` with the four lines in the comment above. – user20650 Jan 14 '16 at 21:21
  • Then this is very odd, because for me it throws the error, on the example code I provided... I will test on another machine tomorrow see if it makes a difference – Jordi Vidal Jan 14 '16 at 22:01
  • Could be a version thing.. im not familiar enough with grid to know.. but fwiw .. Rv3.2.3,gridExtra v2.1.0, ggplot2 v2.0.0 – user20650 Jan 14 '16 at 22:15
  • I updated with what I felt was more accurately capturing hat you're after. I'f I'm off base please roll back the edit. – Tyler Rinker Jan 15 '16 at 02:31
  • I have checked with another machine and I get the same error (on both Mac and Linux machines). I am with R 3.2.3, ggplot2 2.0.0 but gridExtra 2.0.0. Where did you get version 2.1.0? The version available at CRAN is 2.0.0 – Jordi Vidal Jan 15 '16 at 16:55
  • @JordiVidal; from github.. `devtools::install_github('baptiste/gridextra')` ( I wonder if you needed to do this previously`g.iris$widths[[9]] <- unit.c(g.mpg$widths[[5]]+ g.mpg$widths[[6]]` - i cant remember) – user20650 Jan 15 '16 at 22:28
  • @JordiVidal; just to update.. I tried the code in my first comment on an windows r system with older packages (rv3.2.0, gridExtra v2.0, ggplot v1.0.1) and it worked without problem. After updating ggplot2 to v2 it received the problem you mention in your comment (g.mpg$widths has a different structure than before after assigning the widths to it). After updating gridExtra to the dev version from github, the problem resolved.(I wonder if it is to do with gtable then??) – user20650 Jan 16 '16 at 22:20
  • Hi user20650. Thank you very much for pointing to install_github! Indeed it resolved the problem. I have applied the solution to my original coding, but I had some issues with the plots not rendering correctly (I have 4 different charts, 1 per row). The only way I have solve it is to take width of 1 of the plots and assign it to the rest, without using unit.pmax (`g.iris$widths[1:3] <- g.mpg$widths[1:3]`). gtable is poorly documented and cannot find anywhere what do the different numbers in `g.iris$widths` are, which increases difficulty in trying to solve it. Thank you very much for the help! – Jordi Vidal Jan 17 '16 at 10:11
  • @JordiVidal; These may help in seeing the plot layout. `gtable::gtable_show_layout(g.iris) ; g.iris$layout` In plot.iris, we have nine-widths, so focusing on the middle - horizontal panels, left to right , we have the 1)left outer margin, 2)space for the axis title, 3)axis labels / ticks, 4)plot (left facet), 5)space between facet, 6)plot (middle facet), 7)space between facet, 8)plot (right facet), 9)right outer margin. – user20650 Jan 18 '16 at 22:35
  • Note that the other plot also has a legend to the right of the plot, hence why we add the width of the legend + outer legend together, and overwrite the width of the right margin of g.iris. – user20650 Jan 18 '16 at 22:35
  • Do you perhaps have an understanding of the different values found in widths? I cannot seem to find it anywhere! – Jordi Vidal Jan 19 '16 at 19:40

4 Answers4

5

one option is to massage each plot into a 3x3 gtable, where the central cell wraps all the plot panels.

Using the example from @SandyMuspratt

# devtools::install_github("baptiste/egg") 
grid.draw(egg::ggarrange(plots=plots, ncol=1))

enter image description here

the advantage being that once in this standardised format, plots may be combined in various layouts much more easily, regardless of number of panels, legends, axes, strips, etc.

grid.newpage()
grid.draw(ggarrange(plots=list(p1,  p4, p2, p3), widths = c(2,1), debug=TRUE))

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • 1
    your egg package is a gem! Aligning each element is purely awesome. Faceted and unfacet plots are then just align based on plot area. And axis labels can have different lengths! Made my day and many more to come for sure. Thanks a lot! – aurelien Jul 05 '16 at 15:01
  • This is awesome solution! I'm wondering if it's possible to control how big different individual plots are within `egg`? For example, plot1 is 1/2 the size of the combined plot while plot2 to 4 place in the remaining half. Thanks! – Tung Sep 28 '16 at 18:15
  • 1
    (assuming you're talking about the last example, 2x2 layout) that's kind of what widths does (should do); there's a bug though IIRC so you may have to manually edit the resulting gtable widths/heights in some cases – baptiste Sep 28 '16 at 18:33
  • How can I change the strips height? because when i use ggarrange to combine in ggplot() + facet_grid() for an another ggplot(), those strips become short. – Jean Karlos Nov 22 '21 at 22:07
1

I'm not sure if you're still looking for a solution, but this is fairly general. I'm using ggplot 2.1.0 (now on CRAN). It's based on this solution. I break the problem into two parts. First, I deal with the left side of the plots, making sure the widths for the axis material are the same. This has already been done by others, and there are solutions on SO. But I don't think the result looks good. I would prefer the panels to align on the right side as well. So second, the procedure makes sure the widths of the columns to the right of the panels are the same. It does this by adding a column of appropriate width to the right of each of the plots. (There's possibly neater ways to do it. There is - see @baptiste solution.)

library(grid)    # for pmax
library(gridExtra) # to arrange the plots
library(ggplot2)   # to construct the plots
library(gtable)   # to add columns to gtables of plots without legends

mpg$g = "Strip text"  

# Four fairly irregular plots: legends, faceting, strips
p1 <- ggplot(mpg, aes(displ, 1000*cty)) + 
  geom_point() + 
  facet_grid(. ~ drv) + 
  stat_smooth(method = "lm")

p2 <- ggplot(mpg, aes(x = hwy, y = cyl, colour = factor(cyl))) + 
  geom_point() + 
  theme(legend.position=c(.8,.6),
        legend.key.size = unit(.3, "cm"))

p3 <- ggplot(mpg, aes(displ, cty, colour = factor(drv))) + 
  geom_point() + 
  facet_grid(. ~ drv) 


p4 <- ggplot(mpg, aes(displ, cty, colour = factor(drv))) + 
  geom_point() + 
  facet_grid(g ~ .) 

# Sometimes easier to work with lists, and it generalises nicely
plots = list(p1, p2, p3, p4)

# Convert to gtables
g = lapply(plots, ggplotGrob) 

# Apply the un-exported unit.list function for grid package to each plot
g.widths = lapply(g, function(x) grid:::unit.list(x$widths)) 


## Part 1: Make sure the widths of left axis materials are the same across the plots
# Get first three widths from each plot
g3.widths <- lapply(g.widths, function(x) x[1:3])

# Get maximum widths for first three widths across the plots
g3max.widths <- do.call(unit.pmax, g3.widths)

# Apply the maximum widths to each plot
for(i in 1:length(plots)) g[[i]]$widths[1:3] = g3max.widths

# Draw it
do.call(grid.arrange, c(g, ncol = 1))


## Part 2: Get the right side of the panels aligned
# Locate the panels
panels <- lapply(g, function(x) x$layout[grepl("panel", x$layout$name), ])

# Get the position of right most panel
r.panel  = lapply(panels, function(x) max(x$r)) # position of right most panel

# Get the number of columns to the right of the panels
n.cols = lapply(g.widths, function(x) length(x)) # right most column

# Get the widths of these columns to the right of the panels
r.widths  <- mapply(function(x,y,z) x[(y+1):z], g.widths, r.panel, n.cols)

# Get the sum of these widths
sum.r.widths <- lapply(r.widths, sum)

# Get the maximum of these widths
r.width = do.call(unit.pmax, sum.r.widths)

# Add a column to the right of each gtable of width 
# equal to the difference between the maximum
# and the width of each gtable's columns to the right of the panel. 
for(i in 1:length(plots)) g[[i]] = gtable_add_cols(g[[i]], r.width - sum.r.widths[[i]], -1)

# Draw it
do.call(grid.arrange, c(g, ncol = 1))

enter image description here

Community
  • 1
  • 1
Sandy Muspratt
  • 31,719
  • 12
  • 116
  • 122
  • 2
    I've always wondered why ggplot doesn't always keep an empty space anyway, regardless of a legend being there or not. BTW legends aren't always on the right side, which may become a nightmare if you're aiming at full generality. – baptiste Mar 07 '16 at 07:30
  • 1
    how about splitting every plot into three independent gtables corresponding to the following groups: left, panels, right, and aligning those three separately? – baptiste Mar 07 '16 at 07:33
  • I have thought about the three regions. I'll give it some further thought, but not for a couple of days. If you want to step in with a solution, feel free. – Sandy Muspratt Mar 07 '16 at 08:00
  • 1
    actually, I've played with this idea a bit and it doesn't appear to make anything easier, quite the contrary in fact. – baptiste Mar 07 '16 at 08:42
  • 1
    I posted a revised solution. It's a little more general. It does not look for legends as was the case with the original solution. Rather is makes sure the widths of the spaces to the right of the panels across the plots are the same. – Sandy Muspratt Mar 08 '16 at 03:52
  • 1
    the 3x3 idea can be made to work (see egg package) but it was a bit fiddly to get the left axes right-justify – baptiste Apr 04 '16 at 11:16
0

Taking off these two lines and keeping the rest, it worked just fine.

g.iris$widths <- max.widths # assign max. widths to iris gtable
g.mpg$widths <- max.widths # assign max widths to mpg gtable

enter image description here

Probably it was limiting the width of them.

Tyler Rinker
  • 108,132
  • 65
  • 322
  • 519
ldepaula3
  • 56
  • 7
  • Thanks for the feedback, but this is just a reproducible case. My actual case is much more complex and removing those lines make the charts not being properly aligned. In fact, the reproducible error does not produce aligned charts either if we remove those lines – Jordi Vidal Jan 14 '16 at 19:16
  • What do you want to achieve? Want to put all the charts in a row? – ldepaula3 Jan 14 '16 at 19:21
  • 1
    My original setup is a 4 row, 1 column set of charts that are aligned (1 chart per row). With ggplot2 1.0.1 this is done with the above code, but with ggplot2 2.0.0 produces the bug shown in the charts you can see in my first post. – Jordi Vidal Jan 14 '16 at 20:58
  • @JordiVidal Your original code should not work with ggplot2 version 1.0.0, and I have just confirmed it. The reason is that the gtables of the two plots have different numbers of columns. It might be that it once worked when selecting the first three columns; then it would have broken at about ggplot v2. The fix was to apply the unit.list function from grid. But [according to this](https://stat.ethz.ch/pipermail/r-devel/2016-March/072434.html), it might be fixed with the next release of R. But the compression of the facets is nothing to do with this. That still happens in ggplot v1. – Sandy Muspratt Mar 08 '16 at 00:20
0

This is ugly but if you're under a time pressure this hack will work (not generalizable and dependent upon plot window size). Basically make the top plot 2 columns with a blank plot on the right and guess at the widths.

grid.arrange(
    grid.arrange(plot.iris, ggplot() + theme_minimal(),ncol=2, widths = c(.9, .1)),
    plot.mpg, 
    ncol=1
)
Tyler Rinker
  • 108,132
  • 65
  • 322
  • 519