Related
I am trying to create a facet_grid() in ggplot() and am having issues with the margins of my plot. I am using grid.draw() for my final plot, and cannot figure out how to adjust the margins for printing. When I save my plot, it appears fine (see below). However, when I actually print my plot out to hard copy, half of the X&Y labels and my plot title are cut off.
I've attempted using par() to no avail. Here is a reproducible example, similar to my actual plot. I need to keep the panel <- off part because in my actual plot, I have plotted numbers above each bar and they get cut off by the facet sides for days at the beginning/end of each month. I'm thinking this might be the root of the issue, but I'm not really sure to be honest.
data(airquality)
library(stats)
library(ggplot2)
library(gtable)
library(gridExtra)
library(grid)
library(dplyr)
library(scales)
facet <- ggplot() +
geom_bar(data=airquality,aes(y=Wind,x=Day,fill=Temp),colour="black",stat="identity",position='stack') +
#theme_bw() +
facet_grid(~Month) +
theme(axis.title.x=element_text(face="bold",size=14),axis.title.y=element_text(face="bold",size=14),axis.text.x=element_text(face="bold",size=10),axis.text.y=element_text(face="bold",size=10))+
ylab("Wind") +
theme(panel.margin = unit(5, "mm"),panel.border=element_rect(color="black",fill=NA),panel.background = element_rect(fill="grey84"),plot.title = element_text (size=20,face="bold"),legend.position="right",panel.grid.minor=element_blank(),strip.text.x=element_text(size=12,face="bold"),strip.background=element_rect(fill=NA,colour="black"),legend.title=element_text(size=14,face="bold")) +
ggtitle("Test")
gt <- ggplot_gtable(ggplot_build(facet))
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)
Thanks for any and all help! Please, let me know if you need any clarification or have any questions.
you have two options:
set some margins in the ggplot theme
assign a viewport of specific size to the plot/gtable, smaller than the device window by some margin. Here's an illustration of using both strategies at once
library(grid)
fig_size <- c(6, 4) # inches
margin <- unit(4, "line")
p <- ggplot() + theme(plot.background=element_rect(colour="red", size=2, fill="grey50"),
plot.margin = unit(1:4, "line"))
g <- ggplotGrob(p)
g$vp <- viewport(width = unit(fig_size[1], "in") - margin, height=unit(fig_size[2],"in")- margin)
ggsave("plot.pdf", g, width=fig_size[1], height=fig_size[2])
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)
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:
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))
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))
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))
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
Probably it was limiting the width of them.
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
)
I'm trying to arrange plots for publication with the use of cowplot package.
I just want the panels to be equally sized and labelled.
Reproducible example
library(ggplot2)
library(cowplot)
gg1 <- ggplot(mtcars)+
geom_point(aes(x=mpg,y=hp))+
theme_bw()+
theme(aspect.ratio=1)
gg2 <- ggplot(mtcars)+
geom_point(aes(x=mpg,y=hp,fill=cyl))+
facet_wrap(~cyl,ncol=2)+
theme_bw()+
theme(aspect.ratio=1,
legend.position='none')
output <- plot_grid(gg1,gg2, labels = c('A','B'),label_size = 20)
print(output)
The code produces this plot.
As you may see, neither the horizontal axises match nor do the upper edges of the panels.
The argument align from cowplot does not work with faceted plots.
Any ideas?
Since this is one of the highest voted question regarding cowplot and complex alignments, I wanted to point out that cowplot now does have some functionality for aligning faceted plots. (I'm the package author.) However, they don't work in this particular case!
For example, this works (using the axis option in plot_grid()):
gg1 <- ggplot(mtcars) +
geom_point(aes(x=mpg, y=hp)) +
theme_bw()
gg2 <- ggplot(mtcars)+
geom_point(aes(x=mpg, y=hp, fill=cyl)) +
facet_wrap(~cyl, ncol=2) +
theme_bw() +
theme(legend.position='none')
plot_grid(gg1, gg2, labels = c('A','B'), label_size = 20,
align = 'h', axis = 'tb')
We can also do this the following, to get a different type of alignment (depending on whether you want the facet strip to be counted as part of the plot or not):
plot_grid(gg1, gg2, labels = c('A', 'B'), label_size = 20,
align = 'h', axis = 'b')
Now why did I say it doesn't work for this case? Because, if you look at the original code in the question, you'll see that there was a theme(aspect.ratio=1) setting that I removed. cowplot can align plots as long as you don't force a specific aspect ratio, because the method it uses to align plots typically modifies the aspect ratio of the individual plots.
Here's a hack until someone comes up with a more elegant answer: You can use grid.arrange from the gridExtra package to change the relative sizes of the two plots so that the axes line up. The w parameter in the code below is what controls that by giving the left-hand plot a bit more of the horizontal width, thereby making it relatively larger, when compared with the right-hand plot.
library(gridExtra)
w = 0.512
grid.arrange(gg1, gg2, widths=c(w,1-w), ncol=2)
You can also use arrangeGrob and textGrob to add the "A" and "B" titles to each plot.
w = 0.512
grid.arrange(arrangeGrob(textGrob("A", x=0.13, gp=gpar(fontface="bold", cex=1.4)),
gg1, heights=c(0.03,0.97)),
arrangeGrob(textGrob("B", x=0.13, gp=gpar(fontface="bold", cex=1.4)),
gg2, heights=c(0.03,0.97)),
widths=c(w,1-w), ncol=2)
In either case, you need to adjust w by hand to get the plots to line up (which is what makes this method, shall we say, sub-optimal). The appropriate value for w will change depending on the physical size of the plot. w=0.512 seemed to work well when I saved the plot below as a png of 1000 x 500 pixels.
A better answer will probably involve something analogous to this SO answer, but adapted for lining up facetted and non-facetted plots (or, more generally, plots that don't have a one-to-one correspondence between their constituent grobs).
here's a solution based on this idea
library(ggplot2)
library(grid)
library(gridExtra)
library(gtable)
gtable_frame <- function(g, width=unit(1,"null"), height=unit(1,"null")){
panels <- g[["layout"]][grepl("panel", g[["layout"]][["name"]]), ]
ll <- unique(panels$l)
tt <- unique(panels$t)
fixed_ar <- g$respect
if(fixed_ar) { # there lies madness, want to align despite aspect ratio constraints
ar <- as.numeric(g$heights[tt[1]]) / as.numeric(g$widths[ll[1]])
height <- width * ar
g$respect <- FALSE
}
core <- g[seq(min(tt), max(tt)), seq(min(ll), max(ll))]
top <- g[seq(1, min(tt)-1), ]
bottom <- g[seq(max(tt)+1, nrow(g)), ]
left <- g[, seq(1, min(ll)-1)]
right <- g[, seq(max(ll)+1, ncol(g))]
fg <- nullGrob()
lg <- if(length(left)) g[seq(min(tt), max(tt)), seq(1, min(ll)-1)] else fg
rg <- if(length(right)) g[seq(min(tt), max(tt)), seq(max(ll)+1,ncol(g))] else fg
grobs = list(fg, g[seq(1, min(tt)-1), seq(min(ll), max(ll))], fg,
lg, g[seq(min(tt), max(tt)), seq(min(ll), max(ll))], rg,
fg, g[seq(max(tt)+1, nrow(g)), seq(min(ll), max(ll))], fg)
widths <- unit.c(sum(left$widths), width, sum(right$widths))
heights <- unit.c(sum(top$heights), height, sum(bottom$heights))
all <- gtable_matrix("all", grobs = matrix(grobs, ncol=3, nrow=3, byrow = TRUE),
widths = widths, heights = heights)
all[["layout"]][5,"name"] <- "panel" # make sure knows where the panel is for nested calls
if(fixed_ar) all$respect <- TRUE
all
}
p1 <- ggplot(mtcars)+
geom_point(aes(x=mpg,y=hp))+
theme_bw()+
theme(aspect.ratio=1)
p2 <- ggplot(mtcars)+
geom_point(aes(x=mpg,y=hp,fill=cyl))+
facet_wrap(~cyl,ncol=2)+
theme_bw()+
theme(aspect.ratio=1,
legend.position='none')
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
fg1 <- gtable_frame(g1)
fg2 <- gtable_frame(g2)
grid.newpage()
grid.draw(cbind(fg1, fg2))
Note that the gtable_frame function wraps plots based on their panels, but excluding the panel strips by design (I find it more pleasant).
Update: egg package is on CRAN now
https://cran.r-project.org/web/packages/egg/index.html
I just want to add that #baptiste has created a great experimental package egg, which accomplishes what he wrote in his answer:
Install it from github (https://github.com/baptiste/egg)
library("devtools")
install_github("baptiste/egg")
Then simply do
library("egg")
ggarrange(gg1, gg2, ncol=2)
You can add labels manually:
ap <- ggarrange(gg1,gg2, ncol=2)
ggdraw(ap) + draw_plot_label(label=c("a","b"), x=c(0,0.5), y=c(1,1))
(When I tried to first add the labels to the individual plots, the plots didn't get arranged properly.)
I have a simpler solution sticking with plot_grid and the original example. However some may feel it is a bit of a cheat.
One can fine-tune aligning plots with cowplot:plot_grid by adding nested NULL plots and adjusting their height/width ratios. This is applied below:
gg3<-plot_grid(NULL,gg2, NULL, align = 'h', nrow = 3, rel_heights = c(0.06,1,0.06))
plot_grid(gg1,gg3, labels = c('A','B'),label_size = 20)
I would like two separate plots. I am using them in different frames of a beamer presentation and I will add one line to the other (eventually, not in example below). Thus I do not want the presentation to "skip" ("jump" ?) from one slide to the next slide. I would like it to look like the line is being added naturally. The below code I believe shows the problem. It is subtle, but not how the plot area of the second plot is slightly larger than of the first plot. This happens because of the y axis label.
library(ggplot2)
dfr1 <- data.frame(
time = 1:10,
value = runif(10)
)
dfr2 <- data.frame(
time = 1:10,
value = runif(10, 1000, 1001)
)
p1 <- ggplot(dfr1, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2))
print(p1)
dev.new()
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(".")
print(p2)
I would prefer to not have a hackish solution such as setting the size of the axis label manually or adding spaces on the x-axis (see one reference below), because I will use this technique in several settings and the labels can change at any time (I like reproducibility so want a flexible solution).
I'm searched a lot and have found the following:
Specifying ggplot2 panel width
How can I make consistent-width plots in ggplot (with legends)?
https://groups.google.com/forum/#!topic/ggplot2/2MNoYtX8EEY
How can I add variable size y-axis labels in R with ggplot2 without changing the plot width?
They do not work for me, mainly because I need separate plots, so it is not a matter of aligning them virtically on one combined plot as in some of the above solutions.
haven't tried, but this might work,
gl <- lapply(list(p1,p2), ggplotGrob)
library(grid)
widths <- do.call(unit.pmax, lapply(gl, "[[", "widths"))
heights <- do.call(unit.pmax, lapply(gl, "[[", "heights"))
lg <- lapply(gl, function(g) {g$widths <- widths; g$heights <- heights; g})
grid.newpage()
grid.draw(lg[[1]])
grid.newpage()
grid.draw(lg[[2]])
How about using this for p2:
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() +
scale_y_continuous(breaks = NULL) +
scale_x_continuous(breaks = NULL) +
ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2)) +
theme(axis.title.y=element_text(color=NA))
This has the same label as p1, but the color is NA so it doesn't display. You could also use color="white".
I am struggling to plot two or more plots in just one page. One of the plot must be a heat map plot (as it is called in the cookbook ggplot2 book) and the x-axis of all plots have to be perfectly aligned. I after digging up (mostly here), I have ended up with the following script. They are vertically aligned, but I can not change their heights. It follows an example:
library(ggplot2)
library(grid)
dataheat=data.frame(day=rep(1:100,each=51),depth=rep(seq(0,-50,by=-1),100),z=runif(100*51))
data = data.frame(day=1:100,y=-15000*runif(200),sys=rep(c("hsoil","hleaf"),each=100))
pdf("rwu_r.pdf")
p1 = ggplot(data,aes(x=day,y=y,linetype=sys)) + geom_line() + theme_bw()+
theme(legend.position=c(0.85,0.8),legend.background=element_blank(),legend.key=element_blank(),axis.title.x=element_text(size=14)) +
theme(plot.margin=unit(c(1,4,0,0.5),"lines"))+
scale_linetype_discrete(name="",labels=c(hsoil="soil",hleaf="leaf")) +
xlab("Day") + ylab("Pressure head, cm") + scale_x_continuous(expand=c(0,0),limits=c(0,100))
p2 = ggplot(dataheat, aes(x=day,y=depth,fill=z)) +
geom_raster() + xlab("Days") + ylab("Depth, cm") +
scale_x_continuous(expand=c(0,0),limits=c(0,100)) +
scale_y_continuous(expand=c(0,0))# +
# theme(plot.margin=unit(c(0, 0, 0.5, 0.5),"lines"))
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
g1 <- gtable::gtable_add_cols(g1, widths=unit(5,"mm"))
g <- gtable:::rbind_gtable(g1, g2, "first")
grid.draw(g)
dev.off()
Now I want that the height of the top plot is 10% the total height, and the second one is 85% the total HEIGHT. If I change the "theme(plot.margin=unit(c(1,4,0,0.5),"lines"))" of the first plot, it will also change the second one.
Also I also would like to add a third plot aligned with the other ones and take control of its height.
Thanks in advance.