Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using align='hv' in plot_grid ignores defined margins #40

Closed
JoGall opened this issue Jul 14, 2016 · 5 comments
Closed

Using align='hv' in plot_grid ignores defined margins #40

JoGall opened this issue Jul 14, 2016 · 5 comments

Comments

@JoGall
Copy link

JoGall commented Jul 14, 2016

I'm having an issue trying to reduce the margins around my plots in plot_grid.

Using align='hv' perfectly aligns and scales my axes but leaves unwanted whitespace between plots, like so:

cow1

Whereas defining margins within each plot reduces the whitespace as desired, but leaves the y axes misaligned due to their different sizes, like so:

cow2

I've included a reproducible example below.

Thanks in advance for any help on this matter -- I appreciate the work you're doing with cowplot.

#my data
mydat <- structure(list(sex = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 1L, 
1L, 1L, 1L, 1L, 1L), .Label = c("F", "M"), class = "factor"), 
    treat = structure(c(3L, 3L, 1L, 1L, 2L, 2L, 3L, 3L, 1L, 1L, 
    2L, 2L), .Label = c("nylon", "stab", "no treat"), class = "factor"), 
    pre_post = structure(c(2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 
    1L, 2L, 1L), .Label = c("pre", "post"), class = "factor"), 
    meanPO = c(0.271111104444444, 2.32355555333333, 0.288222211111111, 
    0.15, 0.254444435555555, 1.86595237619048, 0.164901954901961, 
    0.247333333333333, 0.794901970588235, 0.6429166625, 0.390196078431373, 
    0.339777768888889), meanproPO = c(1.17066665333333, 3.96755554888889, 
    1.68466666666667, 0.750888877777778, 1.11022221777778, 2.90190476428571, 
    0.269411770588235, 0.493777764444444, 0.790392158823529, 
    0.926458333333333, 0.529583329166667, 0.803111106666667)), .Names = c("sex", 
"treat", "pre_post", "meanPO", "meanproPO"), class = "data.frame", row.names = c(13L, 
15L, 17L, 19L, 21L, 23L, 1L, 3L, 5L, 7L, 9L, 11L))

#individual plots

#set common themes
my_theme <- theme(
    legend.position="none",
    axis.line = element_line(colour = "black")
)
#plot 1
gg_TL <- ggplot(subset(mydat, sex=="F"), aes(x=treat, y=meanPO, fill=pre_post)) +
    geom_bar(position="dodge", stat="identity", width=0.75) +
    scale_y_continuous(limits=c(0,my_ylim), expand=c(0,0) ) +
    labs(x="") +
    my_theme + theme(
        axis.text.x = element_blank()
    )
#plot 2
gg_BL <- ggplot(subset(mydat, sex=="F"), aes(x=treat, y=meanproPO, fill=pre_post)) +
    geom_bar(position="dodge", stat="identity", width=0.75) +
    scale_y_continuous(limits=c(0,my_ylim), expand=c(0,0) ) +
    labs(x="") +
    my_theme
#plot 3
gg_TR <- ggplot(subset(mydat, sex=="M"), aes(x=treat, y=meanPO, fill=pre_post)) +
    geom_bar(position="dodge", stat="identity", width=0.75) +
    scale_y_continuous(limits=c(0,my_ylim), expand=c(0,0) ) +
    labs(x="", y="") +
    my_theme + theme(
        axis.text.x = element_blank()
    )
#plot 4
gg_BR <- ggplot(subset(mydat, sex=="M"), aes(x=treat, y=meanproPO, fill=pre_post)) +
    geom_bar(position="dodge", stat="identity", width=0.75) +
    scale_y_continuous(limits=c(0,my_ylim), expand=c(0,0) ) +
    labs(y="", x="") +
    my_theme

# cowplot
# Plot #1: hv align
plot_grid(gg_TL, gg_TR, gg_BL, gg_BR, labels = LETTERS[1:4], label_size = 18, align="hv")

# Plot #2: define margins of each plot, no hv align
gg_TL <- gg_TL + theme(plot.margin=unit(c(0.6,-0.2,-0.5,0.2), "cm")) 
gg_BL <- gg_BL + theme(plot.margin=unit(c(0.6,-0.2,-0.5,0.2), "cm"))
gg_TR <- gg_TR + theme(plot.margin=unit(c(0.4,0.2,-0.5,0.2), "cm"))
gg_BR <- gg_BR + theme(plot.margin=unit(c(0.4,0.2,-0.5,0.2), "cm"))
plot_grid(gg_TL, gg_TR, gg_BL, gg_BR, labels = LETTERS[1:4], label_size = 18)
@clauswilke
Copy link
Contributor

There's an ugly workaround, but it's quite complicated and I don't have the time right now to work it out in detail. In a nutshell, you need to prepare two graphs without the x-axis tick labels and one that only contains the labels, and then stitch them together. You can obtain a graph with only the labels by making the full plot, converting to a gtable, and extracting the relevant row from the table.

My lab uses these kinds of techniques a lot, and at some point I'll hopefully write code that makes it easier, but I don't have it right now.

@ashenkin
Copy link

