Draw a box around the legend graphic in ggplot? - r

A similar sounding question is asked here. However, in the linked question, they put a bounding box around the legend and legend title. I was wondering if it's possible to put a bounding box around the legend graphic. For example,
library(ggplot2)
# Dummy data
x <- LETTERS[1:20]
y <- paste0("var", seq(1,20))
data <- expand.grid(X=x, Y=y)
data$Z <- runif(400, 0, 5)
# Heatmap
ggplot(data, aes(X, Y, fill= Z)) +
geom_tile()+
scale_fill_gradient(low = "white" ,high = "red")
The above code creates this plot:
Whereas, I am trying to create something like this:
I tried playing with legend.background function from ggplot but I can't get it to work for me.
Any suggestions as to how I would do this?

I don't think you can do this with theme() - you can do it with guides() though, e.g.
library(ggplot2)
# Dummy data
x <- LETTERS[1:20]
y <- paste0("var", seq(1,20))
data <- expand.grid(X=x, Y=y)
data$Z <- runif(400, 0, 5)
# Heatmap
ggplot(data, aes(X, Y, fill= Z)) +
geom_tile()+
scale_fill_gradient(low = "white" ,high = "red") +
guides(fill = guide_colorbar(frame.colour = "black", frame.linewidth = 1.5))
Edit per comment
Looking at the source code for new_scale() there should be a way to apply this solution to two of the same scales, but I can't figure it out. I reckon you should post another question and see if someone can solve it. Until then, maybe this workaround based on cowplot will work for you e.g.
## (I changed 'fill' to 'color' here, but the concept is the same)
library(ggplot2)
library(ggnewscale)
library(cowplot)
# Dummy data
x <- LETTERS[1:20]
y <- paste0("var", seq(1,20))
data <- expand.grid(X=x, Y=y)
data$Z <- runif(400, 0, 5)
# Heatmap with both scales but legends aren't plotted
p1 <- ggplot(data, aes(X, Y, color = Z)) +
geom_tile() +
scale_color_gradient(low = "white", high = "red") +
new_scale_color() +
geom_point(aes(color = Z)) +
theme(legend.position = "none")
# Heatmap with only the first scale
p2 <- ggplot(data, aes(X, Y, color = Z)) +
geom_tile() +
scale_color_gradient(low = "white", high = "red") +
guides(color = guide_colorbar(frame.colour = "black", frame.linewidth = 1.5))
# Heatmap with only the second scale
p3 <- ggplot(data, aes(X, Y, color = Z)) +
geom_point(aes(color = Z)) +
guides(color = guide_colorbar(frame.colour = "black", frame.linewidth = 1.5))
# Grab the legends using cowplot::get_legend()
p2_legend <- get_legend(p2)
p3_legend <- get_legend(p3)
# Combine the legends one on top of the other
legends <- plot_grid(p2_legend, p3_legend, ncol = 1, nrow = 2)
# Combine the heatmap with the legends
plot_grid(p1, legends, ncol = 2, align = "h", rel_widths = c(0.9, 0.1))
## You may need to tinker with spacing/scale/rel_widths/rel_heights to get it looking right, but it should work out ok with some effort

Related

How to sensibly align two legends when using cowplot in R?

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))

Inner ticks in ggplot

I want to add inner ticks to my plot given by a vector.
say my vector is myvec <- c(1,3,4:9, 12, 15)
and my plot:
library(ggplot2)
df <- data.frame(x=seq(1:100), y=sort(rexp(100, 2), decreasing = T))
ggplot(df, aes(x=x, y=y)) + geom_point() +
scale_y_continuous(limits = c(0, 4))
I now want to add inside facing ticks at x= myvec, y=0 in blue color. How do I do it?
Tried to work with this solution, but could not use the vector.
Annotate ggplot with an extra tick and label
Try This:
df2<- cbind.data.frame("myvec" = myvec, z= rep(0, length(myvec)))
ggplot(df, aes(x=x, y=y)) + geom_point() +
scale_y_continuous(limits = c(0, 4)) +
geom_point(data=df2, aes(x=myvec, y=z), shape = "\U2714", color = "blue", size= 2)

Pass changed geom from object to other ggplot

