Related
A similar question was asked here, however I cant adapt the answer to my issue.
I am trying to correctly align two legends when using cowplot. For example, if I create some data and a cowplot with two legends like so:
library(cowplot)
library(ggplot2)
# create some data
dat <- NULL
for(i in 1:20){
x <- LETTERS[1:5]
y <- paste0("var", seq(1,5))
dat[[i]] <- expand.grid(X=x, Y=y)
dat[[i]]$Z <- runif(25, 0, 1)
}
# plotting function
plotFun <- function(data){
ggplot(data, aes(X, Y, fill= Z)) +
geom_tile() +
theme(aspect.ratio = 1,
legend.justification = c(0,1),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank()) +
xlab("") + ylab("")
}
# set up to plot on a grid
allPlots <- lapply(dat, plotFun)
allPlotsAlter <- lapply(allPlots, function(x) x + theme(legend.position = "none"))
n <- length(allPlotsAlter)
nRow <- floor(sqrt(n))
plotGrid <- gridExtra::arrangeGrob(grobs=allPlotsAlter, nrow=nRow)
# create a different type of legend
newPlot <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, fill = Species)) +
geom_bar(stat = 'identity') + theme(legend.justification = c(0,1))
# get both legends and combine
legend <- cowplot::get_legend(allPlots[[1]])
legend1 <- cowplot::get_legend(newPlot)
combineLegend <- cowplot::plot_grid(
legend,
legend1,
nrow = 2)
# now make plot
cowplot::plot_grid(plotGrid,
combineLegend,
rel_widths = c(0.9, 0.11),
ncol = 2)
That creates this type of plot:
As you can see, the two legends have quite a bit of vertical space between them and they are not centred with the plot.
Is there a way to align the two legends so they look something like this:
I'm not sure if it is possible using cowplot... or is there a way to maybe use ggplot's annotate to place the legends?
I would probably go for patchwork, as Stefan suggests, but within cowplot you probably need to adjust the legend margins:
theme_margin <- theme(legend.box.margin = margin(100, 10, 100, 10))
legend <- cowplot::get_legend(allPlots[[1]] + theme_margin)
legend1 <- cowplot::get_legend(newPlot + theme_margin)
combineLegend <- cowplot::plot_grid(
legend,
legend1,
nrow = 2)
# now make plot
cowplot::plot_grid(plotGrid,
combineLegend,
rel_widths = c(0.9, 0.11),
ncol = 2)
If switching to another package is an option for you I would suggest to use patchwork to glue your plots together. One feature offered by patchwork is that using plot_spacer you could easily add some empty panels above and below your legends to "move" them to the center and thereby getting rid of the empty space. Depending on your final result or the height of your final plot you probably have to play a bit around with the heights and/or widths arguments:
library(cowplot)
library(ggplot2)
library(patchwork)
set.seed(123)
# create some data
dat <- NULL
for (i in 1:20) {
x <- LETTERS[1:5]
y <- paste0("var", seq(1, 5))
dat[[i]] <- expand.grid(X = x, Y = y)
dat[[i]]$Z <- runif(25, 0, 1)
}
# plotting function
plotFun <- function(data) {
ggplot(data, aes(X, Y, fill = Z)) +
geom_tile() +
theme(
aspect.ratio = 1,
legend.justification = c(0, 1),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
labs(x = NULL, y = NULL)
}
# set up to plot on a grid
allPlots <- lapply(dat, plotFun)
allPlotsAlter <- lapply(allPlots, function(x) x + theme(legend.position = "none"))
n <- length(allPlotsAlter)
nRow <- floor(sqrt(n))
plotGrid <- wrap_plots(grobs = allPlotsAlter, nrow = nRow)
# create a different type of legend
newPlot <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, fill = Species)) +
geom_bar(stat = "identity") +
theme(legend.justification = c(0, 1))
# get both legends and combine
legend <- cowplot::get_legend(allPlots[[1]])
legend1 <- cowplot::get_legend(newPlot)
combineLegend <- plot_spacer() + legend + legend1 + plot_spacer() + plot_layout(ncol = 1, heights = c(.5, 1, 1, .5))
wrap_elements(plotGrid) + combineLegend + plot_layout(widths = c(4, 1))
I have three plots p1, p2 and p3.
I would like to combine p2 and p3 and add the legends of p2 and p1 on the right side above each other. In the following example the legends are identical. In the real data they are differend.
I use ggplot2 and cowplot
# plot 1
p1 <- ggplot(iris, aes(Sepal.Length, fill = Species)) +
geom_density(alpha = .7)
# plot 2
p2 <- ggplot(iris, aes(Sepal.Width, fill = Species)) +
geom_density(alpha = .7)
# plot 3
p3 <- ggplot(iris, aes(Petal.Width, fill = Species)) +
geom_density(alpha = .7)
# legend1
legend1 <- get_legend(p1)
# legend2
legend2 <- get_legend(p2)
# combine plots
prow <- plot_grid( p2 + theme(legend.position="none"),
p3 + theme(legend.position="none"),
align = 'vh',
labels = c("a", "b"),
hjust = -1,
nrow = 1,
axis="1"
)
prow
# add legend1
p <- plot_grid( prow, legend1, rel_widths = c(1, .3))
p
# add legend2
plot_grid(p, legend2, rel_widths =c(1, .3))
This gives me this:
What I try to get is:
I tried to solve the problem using
+ theme(legend.position=c()
and
+ theme(legend.justification = "top"))
however, I could not get the desired plot.
You're actually very close. You can use ggdraw to achieve what you want. See ?get_legend for another example of this.
# combine plots
prow <- ggdraw(
plot_grid(
plot_grid(p2 + theme(legend.position="none") ,
p3 +theme(legend.position="none"),
align = 'vh',
labels = c("a", "b"),
hjust = -1,
nrow = 1,
axis="1"),
# as suggested by aosmith you can add additional columns to make legends appear closer
plot_grid(legend1, legend2,ncol=4),
# I also set the relative widths so the legend takes up less space
nrow=1, rel_widths = c(1,0.2))
)
prow
I have 7 plots and I organize them in 2 columns, one with 3 plots and other one with 4 plots. I used plot_grid from cowplot for that. The result is almost perfect, however, the column with 3 plots has bigger plots. How can I scale this column to get the same size in all plots and align the first and the last plot of each column?
library(ggplot2)
library(cowplot)
Value <- seq(0,1000, by = 1000/10)
Index <- 0:10
DF <- data.frame(Index, Value)
plot1 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
plot2 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
plot3 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
plot4 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
plot5 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
plot6 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
plot7 <- ggplot(DF, aes(x = Index, y = Value)) +
geom_line(linetype = 2) +
theme(aspect.ratio = 0.5)
col1 <- plot_grid(plot1, plot2, plot3, align = "v", ncol = 1)
col2 <- plot_grid(plot4, plot5, plot6, plot7, align = "v", ncol = 1)
plot_grid(col1, col2, ncol = 2, align = "hv")
You can place a null plot into your first column:
col1 <- plot_grid(plot1, plot2, plot3, NULL, align = "v", ncol = 1)
However, to me, this is not a case for a nested plot grid. Nested plot grids are specifically for the purpose of combining plots with complex arrangements, e.g. one plot in one column spanning two rows in another column. For what you want, I would do a single plot_grid() call, just like the other comment suggests:
plot_grid(plot1, plot4, plot2, plot5, plot3, plot6, NULL, plot7,
ncol = 2, align = "hv")
Please also note that the align option doesn't work when you specify a particular aspect ratio. You can either specify an aspect ratio or align. In most cases, it's not necessary to specify an aspect ratio, you can do that when saving the figure. The function save_plot() in particular takes an aspect ratio option (applied to the whole image, though, not the plot area).
I had do exactly this just last week! Try doing just one call to plot_grid, with NULL where you want the empty spot.
myplotlist <- list(plot1, plot2, plot3, NULL, plot4, plot5, plot6, plot7, plot8)
plot_grid(plotlist=myplotlist, align = "v", ncol = 2)
I have a simple cheat-fix if you want to keep the original plot size differences and also align them, based on the answer by #ClausWilke above.
One can always fine-tune plots alignments using cowplot:plot_grid by adding "nested" NULL plots and changing their height/width ratios. See the solution below:
group1<-plot_grid(plot1, plot2, plot3, nrow = 3)
group2<-plot_grid(plot4, plot5, plot6, plot7, NULL, rel_heights = c(1,1, 1,1,0.001), nrow = 5)
plot_grid(group1, group2, align='h', nrow=1)
This is the output:
This has helped me a lot in those cases where axis parameter will not do the job.
I would like to combine some graphs together using cowplot. But I cannot change the margin sizes. I want to use only one y-axes, but than the margin is still quite large, which I want to decrease. I have used the plot.margin code from ggplot, although that works when I look at the single plot, it doesn't seem to work when the plots are combined.
I have made some example code:
library(ggplot2)
library(cowplot)
x <- c("a", "b")
y1 <- c(3,6)
y2 <- c(10,15)
data1 <- data.frame(x,y1)
data2 <- data.frame(x, y2)
ylab1 <- ylab("Very nice y values")
xlab1 <- xlab("Very nice factors")
plot1 <- ggplot(data1, aes(x=x, y = y1)) +
geom_bar(stat ="identity", position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0.5,0.5,0.5,0.5), "cm")) + xlab1 + ylab1
plot1
ylab2 <- ylab("")
xlab2 <- xlab("Very nice factors")
plot2 <- ggplot(data2, aes(x=x, y = y2)) +
geom_bar(stat = "identity",position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0.5,0.5,0.5,-0.5), "cm")) + xlab2 + ylab2
plot2
plot3 <- plot_grid(plot1, plot2, labels = c("A", "B"), align = "hv",nrow = 1, ncol = 2)
plot3 # Quite large margin between the two plots
I am aware that I could avoid this problem by using facets, however my real plot is rather more complicated than this graph.
Increasing the space between plots in plot_grid was also addressed in this issue.
An extra interesting solution is the one suggested in this comment - try to add an extra empty plot between the two plots and adjust the relative columns widths:
plot4 <- plot_grid(plot1, NULL, plot2, rel_widths = c(1, 0, 1), align = "hv",
labels = c("A", "B"), nrow = 1)
plot4
Can even try negative values in rel_widths, which gives better results:
plot5 <- plot_grid(plot1, NULL, plot2, rel_widths = c(1, -0.1, 1), align = "hv",
labels = c("A", "B"), nrow = 1)
plot5
So, try a combination of adjusting the plot.margin (as answered by #J.Con) and adding an extra empty plot with tweaking rel_widths.
EDIT 2019-12-11
Also check out this comment of the author of cowplot (Claus Wilke):
For those kinds of problems I would now recommend the patchwork library. It's inherently difficult with plot_grid(), due to its underlying design
So, a fast example with patchwork based on their vignette Adding Annotation and Style goes like this:
library(patchwork)
plot3 <- plot1 + plot2 +
plot_annotation(tag_levels = 'A') &
theme(plot.tag = element_text(size = 8))
plot3
Created on 2019-12-11 by the reprex package (v0.3.0)
Your plot.margins were actually working against you. Set them to zero to fill up that white space.
plot1 <- ggplot(data1, aes(x=x, y = y1)) +
geom_bar(stat ="identity", position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0,0,0,0), "cm")) + xlab1 + ylab1
plot1
ylab2 <- ylab("")
xlab2 <- xlab("Very nice factors")
plot2 <- ggplot(data2, aes(x=x, y = y2)) +
geom_bar(stat = "identity",position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0,0,0,0), "cm")) + xlab2 + ylab2
plot2
plot3 <- plot_grid(plot1, plot2, labels = c("A", "B"), align = "hv",nrow = 1, ncol = 2)
plot3
I realize that the align.plots function from the ggExtra package has been deprecated and removed. However, I am using my own version as it seems to provide the specific functionality I need. I have looked into faceting to solve my problem but I don't think it will work for my particular issue. What seems to be the problem is that the top-to-bottom images don't align when I use coord_equal on one of them. This doesn't seem to affect left-to-right though. Here is a simplified (or at least as simple as I can make it) version of what I am trying to achieve.
Create some dummy data frames:
source('https://raw.github.com/jbryer/multilevelPSA/master/r/align.R')
require(psych)
df = data.frame(x=rnorm(100, mean=50, sd=10),
y=rnorm(100, mean=48, sd=10),
group=rep(letters[1:10], 10))
dfx = describe.by(df$x, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfx) = c('group', 'x', 'x.n', 'x.min', 'x.max')
dfy = describe.by(df$y, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfy) = c('group', 'y', 'y.n', 'y.min', 'y.max')
df2 = cbind(dfx, dfy[,2:ncol(dfy)])
range = c(0,100)
This will setup the three plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
opts(legend.position='none') +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1)
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + opts(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + opts(legend.position='none')
The alignment top to bottom does not work with coord_equal
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Broken Plot http://bryer.org/alignplots1.png
The fix is to add respect=TRUE to the grid.layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)
But if I don't use coord_equal the alignment works fine:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1a, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Working Plot http://bryer.org/alignplots2.png
ggplot2 now has ggplotGrob(), which may help with this.
First, we need to update the code used to generate the plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1) + theme_minimal() + theme(legend.position='none')
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p4 <- ggplot(df, aes(x = group, y = y)) +
geom_blank() +
theme(line = element_blank(),
rect = element_blank(),
text = element_blank(),
title = element_blank())
p4 will be blank; we just need the grob to draw.
Then we load the grid package and draw the grobs in a list arranged in rows and columns using cbind() and rbind().
library(grid)
grid.newpage()
grid.draw(
cbind(
rbind(ggplotGrob(p3), ggplotGrob(p4), size = "first"),
rbind(ggplotGrob(p1), ggplotGrob(p2), size = "first"),
size = "first"))
I'm not sure if this method will let you plot p3 in a different width and p2 in a different height, as you have them in the original example; I normally need a grid of similarly-sized graphs, and haven't needed to figure out different sizes.
This answer is partially based on partially based on https://stackoverflow.com/a/17463184/393354
Here is an example:
m <- matrix(c(3, 1, 0, 2), 2, byrow = T)
lay <- gglayout(m, widths = c(1, 3), heights = c(3, 1))
ggtable(p1, p2, p3, layout = lay)
you can use this by
install.packages('devtools')
library(devtools)
dev_mode()
install_github("ggplot2", "kohske", "cutting-edge")
library(ggplot2)
note that this branch is experimental, so maybe there are bugs.
To solve the problem using the align.plots method, specify respect=TRUE on the layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)