Just wanted to chime in here that I'd be interested in this as well. It relates to issue #49. I've tried doing it as you suggest above, but my grob knowledge is miniscule, and I was unable to get it working... Thanks for any time you might have to push out relevant code!

@JoGall
Copy link
Author

JoGall commented Jan 12, 2017

I found a quick and dirty solution without grobbing by altering the margins for each individual plot before combining with plot_grid, e.g.:

#reduce margins
margins_left <- c(0.4,-0.2,-0.4,0.2)
margins_right <- c(0.4,0.2,-0.4,0.2)

gg_TL <- gg_TL + theme(plot.margin=unit(margins_left, "cm"))
gg_BL <- gg_BL + theme(plot.margin=unit(margins_left, "cm"))
gg_TR <- gg_TR + theme(plot.margin=unit(margins_right, "cm"))
gg_BR <- gg_BR + theme(plot.margin=unit(margins_right, "cm"))

#plot
cp <- plot_grid(gg_TL, gg_TR, gg_BL, gg_BR, labels = LETTERS[1:4], label_size = 18, align="hv")

I doubt this is the optimal way but it did the trick for me!

@darachm
Copy link

darachm commented Sep 29, 2017

Altering margins and mutagenizing the grob didn't work for me. I'm making some marginal histograms such that they're on the same range. I ended up making each aligned plot, and stacked them on top of each other. This of course requires theme_classic().

Here's in case it's of use:

datar <- data.frame(x=rnorm(1000),y=rnorm(1000,sd=3)
  ,col=as.logical(round(runif(100))))
sameRange <- range(c(datar$x,datar$y))+c(-1,1)

scatter <- ggplot(datar)+aes(x=x,y=y,col=col)+geom_point()+
  theme_classic()+
  theme(legend.position="bottom")+
  coord_cartesian(xlim=sameRange,ylim=sameRange)
hist_x  <- ggplot(datar)+aes(x=x)+geom_histogram()+
  theme_classic()+
  theme(axis.text.x=element_blank()
    ,axis.ticks.x=element_blank()
    ,axis.title.x=element_blank())+
  xlab(NULL)+
  coord_cartesian(xlim=sameRange)
hist_y  <- ggplot(datar)+aes(x=y)+geom_histogram()+
  theme_classic()+
  theme(axis.text.y=element_blank()
    ,axis.ticks.y=element_blank()
    ,axis.title.y=element_blank())+
  coord_cartesian(xlim=sameRange)+
  xlab(NULL)+coord_flip()
scatter <- scatter+theme(plot.margin=unit(c(0,0,1,1),"cm"))
hist_x <- hist_x+theme(plot.margin=unit(c(0,0,0,0),"cm"))
hist_y <- hist_y+theme(plot.margin=unit(c(0,0,0,0),"cm"))
null <- ggplot()+theme_void()

a  <- cowplot::plot_grid(hist_x,null,scatter,null,axis="tblr",align="v"
  ,rel_widths=c(0.8,.2),rel_heights=c(0.2,0.8))
b  <- cowplot::plot_grid(null,null,scatter,hist_y,axis="tblr",align="h"
  ,rel_widths=c(0.8,.2),rel_heights=c(0.2,0.8))

g <- ggdraw(b)+draw_plot(a);g
ggsave("tmp.png",g)

tmp

@clauswilke
Copy link
Contributor

The development version of cowplot contains a function axis_canvas that allows you to do something somewhat similar. You won't get the axes for the marginal histograms, so that may not be what you need. But if that's OK with you, your code becomes much simpler:

library(cowplot)
library(magrittr)

datar <- data.frame(x=rnorm(1000),
                    y=rnorm(1000, sd=3),
                    col=as.logical(round(runif(100))))

scatter <- ggplot(datar, aes(x=x, y=y, col=col)) + geom_point() + 
  theme(legend.position="bottom")

hist_x <- axis_canvas(scatter, axis = 'x') + geom_histogram(data = datar, aes(x=x))
hist_y <- axis_canvas(scatter, axis = 'y', coord_flip = TRUE) + 
  geom_histogram(data = datar, aes(x=y)) + coord_flip()

scatter %>% insert_xaxis_grob(hist_x, grid::unit(.2, "null"), position = "top") %>%
  insert_yaxis_grob(hist_y, grid::unit(.2, "null"), position = "right") %>%
  ggdraw()

screen shot 2017-09-29 at 5 01 28 pm

And the same with density plots, which make more sense if you want to distinguish between the two colors:

dens_x <- axis_canvas(scatter, axis = 'x') + geom_density(data = datar, aes(x=x, fill=col), alpha = .7)
dens_y <- axis_canvas(scatter, axis = 'y', coord_flip = TRUE) + 
  geom_density(data = datar, aes(x=y, fill=col), alpha = .7) + coord_flip()

scatter %>% insert_xaxis_grob(dens_x, grid::unit(.2, "null"), position = "top") %>%
  insert_yaxis_grob(dens_y, grid::unit(.2, "null"), position = "right") %>%
  ggdraw()

screen shot 2017-09-29 at 5 01 11 pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants