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])
Related
I hope you can help me. I have the idea of visualizing segments within a plot with a rectangle that can be placed next to the y or x-axis which means that it would be outside of the plot area. It should look similar as in the image below:
I tried to reach the mentioned output by trying two different approaches:
I created two viewports with the grid package and put the plot in one viewport that I placed at the bottom and one viewport on top of that. The big problem here is that I need the coordinates from where the grey background panel of the ggplot starts so I can place the top viewport exactly there, so that the segments conincide with the x-axis length. My code looked like following:
container_viewport <- viewport(x=0,y=0,height=1,width=1,just = c("left","bottom"))
pushViewport(container_viewport)
grid.draw(rectGrob())
popViewport()
section_viewport <- viewport(x=0.055,y=0.99,height=0.085,width=0.935,just=c("left","top"))
pushViewport(section_viewport)
plot_obj <- ggplot_build(testplot)
plot_data <- plot_obj$data[[1]]
grid.draw(rectGrob(gp = gpar(col = "red")))
popViewport()
plot_viewport <- viewport(x=0,y=0,height=0.9,width=1,just=c("left","bottom"))
pushViewport(plot_viewport)
grid.draw(ggplotGrob(testplot))
popViewport()
This looks fine but I had to hardcode the coordinates of the viewport at the top.
I used grid.arrange() to arrange to stack the plots vertically (instead of a grob for the rectangle like in the other approach I create a ggplot instead for that). Here, basically the same problem exists, since I somehow need to put the plot representing the rectangle at the top in the right position on the x-axis. My code looked like following:
p1 <- plot_data %>%
ggplot()+
geom_rect(aes(xmin=-Inf,xmax=Inf,ymin=-Inf,ymax=Inf))
p2 <- testplot
test_plot <- grid.arrange(p1,p2,heights=c(1,10))
This approach does not work that good.
Since I would like to create a solution that can be applied generally, trial and error with the coordinates of the viewport is no option since the length of the y-axis label or tick labels can vary and therefore the length and coordinates of the background panel. When this step is done the segmentation of the rectangle should be no problem anymore.
Maybe this is just not possible but if then I would appreciate any help.
Thank you!
I would probably use patchwork here. Let's start by replicating your plot:
library(ggplot2)
library(patchwork)
p <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
geom_point(color = "red") +
labs(x = "test", y = "test")
p
That looks very similar. Now we define (in our own co-ordinates) where we want the section split to occur on the x axis.
section_split <- 5.25
Using just this number, we add rectangles and text annotations that cover a copy of our original plot, and remove its axis annotations using theme_void:
p2 <- p +
annotate("rect", xmin = c(-Inf, section_split), ymin = c(-Inf, -Inf),
xmax = c(section_split, Inf), ymax = c(Inf, Inf),
fill = c("#00a2e8", "#ff7f27")) +
annotate("text", label = c("Section A", "Section B"), size = 6,
y = rep(mean(layer_scales(p)$y$range$range), 2),
x = c((min(layer_scales(p)$x$range$range) + section_split)/2,
(max(layer_scales(p)$x$range$range) + section_split)/2)) +
theme_void()
Now we just draw this second plot above our first, adjusting the relative heights to about 1:10
p2/p + plot_layout(heights = c(1, 10))
The benefit of doing it this way is that, since we copied the original plot, the positional mapping of the x axis is identical between the two plots, and patchwork will automatically line up the panels.
Created on 2023-02-04 with reprex v2.0.2
To display multiple plots I use multiplot (http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/), now I have two plots who share the same x-axis range and are plotted above each other:
multiplot(plot1, plot2)
I removed the x-axis labels and title using:
xlab(NULL) + theme(axis.text.x=element_blank(),axis.ticks.x=element_blank())
But there is still a white margin between the two plots. How can I make this margin smaller or remove it?
To reduce space between the plots, remove the bottom margin of the top plot and remove the top margin of the bottom plot. The code below sets these margins to 0, which still results in a tiny bit of white space between the plots. You can make these margins slightly negative (maybe -0.1 or so) to completely remove the white space. Rather than the multiplot function, we use grid.arrange from the gridExtra package to lay out the plots. :
library(grid)
library(gridExtra)
## Create two sample plots with the same x axis using built-in mtcars data frame
# Top plot: Remove bottom margin, x-labels, and x title
p1 = ggplot(mtcars, aes(wt, mpg)) + geom_point() +
xlab(NULL) +
theme(axis.text.x=element_blank(),axis.ticks.x=element_blank(),
plot.margin=unit(c(1,1,0,1), "lines"))
# Bottom plot: Remove top margin
p2 = ggplot(mtcars, aes(wt, carb)) + geom_point() +
theme(plot.margin=unit(c(0,1,1,1), "lines"))
# Lay out plots in one column
grid.arrange(p1, p2, ncol=1)
Two problems with the above layout: (1) the y axes are not justified properly, and (2) the height of the lower plot's plot area is less than that of upper plot's plot area. The code below addresses these issues:
# Left justify plots
# Source: http://stackoverflow.com/a/13295880/496488
gA <- ggplotGrob(p1)
gB <- ggplotGrob(p2)
maxWidth = grid::unit.pmax(gA$widths[2:5], gB$widths[2:5])
gA$widths[2:5] <- as.list(maxWidth)
gB$widths[2:5] <- as.list(maxWidth)
# Lay out justified plots. Use heights argument to equalize heights of each plot area
grid.arrange(gA, gB, heights=c(0.47,0.53), ncol=1)
You can exactly equalize the heights of each plot area using the same trick as we used to left-justify the plots (rather than doing it by eye using the heights argument to grid.arrange), but then the plot margins get added back. I'm not sure of how to deal with that, but here's the code for reference:
maxHeight = grid::unit.pmax(gA$heights[2:5], gB$heights[2:5])
gA$heights[2:5] <- as.list(maxHeight)
gB$heights[2:5] <- as.list(maxHeight)
The last update of ggplot2 gives much more control over the plot. See for example:
ggplot(mtcars, aes(disp, mpg)) +
geom_point() +
facet_wrap(~vs)
You can further adjust the labels, number of rows, and how scales will be displayed, for instance: nrow = 2; scales = "free".
It is easier than you might think to re-arrange the data so that you can take advantage of the nice alignment features that already exist in ggplot2. See below for an example replicating eipi10's answer, but without having to use ggplotGrob.
What you have to do is just select the columns you want to plot, along with the ID columns (in this case, the car model and the x-axis value column). Then melt, and it's ready to plot using the standard facet procedure.
Note that the "switch" option in the facet_grid call is a new feature that you can access by updating to the most recent CRAN version. I used that to replace the regular y-axis title, which was omitted using theme.
The nice part about this approach is that the plots will always be perfectly aligned.
library("ggplot2")
library("dplyr")
library("reshape2")
df <- mtcars %>%
add_rownames(var = "Model") %>%
select(Model, wt, mpg, carb) %>%
melt(id.vars = c("Model","wt"))
ggplot(df)+
aes(x=wt, y=value)+
geom_point()+
theme(strip.background=element_blank())+
facet_grid(variable ~ ., scales="free_y", switch="y")
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)
When I compile the following MWE I observe that the maximum point (3,5) is significantly cut/cropped by the margins.
The following example is drastically reduced for simplicity.
In my actual data the following are all impacted by limiting my coord_cartesian manually if the coresponding x-axis aesthetic is on the max x value.
Point symbol
Error bars
Statistical symbols inserted by text annotation
MWE
library(ggplot2)
library("grid")
print("Program started")
n = c(0.1,2, 3, 5)
s = c(0,1, 2, 3)
df = data.frame(n, s)
gg <- ggplot(df, aes(x=s, y=n))
gg <- gg + geom_point(position=position_dodge(width=NULL), size = 1.5)
gg <- gg + geom_line(position=position_dodge(width=NULL))
gg <- gg + coord_cartesian( ylim = c(0, 5), xlim = c((-0.05)*3, 3));
print(gg)
print("Program complete - a graph should be visible.")
To show my data appropriately I would consider using any of the following that are possible (influenced by the observation that the x-axis labels themselves are never cut):
Make the margin transparent so the point isn't cut
unless the point is cut by the plot area and not the margin
Bring the panel with the plot area to the front
unless the point is cut by the plot area and not the margin so order is independent
Use xlim = c((-0.05)*3, (3*0.05)) to extend the axis range but implement some hack to not show the overhanging axis bar after the maximum point of 3?
this is how I had it originally but I was told to remove the overhang after the 3 as it was unacceptable.
Is this what you mean by option 1:
gg <- ggplot(df, aes(x=s, y=n)) +
geom_point(position=position_dodge(width=NULL), size = 3) +
geom_line(position=position_dodge(width=NULL)) +
coord_cartesian(xlim=c(0,3), ylim=c(0,5))
# Turn of clipping, so that point at (3,5) is not clipped by the panel grob
gg1 <- ggplot_gtable(ggplot_build(gg))
gg1$layout$clip[gg1$layout$name=="panel"] <- "off"
grid.draw(gg1)
I use ggplot2 and knitr to publish scatterplots with a right-hand-side legend. The legend is included in the aspect ratio and therefore breaks the "squareness" of the plots, as shown in the default themes. When the legend text becomes a bit longer than "a" and "b", the graphs become "long rectangles" instead of "squares".
I would like to keep the graphs "squared", and so would like to exclude the legend from the aspect ratio on my ggplot2 graphs. My .Rprofile has the following information to force ggplot2 to produce low-colour graphs with bigger text and more space around axis titles:
theme_set(theme_bw(16))
theme_update(
axis.title.y = element_text(angle = 90, vjust = -.25),
axis.title.x = element_text(vjust = -1),
plot.margin = unit(c(1,1,1,1), "cm")
)
Is there anything I can add here to exclude the legend from the aspect ratio? Manipulations with coord_equal and coord_fixed have failed so far, as have the fig.width and fig.height options. Thanks for your help!
Edit: working example removed, question answered below with full example code (sorry for the delay in approving the answer).
Setting coord_fixed() should do the trick:
library(ggplot2)
library(gridExtra) ## for grid.arrange()
## Create some data with one longer label
cars <- transform(mtcars,
cyl = factor(cyl, labels=c("4","6","8 is big")))
## Compute ratio needed to make the figure square
## (For a taller narrow plot, multiply ratio by number > 1;
## for a squatter plot, multiply it by number in the interval (0,1))
ratio <- with(cars, diff(range(mpg))/diff(range(wt)))
## Make plots with and without a legend
a <- ggplot(cars, aes(mpg, wt)) +
geom_point() + coord_fixed(ratio=ratio)
b <- ggplot(cars, aes(mpg, wt, color=cyl)) +
geom_point() + coord_fixed(ratio=ratio)
## Plot them to confirm that coord_fixed does fix the aspect ratio
grid.arrange(a,b, ncol=2)