I first make a plot
df <- data.frame(x = c(1:40, rep(1:20, 3), 15:40))
p <- ggplot(df, aes(x=x, y = x)) +
stat_density2d(aes(fill='red',alpha=..level..),geom='polygon', show.legend = F)
Then I want to change the geom_density values and use these in another plot.
# build plot
q <- ggplot_build(p)
# Change density
dens <- q$data[[1]]
dens$y <- dens$y - dens$x
Build the other plot using the changed densities, something like this:
# Built another plot
ggplot(df, aes(x=x, y =1)) +
geom_point(alpha = 0.3) +
geom_density2d(dens)
This does not work however is there a way of doing this?
EDIT: doing it when there are multiple groups:
df <- data.frame(x = c(1:40, rep(1:20, 3), 15:40), group = c(rep('A',40), rep('B',60), rep('C',26)))
p <- ggplot(df, aes(x=x, y = x)) +
stat_density2d(aes(fill=group,alpha=..level..),geom='polygon', show.legend = F)
q <- ggplot_build(p)
dens <- q$data[[1]]
dens$y <- dens$y - dens$x
ggplot(df, aes(x=x, y =1)) +
geom_point(aes(col = group), alpha = 0.3) +
geom_polygon(data = dens, aes(x, y, fill = fill, group = piece, alpha = alpha)) +
scale_alpha_identity() +
guides(fill = F, alpha = F)
Results when applied to my own dataset
Although this is exactly what I'm looking for the fill colors seem not to correspond to the initial colors (linked to A, B and C):
Like this? It is possible to plot a transformation of the shapes plotted by geom_density. But that's not quite the same as manipulating the underlying density...
ggplot(df, aes(x=x, y =1)) +
geom_point(alpha = 0.3) +
geom_polygon(data = dens, aes(x, y, fill = fill, group = piece, alpha = alpha)) +
scale_alpha_identity() +
guides(fill = F, alpha = F)
Edit - OP now has multiple groups. We can plot those with the code below, which produces an artistic plot of questionably utility. It does what you propose, but I would suggest it would be more fruitful to transform the underlying data and summarize that, if you are looking for representative output.
ggplot(df, aes(x=x, y =1)) +
geom_point(aes(col = group), alpha = 0.3) +
geom_polygon(data = dens, aes(x, y, fill = group, group = piece, alpha = alpha)) +
scale_alpha_identity() +
guides(fill = F, alpha = F) +
theme_minimal()

overlaying plots from different dataframes in ggplot without messing with legend

I want to overlay two plots: one is a simple point plot where a variable is used to control the dot size; and another is a simple curve.
Here is a dummy example for the first plot;
library(ggplot2)
x <- seq(from = 1, to = 10, by = 1)
df = data.frame(x=x, y=x^2, v=2*x)
ggplot(df, aes(x, y, size = v)) + geom_point() + theme_classic() + scale_size("blabla")
Now lets overlay a curve to this plot with data from another dataframe:
df2 = data.frame(x=x, y=x^2-x+2)
ggplot(df, aes(x, y, size = v)) + geom_point() + theme_classic() + scale_size("blabla") + geom_line(data=df2, aes(x, y), color = "blue") + scale_color_discrete(name = "other", labels = c("nanana"))
It produces the error:
Error in FUN(X[[i]], ...) : object 'v' not found
The value in v is not used to draw the intended curse, but anyway, I added a dummy v to df2.
df2 = data.frame(x=x, y=x^2-x+2, v=replicate(length(x),0)) # add a dummy v
ggplot(df, aes(x, y, size = v)) + geom_point() + theme_classic() + scale_size("blabla") + geom_line(data=df2, aes(x, y), color = "blue") + scale_color_discrete(name = "other", labels = c("nanana"))
An the result has a messed legend:
What is the right way to achieve the desired plot?
You can put the size aes in the geom_point() call to make it so that you don't need the dummy v in df2.
Not sure exactly what you want regarding the legend. If you replace the above, then the blue portion goes away. If you want to have a legend for the line color, then you have to place color inside the geom_line aes call.
x <- seq(from = 1, to = 10, by = 1)
df = data.frame(x=x, y=x^2, v=2*x)
df2 = data.frame(x=x, y=x^2-x+2)
ggplot(df, aes(x, y)) +
geom_point(aes(size = v)) +
theme_classic() +
scale_size("blabla") +
geom_line(data=df2, aes(x, y, color = "blue")) +
scale_color_manual(values = "blue", labels = "nanana", name = "other")

Broken polygons in levelplot using ggplot stat_density_2d

Creating a levelplot using ggplot's stat_density_2d I get "broken" polygons. For example, the outer one in the example below.
How can I fix this, to get a smooth form?
set.seed(0)
n <- 50
d <- data.frame(x = rnorm(n, -.7, .5),
y = rnorm(n, 0, .8))
ggplot(d, aes(x, y)) +
geom_point() +
stat_density_2d(aes(fill = ..level..), alpha=.1, geom = "polygon")
To build on #hrbrmstr's answer (which, at least on my machine, lops off one data point because the x scale isn't sufficiently wide), a slightly more involved approach would be to get the limits of the data, set the scale limits, then reset the plot limits back to the original range:
g <- ggplot(d, aes(x, y)) +
geom_point() +
stat_density_2d(aes(fill = ..level..), alpha=.1, geom = "polygon")
dat_lims <- lapply(d, function(v) c(min(v), max(v)))
plot_lims <- ggplot_build(g)$panel$ranges[[1]][c("x.range", "y.range")]
g +
scale_x_continuous(limits = dat_lims$x * 1.1) +
scale_y_continuous(limits = dat_lims$y * 1.1) +
coord_cartesian(xlim = plot_lims$x.range, ylim = plot_lims$y.range)
Output:

Resources