Common border across two legends in ggplot2 - r

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)

Related

adjust heights of two plots (nrow=1) with ggarrange

How can I display two plots in one row with R function ggarrange() so that they have the same dimensions, in particular the same height?
In this example, the second plot is a bit higher than the first plot. I would like to increase the size of a1_plot, so that it matches the size of a2_plot.
# required packages
library(ggplot2)
library(ggbreak)
library(directlabels)
library(ggpubr)
# make dataframe
df1 <- data.frame(first_column=c("value_1","value_2","value_3","value_4","value_4","value_5"),
second_column=c("123","123","325","325","656","656"),
third_column=c(12,13,1,19,200,360),
fourth_column=c(1,124,155,3533,5533,6666))
# plot 1
a1_plot <-
ggplot(df1, aes(x=third_column, y=fourth_column, colour=second_column)) +
scale_x_continuous(breaks = c(0,50,100,150,200,250,300)) +
ylab("Fourth column")+ xlab("Third column") +
scale_x_break(breaks = c(210,400)) +
geom_dl(mapping=aes(x=third_column, y=fourth_column, label=second_column),
method = list(dl.trans(x = x + 0.1), dl.combine("last.points"))) +
theme(legend.position = "none")
# plot 2
a2_plot <-
ggplot(data=df1)+
geom_point(aes(x=second_column, y=fourth_column) +
xlab("X axis")+ ylab("Y axis") +
theme(legend.position = "none")
# merge plot1 and plot2
ggarrange(print(a1_plot), print(a2_plot), labels = c('a1', 'a2'))
I was unable to change the height of plot 1. By adjusting the margins of plot 2, the problem has been solved.
theme(legend.position = "none", plot.margin = unit(x=c(3.6,5,3.9,0), units = "mm"))

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

Custom legend in ggpairs disappears after changing theme

I am having problems with a vanishing legend in ggpairs.
I add a legend inside the top part of a lower triangular ggpairs plot as follows.
First I create a ggpairs plot without legend then I strip the legend I want from and ad hoc graph and place in the ggpairs plot it with putPlot. It works nicely until I try to modify the theme which makes the legend disappear.
# 1 produce graph without legend
library(GGally)
library(ggplot2)
plotwithoutlegend <-ggpairs(
iris,
columns=1:4,
switch="both",
upper="blank",
mapping=aes(color = Species,
shape= Species,
fill=Species,
alpha=0.5)
)
#2 grab the legend from a graph with the legend I want (without alpha).
auxplot <- ggplot(iris, aes(x=Petal.Length, y=Petal.Width,
color=Species,
shape=Species,
fill=Species)) + geom_point()
mylegend <- grab_legend(auxplot)
# 3 place the legend in the ggpairs grid with putPlot
graph1 <- putPlot(plotwithoutlegend,mylegend,3,4)
show(graph1)
This produces a graph with the legend in the desired place.
ggpairs graph with legend before changing theme:
However if I change some aspect of the theme the legend disappears.
graph2 <- graph1 +theme(strip.background =element_blank(), strip.placement = "outside")
show(graph2)
Legend vanishes after changing theme:
I had similar issue. I think you need to use library(grid). see my solution.
# plotwithoutlegend
plotwithoutlegend <- ggpairs(
iris,
columns=1:4,
switch="both",
upper="blank",
mapping=aes(color = Species,
shape= Species,
fill=Species,
alpha=0.5)
)+
theme(strip.background =element_blank(), strip.placement = "outside")
#2 grab the legend from a graph with the legend I want (without alpha).
auxplot <- ggplot(iris, aes(x=Petal.Length, y=Petal.Width,
color=Species,
shape=Species,
fill=Species)) + geom_point()
mylegend <- grab_legend(auxplot)
##### plot legend with plot
grid.newpage()
grid.draw(plotwithoutlegend)
vp = viewport(x=.9, y=.75, width=.35, height=.3) ## control legend position
pushViewport(vp)
grid.draw(mylegend)
upViewport()
Learned here: Legend using ggpairs
# first run your code until
graph2 <- graph1 +theme(strip.background =element_blank(), strip.placement = "outside")
# then run this code
colidx <- c(3,5,6,7)
for (i in 1:length(colidx)) {
# Address only the diagonal elements
# Get plot out of plot-matrix
inner <- getPlot(graph2, i, i);
# Add ggplot2 settings (here we remove gridlines)
inner <- inner + theme(panel.grid = element_blank()) +
theme(axis.text.x = element_blank())
# Put it back into the plot-matrix
graph2 <- putPlot(graph2, inner, i, i)
for (j in 1:length(colidx)){
if((i==1 & j==1)){
# Move the upper-left legend to the far right of the plot
inner <- getPlot(graph2, i, j)
inner <- inner + theme(legend.position=c(length(colidx)-0.25,0.50))
graph2 <- putPlot(graph2, inner, i, j)
}
else{
# Delete the other legends
inner <- getPlot(graph2, i, j)
inner <- inner + theme(legend.position="none")
graph2 <- putPlot(graph2, inner, i, j)
}
}
}
# then run this code
show(graph2)

How to vertically arrange ggplots with single set of axes and legend?

I'd like to vertically arrange my stacked geom_bar objects and display them with unbroken vertical lines (see concept below) and a single set of axes and legend. I'm using plot_grid now but should perhaps be using facet wrapping? I'm unsure whether that would allow me to place vertical lines. The code that generates my current plot is here.
my concept:
my current plot:
You could create your plots and disable the axis text, line and ticks. Then make the axis titles match the background color so they are not visible (but retain the same graph dimensions) and plot them with plot_grid() as you are doing. Then overlay a full sized plot with zero data, the axis titles and vertical lines over the top of it using draw_plot(). For the single legend, leverage the following SO answer:
Align multiple plots in ggplot2 when some have legends and others don't
The code:
#!/usr/bin/env Rscript
if (!require("pacman")) install.packages("pacman")
pacman::p_load(ggplot2, cowplot)
### Create some garbage data to plot
d0 <- data.frame(foo=c(0,0,0,0,0),bar=c("SX_RUNNYNOSE","SX_COUGH","SX_HEADACHE","SX_MALAISE","SX_MYALGIA"))
d1 <- data.frame(foo=c(1,2,3,4,5),bar=c("SX_RUNNYNOSE","SX_COUGH","SX_HEADACHE","SX_MALAISE","SX_MYALGIA"))
### Create a plot with 0 data but having the axis titles and vertical lines
p0 <- ggplot(d0, aes(x=seq(1,5), y=foo, fill=bar)) +
geom_bar(stat="identity") +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.line.x=element_blank(),
axis.line.y=element_blank(),
axis.ticks.x=element_blank(),
axis.ticks.y=element_blank()
) +
theme(legend.position = "none") +
geom_segment(aes(x=2, y=0, xend=2, yend=4.9), color='red') +
geom_text(aes(x=2, y=max(d1$foo), label="T0")) +
geom_segment(aes(x=3, y=0, xend=3, yend=4.9), color='red') +
geom_text(aes(x=3, y=max(d1$foo), label="T24")) +
labs(y="Continued Symptom Count Among Samples", x="Time Elapsed Since Viral Challenge")
### A bar pot with the sample data and only the bars (no axis, etc)
### Make color of axis titles white to match the background color so they are not visible
p1 <- ggplot(d1, aes(x=seq(1,5), y=foo, fill=bar)) +
geom_bar(stat="identity") +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.line.x=element_blank(),
axis.line.y=element_blank(),
axis.ticks.x=element_blank(),
axis.ticks.y=element_blank(),
axis.title.x = element_text(colour = "white"),
axis.title.y = element_text(colour = "white")
) +
theme(legend.title=element_blank())
### Arrange bar plots and legends in a grid and use draw_plot to
### overlay the single axis titles and vertical bars across all
### plots
g <- plot_grid(
plot_grid(
p1 + theme(legend.position = "none")
, p1 + theme(legend.position = "none")
, p1 + theme(legend.position = "none")
, ncol = 1
, align = "h"
, labels=c("Rhinovirus", "H3N2", "H1N1")
, hjust=c(-0.5,-1,-1)) +
draw_plot(p0, 0, 0, 1, 1, 1)
, plot_grid(
ggplot()
, get_legend(p1)
, ggplot()
, ncol =1)
, rel_widths = c(9,3)
)
g
The result:

