Arrange common plot width with facetted ggplot 2.0

2020-02-28 18:20发布

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

4条回答
对你真心纯属浪费
2楼-- · 2020-02-28 18:31

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

查看更多
▲ chillily
3楼-- · 2020-02-28 18:36

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

查看更多
三岁会撩人
4楼-- · 2020-02-28 18:41

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.

查看更多
聊天终结者
5楼-- · 2020-02-28 18:44

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
)
查看更多
登录 后发表回答