Set legend width to be 100% plot width - r

How do I set the legend height or width to be 100% of the plot height/width regardless of the actual dimensions?
library(ggplot2)
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point()+
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.key.width=unit(0.1,"npc"))
Created on 2022-02-11 by the reprex package (v2.0.1)
Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.1.0 (2021-05-18)
#> os Ubuntu 20.04.3 LTS
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> ggplot2 * 3.3.5 2021-06-25 [1] CRAN (R 4.1.0)
#>
#> ──────────────────────────────────────────────────────────────────────────────

The only way I know of is to manually adjust the grid objects in the gtable of the plot. AFAIK, the guides are mostly defined in cm (rather than relative units), so getting them adapted to the panels is a bit of a pain. I'd also love to know a better way to do this.
library(ggplot2)
g <- ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point()+
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.key.width=unit(0.1,"npc"),
legend.margin = margin(), # pre-emptively set zero margins
legend.spacing.x = unit(0, "cm"))
gt <- ggplotGrob(g)
# Extract legend
is_legend <- which(gt$layout$name == "guide-box")
legend <- gt$grobs[is_legend][[1]]
legend <- legend$grobs[legend$layout$name == "guides"][[1]]
# Set widths in guide gtable
width <- as.numeric(legend$widths[4]) # save bar width (assumes 'cm' unit)
legend$widths[4] <- unit(1, "null") # replace bar width
# Set width/x of bar/labels/ticks. Assumes everything is 'cm' unit.
legend$grobs[[2]]$width <- unit(1, "npc")
legend$grobs[[3]]$children[[1]]$x <- unit(
as.numeric(legend$grobs[[3]]$children[[1]]$x) / width, "npc"
)
legend$grobs[[5]]$x0 <- unit(as.numeric(legend$grobs[[5]]$x0) / width, "npc")
legend$grobs[[5]]$x1 <- unit(as.numeric(legend$grobs[[5]]$x1) / width, "npc")
# Replace legend
gt$grobs[[is_legend]] <- legend
# Draw new plot
grid::grid.newpage()
grid::grid.draw(gt)
Created on 2022-02-11 by the reprex package (v2.0.1)

Please forgive me for double answers, but I believe this to be a totally different approach, and the credits for the idea go to #benson23.
We can use ggh4x::force_panelsizes() to set an absolute size for the panel and match the width of the bar. Upside is that it is reasonably easy to do, downside is that your plot's width doesn't automagically adapts to the window size anymore.
library(ggplot2)
library(ggh4x)
width <- unit(10, "cm")
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point() +
guides(colour = guide_colorbar(barwidth = width)) +
force_panelsizes(cols = width) +
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.spacing.x = unit(0, "cm"))
The process becomes slightly more complicated if a plot has multiple panels, but it is not undoable.
ncol <- 3
total_width <- unit(10, "cm")
# Optionally: replace `theme_get()` with actual theme you're using
spacing <- calc_element("panel.spacing.x", theme_get())
panel_widths <- (total_width - spacing * (ncol - 1)) / ncol
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point() +
guides(colour = guide_colorbar(barwidth = total_width)) +
facet_wrap(~ Species, ncol = ncol) +
force_panelsizes(cols = panel_widths) +
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.spacing.x = unit(0, "cm"))
Created on 2022-02-11 by the reprex package (v2.0.1)
Disclaimer: I'm the author of {ggh4x}

Here I'll use a very manual way to set the legend width, which is guides(colour = guide_colorbar(barwidth = 25)). Just try out the barwidth parameter, you'll get one that fits the plot.
I'd also love to know a way to easily get the width of the plot, so that we don't need to manually set the barwidth.
UPDATE: As you can see in the comment under this answer, both #teunbrand and I agree that we can use a barwidth value close to the value of fig.width in Rmarkdown or width in ggsave if you are using these utilities to display your graph
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point()+
guides(colour = guide_colorbar(barwidth = 25)) +
theme(
legend.title=element_blank(),
legend.position="bottom")

Adapted #teunbrand's ggplotGrob answer to work as a function. Added ggplotify to convert gtable back to ggplot. Note that it only works specifically for horizontal colorbars.
Original plot:
library(ggplot2)
library(ggplotify)
g <- ggplot(iris, aes(Petal.Width, Sepal.Width, color=Sepal.Width))+
geom_point()+
theme(legend.position="bottom")
g
Plot with full width colorbar legend:
#' #param x A ggplot object with colorbar legend along the width.
#'
full_width_legend <- function(x){
gt <- ggplotGrob(x)
# Extract legend
is_legend <- which(gt$layout$name == "guide-box")
legend <- gt$grobs[is_legend][[1]]
legend <- legend$grobs[legend$layout$name == "guides"][[1]]
# Set widths in guide gtable
width <- as.numeric(legend$widths[4]) # save bar width (assumes 'cm' unit)
legend$widths[4] <- unit(1, "null") # replace bar width
# Set width/x of bar/labels/ticks. Assumes everything is 'cm' unit.
legend$grobs[[2]]$width <- unit(1, "npc")
legend$grobs[[3]]$children[[1]]$x <- unit(
as.numeric(legend$grobs[[3]]$children[[1]]$x) / width, "npc"
)
legend$grobs[[5]]$x0 <- unit(as.numeric(legend$grobs[[5]]$x0) / width, "npc")
legend$grobs[[5]]$x1 <- unit(as.numeric(legend$grobs[[5]]$x1) / width, "npc")
# Replace legend
gt$grobs[[is_legend]] <- legend
return(ggplotify::as.ggplot(gt))
}
g1 <- full_width_legend(g)
g1
Created on 2022-02-11 by the reprex package (v2.0.1)

Related

Is it possible to specify the size / layout of a single plot to match a certain grid in R?