How to display strip labels below the plot when faceting?

It seems that the strips are always above the plot created by ggplot2. Can they be moved below the plot?
For example:
library(ggplot2)
qplot(hwy, cty, data = mpg) + facet_grid( . ~ manufacturer)
displays the car information on top. Can they be displayed be at the bottom?
Update: Using ggplot2 version 2.1.0, consider using switch = 'x'. See ?facet_grid for details.
Using gtable functions, it is easy to move the strip. (Or see here for anther version - swapping x-axis and strip)
library(ggplot2)
library(gtable)
library(grid)
p <- ggplot(mpg, aes(hwy, cty)) + geom_point() + facet_grid( . ~ manufacturer) +
theme(strip.text.x = element_text(angle = 90, vjust = 1),
strip.background = element_rect(fill = NA))
# Convert the plot to a grob
gt <- ggplotGrob(p)
# Get the positions of the panels in the layout: t = top, l = left, ...
panels <-c(subset(gt$layout, grepl("panel", gt$layout$name), select = t:r))
# Add a row below the x-axis tick mark labels,
# the same height as the strip
gt = gtable_add_rows(gt, gt$height[min(panels$t)-1], max(panels$b) + 2)
# Get the strip grob
stripGrob = gtable_filter(gt, "strip-t")
# Insert the strip grob into the new row
gt = gtable_add_grob(gt, stripGrob, t = max(panels$b) + 3, l = min(panels$l), r = max(panels$r))
# remove the old strip
gt = gt[-(min(panels$t)-1), ]
grid.newpage()
grid.draw(gt)

Resources