Space between gpplot2 horizontal legend elements - r

I have a ggplot2 plot as follows:
library(ggplot2)
ggplot(mtcars, aes(factor(cyl), fill=factor(cyl))) +
geom_bar() +
coord_flip() +
theme(legend.position = 'top') +
guides(fill = guide_legend(title=NULL))
I'd like add spacing between the fill elements as follows:

The issue mentioned by alistaire and Tyler Rinker was solved. Now we can adjust the margins of the element_text`.
ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) +
geom_bar() +
coord_flip() +
theme(
legend.position = 'top',
legend.title = element_blank(),
legend.text = element_text(margin = margin(r = 2, unit = 'cm'))
)

It really seems something like theme(legend.text = element_text(margin = margin(r = 2, unit = 'in'))) would be the right way to accomplish the task, but that doesn't do anything at all.
Instead, (and not for the first time) I fall back on the Microsoft Word style of alignment-hacking, i.e. just add spaces:
ggplot(mtcars, aes(factor(cyl), fill=factor(paste(cyl, ' ')))) +
geom_bar() +
coord_flip() +
theme(legend.position = 'top') +
guides(fill = guide_legend(title=NULL))
Because there's spaces on the 8 as well, it's a little off-center, but if you just paste them onto the previous labels you can nudge them around as you like.
Apologies for any nightmares caused to graphic designers.

This is another hack but one that I prefer, as it adds additional white space at the end of each label according to its number of characters. Replace fill = factor(cyl) with
fill = sprintf("%-20s", factor(cyl)).
This pads all strings in the vector with white characters on the right to reach 20 characters total. This is perfect if you have text labels of different lengths. You can change 20 to whatever number you want, or remove the negative sign to add spaces to the left instead of the right. In general sprintf() is a good function to explore and use for formatting text and numbers as desired.

This is a hack, but...
Let's add some empty factor levels in cyl between the real levels. Then we'll make sure they're included in the plot (using drop=FALSE) for spacing in the legend, but will set their colors and labels to empty values so that you can't see them in the legend. I found that I also needed to include override.aes=list(color="white") in order to avoid the blank legend key boxes still being ever-so-slightly visible in the legend.
mtcars$cyl = factor(mtcars$cyl, levels=c(4, 11:15, 6, 16:20, 8))
cols = hcl(seq(15,375,length.out=4)[1:3], 100, 65)
ggplot(mtcars, aes(cyl, fill=cyl)) +
geom_bar() +
coord_flip() +
scale_fill_manual(values=c(cols[1], rep("white",5), cols[2], rep("white",5), cols[3]),
labels=c(4, rep("",5), 6, rep("",5), 8), drop=FALSE) +
theme(legend.position = 'top') +
guides(fill = guide_legend(title=NULL, nrow=1, override.aes=list(color="white")))

With ggplot2 v3.0.0, we can use legend.spacing.x to manipulate the space between legend keys.
library(ggplot2)
ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) +
geom_bar() +
coord_flip() +
theme(legend.position = 'top') +
guides(fill = guide_legend(title = "Cyl")) +
theme(legend.spacing.x = unit(0.5, 'cm'))
Created on 2018-05-30 by the reprex package (v0.2.0).

Not a hack here, this is the way to do it:
Use theme(legend.text = element_text(margin = margin(r = 2, unit = 'cm')))
ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) +
geom_bar() +
coord_flip() +
theme(
legend.position = 'top',
legend.title = element_blank(),
legend.text = element_text(margin = margin(r = 2, unit = 'cm'))
)
Will do it.

Related

ggplot legend without title leaves space for title

So I have a ggplot that doesn't require a legend because it actually has a title and thus doesn't need a legend that would simply repeat the title.
Imagine something like this:
ggplot(iris)+
geom_point(aes(x=Sepal.Length, y=Sepal.Width, color=Species))+
theme(legend.box.background=element_rect(fill="white", color="black"))+
labs(color="")+
ggtitle("Sepals ~ Species")+
xlab("Length")+
ylab("Width")
(ignore the fact that the legend in my reprex only has two lines drawn for the box)
Do you notice the graphical problem? Apparently ggplot "thinks" there is a legend title and leaves some space, so I though using element_blank for the legend title might work.
ggplot(iris)+
geom_point(aes(x=Sepal.Length, y=Sepal.Width, color=Species))+
labs(color=element_blank())+
theme(legend.box.background=element_rect(fill=NA, color="black"),
legend.margin=margin(t=0,r=0,b=0,l=0))+
ggtitle("Sepals ~ Species")+
xlab("Length")+
ylab("Width")
While this improves the situation by making the box smaller at the top, it does not fix the problem because the top space is still smaller. As I have manually set the legend margins to 0 this can't be the issue.
any ideas?
You can set theme(legend.title = element_blank()) . This also means you don't need to set an empty string for the label.
To show this, let's make that box outline a little thicker, and use the "empty string" method:
ggplot(iris) +
geom_point(aes(Sepal.Length, Sepal.Width, color = Species)) +
ggtitle("Sepals ~ Species") +
labs(x = "Length", y = "Width", color = "") +
theme(legend.box.background = element_rect(color ="black", size = 2))
We can see that there is an obvious space where the title should be.
But now let's try it with the element_blank() method:
ggplot(iris) +
geom_point(aes(Sepal.Length, Sepal.Width, color = Species)) +
ggtitle("Sepals ~ Species") +
labs(x = "Length", y = "Width") +
theme(legend.box.background = element_rect(color ="black", size = 2),
legend.title = element_blank())
As Tjebo points out, the other option is to use NULL instead of an empty string, which does the same thing as theme(legend.title = element_blank())
ggplot(iris) +
geom_point(aes(Sepal.Length, Sepal.Width, color = Species)) +
ggtitle("Sepals ~ Species") +
labs(x = "Length", y = "Width", color = NULL) +
theme(legend.box.background = element_rect(color ="black", size = 2))
You additionally need to change legend.spacing. Very related: Reduce padding in ggplot2 legend
By the way, margin() has as defaults all = 0, so you don't need to type them out... ;)
library(ggplot2)
ggplot(iris)+
geom_point(aes(x=Sepal.Length, y=Sepal.Width, color=Species))+
labs(color=NULL) +
theme(legend.box.background=element_rect(fill="white", color="black"),
legend.margin=margin(),
legend.spacing.y = unit(0, "mm"))
Created on 2022-05-31 by the reprex package (v2.0.1)

How to align text outside of ggplot?

I wanted to make a barplot like the figure a of this one that I found in a publication with something like a table tab showing some information outside the plot using ggplot. I found this general approach of adding text outside of a plot using gridExtra.
However, my question is how to align the height of each row of the table tab to each bar of the barplot so they match?
Here is an example. I wanted to add the note as a table tab on the right of the barplot.
library(ggplot2)
library(gridExtra)
df <- data.frame(Model = c("Datsun 710","Duster 360","Hornet 4 Drive","Hornet
Sportabout","Mazda RX4 Wag","Merc 230","Merc 240D","Valiant"),
logFC = c(1.879,1.552,1.360,1.108,-2.407,-2.416,-2.670,-3.061),
Note = c("ModelA","ModelB","ModelC","ModelD","ModelE","ModelF","ModelG","ModelH"))
plot <- ggplot(df, aes(Model, logFC)) +
geom_bar(stat="identity") +
coord_flip() +
theme_bw() +
ggtitle("Data for cars") +
theme(plot.title = element_text(size = 18, hjust = 0.5))
tab <- as.data.frame(
c(Note = df$Note))
rownames(tab) <- NULL
p_tab <- tableGrob(unname(tab))
grid.arrange(plot, p_tab, ncol = 2)
Per Gregor's comment, this works for me:
plot <- ggplot(df, aes(Model, logFC, label=Note)) +
geom_bar(stat="identity") +
coord_flip(clip = "off") +
theme_bw() +
ggtitle("Data for cars") +
theme(plot.title = element_text(size = 18, hjust = 0.5))+
geom_text(y = 3,
hjust = 0,
size = 5) +
theme(plot.margin = unit(c(1,10,1,1), "lines"),
panel.border=element_blank(),
axis.line = element_line(),
panel.grid.major=element_blank(),
panel.grid.minor = element_blank())

Add text (or annotation) above (or below) the legend of a ggplot barplot with facet

I've literally spend a couple of days searching for the correct answer to this. I want to place additional text/annotation on the outside of the plot area--above or below the legend--on a geom_bar that has nested facets like below.
Some things I've tried:
annotate_custom, and annotate don't work because they add an annotation in every individual facet.
Grid.text worked (I was able to place the text correctly) but I was then unable to use ggsave (which is important) to save the completed plot.
I've also looked at answers that dealt with creating an annotation on a single facet of a multi-faceted plot. Those answers don't help because I want the text/annotation to be outside of the plotting area (above/below the legend) and not in the plotting area itself.
Thanks in advance for your help!
Here are some data:
library(ggplot2)
library(ggh4x) # for the facet_nested function
my.df<-data.frame("top.nest"=sample(c("Group.1","Group.2","Group.3"),26,rep=T),
"bottom.nest"=sample(c("Cat.1","Cat.2","Cat.3","Cat.4","Cat.5"),26,rep=T),
"my.teams"=c(LETTERS),
"quantity"=abs(round(rnorm(26,200,20))),
"my.factor"=sample(c("A","B","C"),26,rep=T))
my.plot<- ggplot(my.df, aes(my.teams, quantity, fill = my.factor)) +
geom_bar(stat = "identity") +
geom_col(width=1) +
scale_fill_manual(values=c("blue","red","green"), labels = c("A","B","C")) +
labs(title = "How do you add text/annotation either below or above the legend",
subtitle="of a facet barplot in ggplot?") +
theme(plot.title = element_text(hjust = 0.5, size=12),
plot.subtitle = element_text(hjust=0.5, size=12)) +
scale_y_continuous(expand = c(0, 0), limits=c(0,250)) +
facet_nested(~top.nest + bottom.nest, scales="free_x", space="free")
I've tried the following suggestion, which doesn't work for me. I added these three lines to the original code.
labs(tag = "XX") +
coord_cartesian(xlim = c(50, 350), ylim = c(10, 35), clip = "off") +
theme(plot.tag.position = c(.01, .95))
Whether I add it before, or after, the facet_nested command, the result is the same (see image below). My hunch is that is has to do with the fact that in the example provided in the first response to this question, the x variable is numeric. Mine is not.
EDIT: Okay, the code now works with the added 3 lines, as long as I remove the xlim=c() part. It works with a ylim=c(), or just remove all limits. It kind of makes sense given that the x-variable was not (as I earlier mentioned) numeric.
Here's the full code, and the successful plot!
my.plot<- ggplot(my.df, aes(my.teams, quantity, fill = my.factor)) +
geom_bar(stat = "identity") +
geom_col(width=1) +
scale_fill_manual(values=c("blue","red","green"), labels = c("A","B","C")) +
labs(title = "Successfully added text/annotation below the legend",
subtitle="of a facet barplot in ggplot") +
theme(plot.title = element_text(hjust = 0.5, size=12),
plot.subtitle = element_text(hjust=0.5, size=12)) +
scale_y_continuous(expand = c(0, 0), limits=c(0,250)) +
facet_nested(~top.nest + bottom.nest, scales="free_x", space="free") +
labs(tag = "XX") +
coord_cartesian(clip = "off") +
theme(plot.tag.position = c(.95, .6))
For completion, here is my final code. On the suggestion of Mr.Flick, I had initially tried using the answer found here. That gave me the second image in my original question above--the one without bars, but with random lines. I am assuming it was because the xlim code was throwing off my plot, given that my x-variable was not numeric. By removing the xlim part, I was able to get what I wanted.
my.plot<- ggplot(my.df, aes(my.teams, quantity, fill = my.factor)) +
geom_bar(stat = "identity") +
geom_col(width=1) +
scale_fill_manual(values=c("blue","red","green"), labels = c("A","B","C")) +
labs(title = "Successfully added text/annotation below the legend",
subtitle="of a facet barplot in ggplot",
tag ="My first tag line\nSecond tag line") +
theme(plot.title = element_text(hjust = 0.5, size=12),
plot.subtitle = element_text(hjust=0.5, size=12),
plot.margin = margin(1, 4, 1, 1, "lines"),
plot.tag.position = c(.91,.6),
plot.tag = element_text(hjust =0, size=9)) +
scale_y_continuous(expand = c(0, 0), limits=c(0,250)) +
facet_nested(~top.nest + bottom.nest, scales="free_x", space="free") +
coord_cartesian(clip = "off")

Moving facet labels that have two lines near the plotting area

I’m working with faceted plots and I’m having an issue trying to move facet labels that have two lines near the plotting area.
Consider the minimal example:
require(ggplot2)
labs <- as_labeller(c(`0` = "LABEL 1",
`1` = "LABEL 2 HAS TWO LINES\nBECAUSE IT'S TOO LONG"))
p <- ggplot(mtcars, aes(disp, drat)) +
geom_point() +
theme_bw() +
geom_hline(yintercept=2, linetype="solid") +
geom_vline(xintercept=50, linetype="solid") +
scale_x_continuous(limits=c(50,500), expand =c(0,0)) +
scale_y_continuous(limits = c(2, 5), expand = c(0,0)) +
theme(panel.border = element_blank(),
strip.background = element_blank(),
strip.text = element_text(size=9),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank()) +
facet_wrap(~am, labeller = labs)
p
Now adding vjust=-0.62 to move the facet labels near the plotting area we have the following:
p + theme(strip.text = element_text(size=9, vjust=-0.62))
As you can see only LABEL 1, the single line label, is moved close to the plotting area - and that’s the problem.
I wished that both labels could have moved. Does anyone have any suggestion?
*Observation: I’m working with a considerable amount of faceted plots so making and customizing plots one-by-one doesn’t seem to be a good idea.
Hope this may helpful for you
p + theme(strip.text = element_text(size=9, vjust=1))

Is there a way to change the spacing between legend items in ggplot2?

Is there a way to change the spacing between legend items in ggplot2? I currently have
legend.position ="top"
which automatically produces a horizontal legend. However, the spacing of the items is very close together and I am wondering how to space them farther apart.
ggplot2 v3.0.0 released in July 2018 has working options to modify legend.spacing.x, legend.spacing.y and legend.text.
Update Dec 2021 - to make legend.spacing.y work, you will need to set byrow = TRUE in the corresponding guide_legend. See also this thread. Example below.
Example: Increase horizontal spacing between legend keys
library(ggplot2)
ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) +
geom_bar() +
coord_flip() +
scale_fill_brewer("Cyl", palette = "Dark2") +
theme_minimal(base_size = 14) +
theme(legend.position = 'top',
legend.spacing.x = unit(1.0, 'cm'))
Note: If you only want to expand the spacing to the right of the legend text, use stringr::str_pad()
Example: Increase vertical spacing (mind byrow = TRUE)
library(ggplot2)
ggplot(mtcars, aes(y = factor(cyl), fill = factor(cyl))) +
geom_bar() +
theme(legend.spacing.y = unit(1.0, 'cm')) +
## important additional element
guides(fill = guide_legend(byrow = TRUE))
Example: Move the legend key labels to the bottom and increase vertical spacing
ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) +
geom_bar() +
coord_flip() +
scale_fill_brewer("Cyl", palette = "Dark2") +
theme_minimal(base_size = 14) +
theme(legend.position = 'top',
legend.spacing.x = unit(1.0, 'cm'),
legend.text = element_text(margin = margin(t = 10))) +
guides(fill = guide_legend(title = "Cyl",
label.position = "bottom",
title.position = "left", title.vjust = 1))
Example: for scale_fill_xxx & guide_colorbar
ggplot(mtcars, aes(mpg, wt)) +
geom_point(aes(fill = hp), pch = I(21), size = 5)+
scale_fill_viridis_c(guide = FALSE) +
theme_classic(base_size = 14) +
theme(legend.position = 'top',
legend.spacing.x = unit(0.5, 'cm'),
legend.text = element_text(margin = margin(t = 10))) +
guides(fill = guide_colorbar(title = "HP",
label.position = "bottom",
title.position = "left", title.vjust = 1,
# draw border around the legend
frame.colour = "black",
barwidth = 15,
barheight = 1.5))
The below is obsolete, but is left for curious people.
For vertical legends, settinglegend.key.size only increases the size of the legend keys, not the vertical space between them
ggplot(mtcars) +
aes(x = cyl, fill = factor(cyl)) +
geom_bar() +
scale_fill_brewer("Cyl", palette = "Dark2") +
theme_minimal(base_size = 14) +
theme(legend.key.size = unit(1, "cm"))
In order to increase the distance between legend keys, modification of the legend-draw.r function is needed. See this issue for more info
# function to increase vertical spacing between legend keys
# #clauswilke
draw_key_polygon3 <- function(data, params, size) {
lwd <- min(data$size, min(size) / 4)
grid::rectGrob(
width = grid::unit(0.6, "npc"),
height = grid::unit(0.6, "npc"),
gp = grid::gpar(
col = data$colour,
fill = alpha(data$fill, data$alpha),
lty = data$linetype,
lwd = lwd * .pt,
linejoin = "mitre"
))
}
### this step is not needed anymore per tjebo's comment below
### see also: https://ggplot2.tidyverse.org/reference/draw_key.html
# register new key drawing function,
# the effect is global & persistent throughout the R session
# GeomBar$draw_key = draw_key_polygon3
ggplot(mtcars) +
aes(x = cyl, fill = factor(cyl)) +
geom_bar(key_glyph = "polygon3") +
scale_fill_brewer("Cyl", palette = "Dark2") +
theme_minimal(base_size = 14) +
theme(legend.key = element_rect(color = NA, fill = NA),
legend.key.size = unit(1.5, "cm")) +
theme(legend.title.align = 0.5)
I think the best option is to use guide_legend within guides:
p + guides(fill=guide_legend(
keywidth=0.1,
keyheight=0.1,
default.unit="inch")
)
Note the use of default.unit , no need to load grid package.
A simple fix that I use to add space in horizontal legends, simply add spaces in the labels (see extract below):
scale_fill_manual(values=c("red","blue","white"),
labels=c("Label of category 1 ",
"Label of category 2 ",
"Label of category 3"))
To add spacing between entries in a legend, adjust the margins of the theme element legend.text.
To add 30pt of space to the right of each legend label (may be useful for a horizontal legend):
p + theme(legend.text = element_text(
margin = margin(r = 30, unit = "pt")))
To add 30pt of space to the left of each legend label (may be useful for a vertical legend):
p + theme(legend.text = element_text(
margin = margin(l = 30, unit = "pt")))
for a ggplot2 object p. The keywords are legend.text and margin.
[Note about edit: When this answer was first posted, there was a bug. The bug has now been fixed]
Now that opts is deprecated in ggplot2 package, function theme should be used instead:
library(grid) # for unit()
... + theme(legend.key.height=unit(3,"line"))
... + theme(legend.key.width=unit(3,"line"))
Looks like the best approach (in 2018) is to use legend.key.size under the theme object. (e.g., see here).
#Set-up:
library(ggplot2)
library(gridExtra)
gp <- ggplot(data = mtcars, aes(mpg, cyl, colour = factor(cyl))) +
geom_point()
This is real easy if you are using theme_bw():
gpbw <- gp + theme_bw()
#Change spacing size:
g1bw <- gpbw + theme(legend.key.size = unit(0, 'lines'))
g2bw <- gpbw + theme(legend.key.size = unit(1.5, 'lines'))
g3bw <- gpbw + theme(legend.key.size = unit(3, 'lines'))
grid.arrange(g1bw,g2bw,g3bw,nrow=3)
However, this doesn't work quite so well otherwise (e.g., if you need the grey background on your legend symbol):
g1 <- gp + theme(legend.key.size = unit(0, 'lines'))
g2 <- gp + theme(legend.key.size = unit(1.5, 'lines'))
g3 <- gp + theme(legend.key.size = unit(3, 'lines'))
grid.arrange(g1,g2,g3,nrow=3)
#Notice that the legend symbol squares get bigger (that's what legend.key.size does).
#Let's [indirectly] "control" that, too:
gp2 <- g3
g4 <- gp2 + theme(legend.key = element_rect(size = 1))
g5 <- gp2 + theme(legend.key = element_rect(size = 3))
g6 <- gp2 + theme(legend.key = element_rect(size = 10))
grid.arrange(g4,g5,g6,nrow=3) #see picture below, left
Notice that white squares begin blocking legend title (and eventually the graph itself if we kept increasing the value).
#This shows you why:
gt <- gp2 + theme(legend.key = element_rect(size = 10,color = 'yellow' ))
I haven't quite found a work-around for fixing the above problem...
Let me know in the comments if you have an idea, and I'll update accordingly!
I wonder if there is some way to re-layer things using $layers...
From Koshke's work on ggplot2 and his blog (Koshke's blog)
... + theme(legend.key.height=unit(3,"line")) # Change 3 to X
... + theme(legend.key.width=unit(3,"line")) # Change 3 to X
Type theme_get() in the console to see other editable legend attributes.
Use any of these
legend.spacing = unit(1,"cm")
legend.spacing.x = unit(1,"cm")
legend.spacing.y = unit(1,"cm")

Resources