I use R for most of my data analysis. Until now I used to export the results as a CSV and visualized them using Macs Numbers.
The reason: The Graphs are embeded in documents and there is a rather large border on the right side reserved for annotations (tufte handout style). Between the acutal text and the annotations column there is white space. The plot of the graphs needs to fit the width of text while the legend should be placed in the annotation column.
I would prefer to also create the plots within R for a better workflow and higher efficiency. Is it possible to create such a layout using plotting with R?
Here is an example of what I would like to achieve:
And here is some R Code as a starter:
library(tidyverse)
data <- midwest %>%
head(5) %>%
select(2,23:25) %>%
pivot_longer(cols=2:4,names_to="Variable", values_to="Percent") %>%
mutate(Variable=factor(Variable, levels=c("percbelowpoverty","percchildbelowpovert","percadultpoverty"),ordered=TRUE))
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(position=position_dodge(width=0.85),width=0.8) +
labs(x="County") +
theme(text=element_text(size=9),
panel.background = element_rect(fill="white"),
panel.grid = element_line(color = "black",linetype="solid",size= 0.3),
panel.grid.minor = element_blank(),
panel.grid.major.x=element_blank(),
axis.line.x=element_line(color="black"),
axis.ticks= element_blank(),
legend.position = "right",
legend.title = element_blank(),
legend.box.spacing = unit(1.5,"cm") ) +
scale_y_continuous(breaks= seq(from=0, to=50,by=5),
limits=c(0,51),
expand=c(0,0)) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
I know how to set a custom font, just left it out for easier saving.
Using ggsave
ggsave("Graph_with_R.jpeg",plot=last_plot(),device="jpeg",dpi=300, width=18, height=9, units="cm")
I get this:
This might resample the result aimed for in the actual case, but the layout and sizes do not fit exact. Also recognize the different text sizes between axis titles, legend and tick marks on y-axes. In addition I assume the legend width depends on the actual labels and is not fixed.
Update
Following the suggestion of tjebo I posted a follow-up question.
Can it be done? Yes. Is it convenient? No.
If you're working in ggplot2 you can translate the plot to a gtable, a sort of intermediate between the plot specifications and the actual drawing. This gtable, you can then manipulate, but is messy to work with.
First, we need to figure out where the relevant bits of our plot are in the gtable.
library(ggplot2)
library(gtable)
library(grid)
plt <- ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar(position = position_dodge2(preserve = "single"))
# Making gtable
gt <- ggplotGrob(plt)
gtable_show_layout(gt)
Then, we can make a new gtable with prespecified dimensions and place the bits of our old gtable into it.
# Making a new gtable
new <- gtable(widths = unit(c(12.5, 1.5, 4), "cm"),
heights = unit(9, "cm"))
# Adding main panel and axes in first cell
new <- gtable_add_grob(
new,
gt[7:9, 3:5], # If you see the layout above as a matrix, the main bits are in these rows/cols
t = 1, l = 1
)
# Finding the legend
legend <- gt$grobs[gt$layout$name == "guide-box"][[1]]
legend <- legend$grobs[legend$layout$name == "guides"][[1]]
# Adding legend in third cell
new <- gtable_add_grob(
new, legend, t = 1, l = 3
)
# Saving as raster
ragg::agg_png("test.png", width = 18, height = 9, units = "cm", res = 300)
grid.newpage(); grid.draw(new)
dev.off()
#> png
#> 2
Created on 2021-04-02 by the reprex package (v1.0.0)
The created figure should match the dimensions you're looking for.
Another option is to draw the three components as separate plots and stitch them together in the desired ratio.
The below comes quite close to the desired ratio, but not exactly. I guess you'd need to fiddle around with the values given the exact saving dimensions. In the example I used figure dimensions of 7x3.5 inches (which is similar to 18x9cm), and have added the black borders just to demonstrate the component limits.
library(tidyverse)
library(patchwork)
data <- midwest %>%
head(5) %>%
select(2,23:25) %>%
pivot_longer(cols=2:4,names_to="Variable", values_to="Percent") %>%
mutate(Variable=factor(Variable, levels=c("percbelowpoverty","percchildbelowpovert","percadultpoverty"),ordered=TRUE))
p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col() +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
p_legend <- cowplot::get_legend(p1)
p_main <- p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(show.legend = FALSE) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
p_main + plot_spacer() + p_legend +
plot_layout(widths = c(12.5, 1.5, 4)) &
theme(plot.margin = margin(),
plot.background = element_rect(colour = "black"))
Created on 2021-04-02 by the reprex package (v1.0.0)
update
My solution is only semi-satisfactory as pointed out by the OP. The problem is that one cannot (to my knowledge) define the position of the grob in the third panel.
Other ideas for workarounds:
One could determine the space needed for text (but this seems not so easy) and then to size the device accordingly
Create a fake legend - however, this requires the tiles / text to be aligned to the left with no margin, and this can very quickly become very hacky.
In short, I think teunbrand's solution is probably the most straight forward one.
Update 2
The problem with the left alignment should be fixed with Stefan's suggestion in this thread

Control padding of grobs added to patchwork

This is a follow up problem to this question. The OP asked for a way to arrange parts of a plot in specific distances. I think teunbrand gave a very good answer.
My own suggestion (extract the legend with cowplot, and stitch them to a plot in desired proportions) is not fully satisfactory, because it worked only "by chance" in the given example - the legend labels were long enough to center the legend grob into the viewport for the third plot.
Having shorter labels reveals the problem - when adding a grob, patchwork centres this grob, basically padding equally to all sides.
My question is, do you know of a way to control this padding behaviour?
Cowplot (or any other ggplot combining package for that sake) also very welcome.
library(tidyverse)
library(patchwork)
data <- midwest %>%
head(5) %>%
select(2,23:25) %>%
pivot_longer(cols=2:4,names_to="Variable", values_to="Percent") %>%
mutate(Variable=factor(Variable,
levels=c("percbelowpoverty","percchildbelowpovert","percadultpoverty"),
labels = paste0("perc", 1:3)))
p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col() +
scale_fill_manual(values = c("#CF232B","#942192","#000000")) +
theme(legend.background = element_rect(fill = "grey50"))
p_legend <- cowplot::get_legend(p1)
p_main <- p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(show.legend = FALSE) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
p_main + plot_spacer() + p_legend +
plot_layout(widths = c(12.5, 1.5, 4)) &
theme(plot.margin = margin(),
plot.background = element_rect(colour = "black"))
Not so desired result - the legend grob (with grey background) should be aligned to the left plot border (black line)
Created on 2021-04-09 by the reprex package (v1.0.0)
As far as I get it the issue is not on patchworks side. Having a look at the layout of the legend's gtable we see that it is made up of 5 rows and 5 columns and that the legend is to be placed in the cell in the center:
p_legend <- cowplot::get_legend(p1)
p_legend
#> TableGrob (5 x 5) "guide-box": 2 grobs
#> z cells name
#> 99_a788e923bf245af3853cee162f5f8bc9 1 (3-3,3-3) guides
#> 0 (2-4,2-4) legend.box.background
#> grob
#> 99_a788e923bf245af3853cee162f5f8bc9 gtable[layout]
#> zeroGrob[NULL]
gtable::gtable_show_layout(p_legend)
Hence, when adding the legend patchwork centers is as demanded by the gtable layout.
One option to control the positioning or the padding of the legend would be to squash the first column via cowplot::gtable_squash_cols and if desired add some padding by adding a new column with the desired amount of padding via gtable::gtable_add_cols:
# Squash first column
p_legend <- cowplot::gtable_squash_cols(p_legend, 1)
# Add some padding by adding a new col
p_legend <- gtable::gtable_add_cols(p_legend, unit(.1, "cm"), pos = 1)
p_main <- p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(show.legend = FALSE) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
p_main + plot_spacer() + p_legend +
plot_layout(widths = c(12.5, 1.5, 4)) &
theme(plot.margin = margin(),
plot.background = element_rect(colour = "black"))

Center legend in ggplot2 relative to image

I would like to leave my subtitle centered without having to manually change the position by legend.position in theme(). If I select "bottom", the caption will be centered relative to the graphic frame rather than the image, making it necessary to make changes to the margins. Is there any way to center by some argument as in the image?
You will have to use a workaround such as extracting the legend then combine it with the original plot. Here is an example using get_legend and plot_grid functions from the cowplot package.
library(ggplot2)
library(cowplot)
#>
#> ********************************************************
#> Note: As of version 1.0.0, cowplot does not change the
#> default ggplot2 theme anymore. To recover the previous
#> behavior, execute:
#> theme_set(theme_cowplot())
#> ********************************************************
p1 <- ggplot(iris, aes(x = Species, y = Petal.Length)) +
geom_col(aes(fill = Species)) +
coord_flip() +
scale_fill_brewer(palette = 'Set2') +
theme_minimal(base_size = 14) +
theme(legend.position = 'bottom')
# extract the legend
p1_legend <- get_legend(p1)
# plot p1 and legend together
p2 <- plot_grid(p1 + theme(legend.position = 'none'), p1_legend,
nrow = 2, rel_heights = c(1, 0.1))
# comparison
plot_grid(p1, p2,
nrow = 2)
Created on 2019-12-25 by the reprex package (v0.3.0)

Common border across two legends in ggplot2

I'm generating a simple chart:
data(iris); require(ggthemes)
ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(aes(shape=Species, colour=Petal.Width)) +
scale_colour_gradient() +
theme_gdocs() +
labs(shape="Species label", colour="Petal width label")
I would like to draw a common border across those two legends:
Obviously the code theme(legend.background = element_rect(colour = 'black')) will generate two borders, each for each legend element.
Edit
As of version 2.2.0, ggplot allows a border for each individual legend (legend.background), and a border for the combined legend (legend.box.background). Set the legend.box.background to desired colors, fills, sizes, etc. But also set legend.background to element_blank().
ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(aes(shape=Species, colour=Petal.Width)) +
scale_colour_gradient() +
theme_gdocs() +
labs(shape="Species label", colour="Petal width label") +
theme(legend.background = element_blank(),
legend.box.background = element_rect(size = 2))
You probably need to delve into the structure of the ggplot grob; something like this:
Minor edit: Updating to ggplot2 2.0.0 (and ggthemes 3.0.0)
# Load packages and data
library(ggplot2)
library(gtable)
library(grid)
data(iris)
# Small problem with theme_gdocs drawing a border around each legend.
# Fixed in the github development version
# library(devtools)
# install_github("jrnold/ggthemes")
library(ggthemes)
p <- ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(aes(shape=Species, colour=Petal.Width)) +
scale_colour_gradient() +
theme_gdocs() +
labs(shape="Species label", colour="Petal width label")
# Get the ggplot grob
gt = ggplotGrob(p)
# Get the combined legend
leg = gtable_filter(gt, "guide-box")
# Get the individual legends - to get their widths and heights
leg1 = leg$grobs[[1]]$grobs[[1]]
leg2 = leg$grobs[[1]]$grobs[[2]]
# Calculate widths and heights for border (Note: some margin widths are dropped from widths)
rectHeight = sum(leg1$heights + leg2$heights)
rectWidth = sum(unit.pmax(leg1$widths[3:5], leg2$widths[3:5]))
# Create border with widths and heights
rect <- rectGrob( width = rectWidth, height = rectHeight,
gp = gpar(col = "black", fill = NA, lwd = 5), name = "gRect")
# Create combined legend - i.e., legend plus border
leg <- grobTree(leg, rect)
# Insert combined legend back into ggplot grob
gt$grobs[gt$layout$name == "guide-box"][[1]] <- leg
# Draw it
grid.newpage()
grid.draw(gt)

R, ggplot - Graphs sharing the same y-axis but with different x-axis scales

Context
I have some datasets/variables and I want to plot them, but I want to do this in a compact way. To do this I want them to share the same y-axis but distinct x-axis and, because of the different distributions, I want one of the x-axis to be log scaled and the other linear scaled.
Example
Suppose I have a long tailed variable (that I want the x-axis to be log-scaled when plotted):
library(PtProcess)
library(ggplot2)
set.seed(1)
lambda <- 1.5
a <- 1
pareto <- rpareto(1000,lambda=lambda,a=a)
x_pareto <- seq(from=min(pareto),to=max(pareto),length=1000)
y_pareto <- 1-ppareto(x_pareto,lambda,a)
df1 <- data.frame(x=x_pareto,cdf=y_pareto)
ggplot(df1,aes(x=x,y=cdf)) + geom_line() + scale_x_log10()
And a normal variable:
set.seed(1)
mean <- 3
norm <- rnorm(1000,mean=mean)
x_norm <- seq(from=min(norm),to=max(norm),length=1000)
y_norm <- pnorm(x_norm,mean=mean)
df2 <- data.frame(x=x_norm,cdf=y_norm)
ggplot(df2,aes(x=x,y=cdf)) + geom_line()
I want to plot them side by side using the same y-axis.
Attempt #1
I can do this with facets, which looks great, but I don't know how to make each x-axis with a different scale (scale_x_log10() makes both of them log scaled):
df1 <- cbind(df1,"pareto")
colnames(df1)[3] <- 'var'
df2 <- cbind(df2,"norm")
colnames(df2)[3] <- 'var'
df <- rbind(df1,df2)
ggplot(df,aes(x=x,y=cdf)) + geom_line() +
facet_wrap(~var,scales="free_x") + scale_x_log10()
Attempt #2
Use grid.arrange, but I don't know how to keep both plot areas with the same aspect ratio:
library(gridExtra)
p1 <- ggplot(df1,aes(x=x,y=cdf)) + geom_line() + scale_x_log10() +
theme(plot.margin = unit(c(0,0,0,0), "lines"),
plot.background = element_blank()) +
ggtitle("pareto")
p2 <- ggplot(df2,aes(x=x,y=cdf)) + geom_line() +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = unit(c(0,0,0,0), "lines"),
plot.background = element_blank()) +
ggtitle("norm")
grid.arrange(p1,p2,ncol=2)
PS: The number of plots may vary so I'm not looking for an answer specifically for 2 plots
Extending your attempt #2, gtable might be able to help you out. If the margins are the same in the two charts, then the only widths that change in the two plots (I think) are the spaces taken by the y-axis tick mark labels and axis text, which in turn changes the widths of the panels. Using code from here, the spaces taken by the axis text should be the same, thus the widths of the two panel areas should be the same, and thus the aspect ratios should be the same. However, the result (no margin to the right) does not look pretty. So I've added a little margin to the right of p2, then taken away the same amount to the left of p2. Similarly for p1: I've added a little to the left but taken away the same amount to the right.
library(PtProcess)
library(ggplot2)
library(gtable)
library(grid)
library(gridExtra)
set.seed(1)
lambda <- 1.5
a <- 1
pareto <- rpareto(1000,lambda=lambda,a=a)
x_pareto <- seq(from=min(pareto),to=max(pareto),length=1000)
y_pareto <- 1-ppareto(x_pareto,lambda,a)
df1 <- data.frame(x=x_pareto,cdf=y_pareto)
set.seed(1)
mean <- 3
norm <- rnorm(1000,mean=mean)
x_norm <- seq(from=min(norm),to=max(norm),length=1000)
y_norm <- pnorm(x_norm,mean=mean)
df2 <- data.frame(x=x_norm,cdf=y_norm)
p1 <- ggplot(df1,aes(x=x,y=cdf)) + geom_line() + scale_x_log10() +
theme(plot.margin = unit(c(0,-.5,0,.5), "lines"),
plot.background = element_blank()) +
ggtitle("pareto")
p2 <- ggplot(df2,aes(x=x,y=cdf)) + geom_line() +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = unit(c(0,1,0,-1), "lines"),
plot.background = element_blank()) +
ggtitle("norm")
gt1 <- ggplotGrob(p1)
gt2 <- ggplotGrob(p2)
newWidth = unit.pmax(gt1$widths[2:3], gt2$widths[2:3])
gt1$widths[2:3] = as.list(newWidth)
gt2$widths[2:3] = as.list(newWidth)
grid.arrange(gt1, gt2, ncol=2)
EDIT
To add a third plot to the right, we need to take more control over the plotting canvas. One solution is to create a new gtable that contains space for the three plots and an additional space for a right margin. Here, I let the margins in the plots take care of the spacing between the plots.
p1 <- ggplot(df1,aes(x=x,y=cdf)) + geom_line() + scale_x_log10() +
theme(plot.margin = unit(c(0,-2,0,0), "lines"),
plot.background = element_blank()) +
ggtitle("pareto")
p2 <- ggplot(df2,aes(x=x,y=cdf)) + geom_line() +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = unit(c(0,-2,0,0), "lines"),
plot.background = element_blank()) +
ggtitle("norm")
gt1 <- ggplotGrob(p1)
gt2 <- ggplotGrob(p2)
newWidth = unit.pmax(gt1$widths[2:3], gt2$widths[2:3])
gt1$widths[2:3] = as.list(newWidth)
gt2$widths[2:3] = as.list(newWidth)
# New gtable with space for the three plots plus a right-hand margin
gt = gtable(widths = unit(c(1, 1, 1, .3), "null"), height = unit(1, "null"))
# Instert gt1, gt2 and gt2 into the new gtable
gt <- gtable_add_grob(gt, gt1, 1, 1)
gt <- gtable_add_grob(gt, gt2, 1, 2)
gt <- gtable_add_grob(gt, gt2, 1, 3)
grid.newpage()
grid.draw(gt)
The accepted answer is exactly what makes people run when comes to plotting using R! This is my solution:
library('grid')
g1 <- ggplot(...) # however you draw your 1st plot
g2 <- ggplot(...) # however you draw your 2nd plot
grid.newpage()
grid.draw(cbind(ggplotGrob(g1), ggplotGrob(g2), size = "last"))
This takes care of the y axis (minor and major) guide-lines to align in multiple plots, effortlessly.
Dropping some axis text, unifying the legends, ..., are other tasks that can be taken care of while creating the individual plots, or by using other means provided by grid or gridExtra packages.
The accepted answer looks a little too daunting to me. So I find two ways to get around it with less efforts. Both are based on your Attempt #2 grid.arrange() method.
1. Make plot 1 no y-axis as well
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank()
So all the plots will be the same. You won't have problems with different aspects ratios. You will need to generate a separate y-axis with R or your favorite image editting app.
2. Fix and respect aspects ratio
Add aspect.ratio = 1 or whatever ratio you desire to theme() of individual plots. Then use respect=TRUE in your grid.arrange()
This way you can keep y-axis in plot1 and still maintains aspects ratio in all plots. Inspired by this answer.
Hope you find these helpful!

Resources