Move subtitle to left direction (aligning to y axis area) in ggplot2 - r

Given sample data and ggplot plotting code below:
df <- data.frame(Seller=c("Ad","Rt","Ra","Mo","Ao","Do"),
Avg_Cost=c(5.30,3.72,2.91,2.64,1.17,1.10), Num=c(6:1))
text <- "Real estate agents often refer to a home's curb appeal, the first impression
it makes on potential buyers. As a would-be seller, it's important to take as dispassionate
a look as possible at the outside of your home."
ggplot(df, aes(x=reorder(Seller, Num), y=Avg_Cost)) +
geom_bar(stat='identity') +
coord_flip() +
labs(
title = 'Costs of Selling a Home',
subtitle = stringr::str_wrap(text, 80)
) +
theme(
plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0),
plot.margin = unit(c(0.1, 0, 0, 0), "cm")
)
Result:
I attempt to slightly adjust subtitle to left direction (as the red arrow shows), the final subtitle will be relocated in the area of black rectangle.
I've tried by adjusting values of hjust in plot.subtitle(), but I didn't get the chance of success. How could I achieve that? Thanks.
Reference:
https://r-charts.com/ggplot2/margins/

TBMK there is no easy out-of-the-box solution to achieve your desired result.
One option would be to create your title and subtitle as grid::textGrobs and to add them to your main plot via e.g. patchwork (or annotation_custom or ...) which allows to set the left margin for the subtitle, e.g. in my code below I used a margin of 5 mm on the left.
For the other params of the textGrobs I tried to replicate the default specs of theme_grey for the plot.title and plot.subtitle.
To add the grobs via patchwork you have to wrap them inside wrap_elements.
One drawback of this approach is that you have to set the (relative) height of the main plot (or the annotations) via plot_layout(heights = ...) according to your final plot size. So this requires some fiddling:
library(ggplot2)
library(grid)
library(patchwork)
pmain <- ggplot(df, aes(x = Avg_Cost, y = reorder(Seller, Num))) +
geom_bar(stat = "identity") +
theme(plot.margin = unit(c(0, 0, 0, 0), "cm"))
title <- grid::textGrob(
label = "Costs of Selling a Home", vjust = 1, y = unit(1, "npc"),
gp = gpar(fontsize = 11 * rel(1.2), lineheight = .9)
)
subtitle <- grid::textGrob(
label = stringr::str_wrap(text, 80), vjust = 1, y = unit(1, "npc"),
x = unit(0, "npc") + unit(5, "mm"), hjust = 0,
gp = gpar(fontsize = 11 * rel(1), lineheight = .9)
)
wrap_elements(panel = title) / wrap_elements(plot = subtitle) / pmain + #
plot_layout(heights = c(1, 3, 20)) &
theme(plot.margin = unit(c(.1, 0, 0, 0), "cm"))

Say p is OP's plot, simply do
p +
theme(plot.title.position = "plot")
Result:
Since ggplot2 v3.3.0 (2020-03-05) there are 2 changes that might be of interested here
there is a new theme argument called plot.title.position
no need for coord_flip() anymore, since direction of a plot is deducted from the aesthetic mappings
Relevant for this question is the 1. point.
Below is OP's plot, without coord_flip(), with geom_col() instead of geom_bar(..., stat = "identity") and switch of the aesthetics.
library(ggplot2)
p <- ggplot(df,
aes(x=Avg_Cost,
y=reorder(Seller, Num))
) +
geom_col() +
# geom_bar(stat='identity') +
# coord_flip() +
labs(
title = 'Costs of Selling a Home',
subtitle = stringr::str_wrap(text, 80)
) +
theme(
plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0),
plot.margin = unit(c(0.1, 0, 0, 0), "cm")
)
Reference: https://www.tidyverse.org/blog/2020/03/ggplot2-3-3-0/#grab-bag

You can achieve the expected result using theme in ggplot2.
You just need to change the value of hjust.
plot.subtitle = element_text(hjust = -0.1)
It will give you the output like this:

Related

Unequal horizontal adjustment to title lines in ggplot

I've got a plot with a fairly wide y-axis label, so I want to adjust the title to the left so that it's flush with the label rather than the axis (like this question) but the twist is that I have a multiline title. I've been using hjust, but it adjusts the two lines differently.
For example
ggplot(mtcars,aes(x=wt,y=mpg))+
geom_point()+
ggtitle("Figure: My long and winding title\nthat goes on and on and on") +
ylab("My long label") +
theme_bw() +
theme(plot.title = element_text(size=16, hjust=-.33, color="black", face="bold")) +
theme(axis.title.y = element_text(angle = 0, hjust = 1))
Gives
Is there a way to make the start of the two lines of the title flush after adjusting them horizontally?
You could use the following code. First create the plot and assign it to g, then turn g in to a grob with ggplotGrob. Consequently, manipulate the title left alignment in the layout part of the grob (from 5 to 2). And Finally plot the adapted grob.
g <- ggplot(mtcars,aes(x=wt,y=mpg)) +
geom_point() +
ggtitle("Figure: My long and winding title\nthat goes on and on and on") +
ylab("My long label") +
theme_bw() +
theme(plot.title = element_text(size=16, color="black", face="bold")) +
theme(axis.title.y = element_text(angle = 0, hjust = 1))
grob <- ggplotGrob(g)
# what is the current content
grob$layout$l[grob$layout$name == "title"]
[1] 5
grob$layout$l[grob$layout$name == "title"] <- 2
# plot the new grob
grid::grid.draw(grob)
yielding this plot:
Please let me know whether this is what you want.
Sorry I misunderstood your question.
I think you're just missing a space in the title.
ggtitle("Figure: My long and winding title\n that goes on and on and on")
Here You have it:
library(ggplot2)
library(grid)
library("gridExtra")
p<-ggplot(mtcars,aes(x=wt,y=mpg))+
geom_point()+
ggtitle("") +
ylab("My long label") +
theme_bw() +theme(axis.title.y = element_text(angle = 0, hjust = 1))
title.grob <- textGrob(
label = "Figure: My long and winding title\nthat goes on and on and on",
x = unit(0, "lines"),
y = unit(0, "lines"),
hjust = 0, vjust = 0,
gp = gpar(fontsize = 16))
p1 <- arrangeGrob(p, top = title.grob)
grid.draw(p1)`
hjust is the responsible of the white space.
Removing it remove the 2 white spaces on the second line.
library(ggplot2)
ggplot(mtcars,aes(x=wt,y=mpg))+
geom_point()+
ggtitle("Figure: My long and winding title\nthat goes on and on and on") +
ylab("My long label") +
theme_bw() +
theme(plot.title = element_text(size=16,
color="black", face="bold",
),
axis.title.y = element_text(angle = 0, hjust = 1))
EDIT 1:
If you want to automatically split the title in multiple line, you can use gsub. Here is one example where I split the title after 30 characters. (source)
long_title <- "Figure: My long and winding title that goes on and on and on"
ggplot(mtcars,aes(x=wt,y=mpg))+
geom_point()+
ggtitle(gsub('(.{1,30})(\\s|$)', '\\1\n', long_title)) +
ylab("My long label") +
theme_bw() +
theme(plot.title = element_text(size=16,
color="black", face="bold",
),
axis.title.y = element_text(angle = 0, hjust = 1))
Hope that help !

How can I adjust the vertical spacing of font and axis title in ggplot2? [duplicate]

I want to place the title inside the plot instead at the default top position.
Here is a simple code snippet
library(ggplot2)
df <- data.frame(x = c(1:10), y = rnorm(10, 1, 2))
ggplot(df, aes(x, y))+
geom_line() +
ggtitle("Demo") +
theme(plot.title = element_text(vjust = -3))
In the past I was able to do this by varying vjust value, but now it is not working. Any idea how to do this?
In the ggplot issue "vjust not working in v 2.0 for plot.title?", Hadley writes:
"All text elements now have a margin, which by default scale with the font
size in the theme. This leads to nicer spacing, particularly at large font
sizes. This means hacks with vjust and hjust no longer work. Instead,
use the margin() parameter of element_text()"
Play around with the t and b arguments in margin to adjust the title, e.g.:
ggplot(df, aes(x, y))+
geom_line() +
ggtitle("Demo") +
theme(plot.title = element_text(margin = margin(t = 10, b = -20)))
See ?margin for further arguments.
Note that you should use the margin argument for axis.title.x and axis.title.y as well:
ggplot() + ggtitle("this is title") + xlab("this is x") + ylab("this is y") +
theme(plot.title = element_text(margin = margin(b = -10)),
axis.title.x = element_text(margin = margin(t = -10)),
axis.title.y = element_text(margin = margin(r = -10)))

Adjust title vertically to inside the plot - vjust not working

I want to place the title inside the plot instead at the default top position.
Here is a simple code snippet
library(ggplot2)
df <- data.frame(x = c(1:10), y = rnorm(10, 1, 2))
ggplot(df, aes(x, y))+
geom_line() +
ggtitle("Demo") +
theme(plot.title = element_text(vjust = -3))
In the past I was able to do this by varying vjust value, but now it is not working. Any idea how to do this?
In the ggplot issue "vjust not working in v 2.0 for plot.title?", Hadley writes:
"All text elements now have a margin, which by default scale with the font
size in the theme. This leads to nicer spacing, particularly at large font
sizes. This means hacks with vjust and hjust no longer work. Instead,
use the margin() parameter of element_text()"
Play around with the t and b arguments in margin to adjust the title, e.g.:
ggplot(df, aes(x, y))+
geom_line() +
ggtitle("Demo") +
theme(plot.title = element_text(margin = margin(t = 10, b = -20)))
See ?margin for further arguments.
Note that you should use the margin argument for axis.title.x and axis.title.y as well:
ggplot() + ggtitle("this is title") + xlab("this is x") + ylab("this is y") +
theme(plot.title = element_text(margin = margin(b = -10)),
axis.title.x = element_text(margin = margin(t = -10)),
axis.title.y = element_text(margin = margin(r = -10)))

ggplot2, change title size

I would like to have my main title and axis title have the same font size as the annotated text in my plot.
i used theme_get() and found that text size is 12, so I did that in my theme statement - this did not work. I also tried to send the relative size to 1, and this did not work
I was hoping someone could please help me.
Code is below
library(ggplot2)
library(gridExtra) #to set up plot grid
library(stringr) #string formatting functions
library(plyr) #rbind.fill function
library(reshape2) #transformation of tables
dat<-data.frame(
est=c(2.75,2.95,2.86,2.99),
ucl=c(2.92,3.23,3.38,4.91),
lcl=c(2.24,1.67,2.44,1.82),
ord=c(1,2,1,2)
)
dat$varname<-c('Estimate','Predictive','Estimate','Predictive')
dat$grp<-c('Cobalt','Cobalt','Chromium','Chromium')
for (i in unique(dat$grp)) {
dat <- rbind.fill(dat, data.frame(grp = i, ord=0,
stringsAsFactors = F))
}
dat$grp_combo <- factor(paste(dat$grp, dat$ord, sep = ", "))
dat$grpN <- as.numeric(dat$grp_combo)
rng <- c(0,6)
scale.rng <-1
xstart=-(max(dat$grpN)+2)
xend=4
ThemeMain<-theme(legend.position = "none", plot.margin = unit(c(0,0,0, 0), "npc"),
panel.margin = unit(c(0,0, 0, 0), "npc"),
title =element_text(size=12, face='bold'),
axis.text.y = element_blank(),
axis.text.x = element_text(color='black'),
axis.ticks.y = element_blank(),
axis.title.x = element_text(size=12,color='black',face='bold')
)
BlankSettings <- theme(legend.position = "none",
title =element_text(size=12, face='bold'),
plot.margin = unit(c(0,0, 0, 0), "npc"),
panel.margin = unit(c(0,0, 0, 0), "npc"),
axis.text.x = element_text(color='white'),
axis.text.y = element_blank(),
axis.ticks.x = element_line(color = "white"),
axis.ticks.y=element_blank(),
axis.title.x = element_text(size=12,color='white',face='bold'),
panel.grid = element_blank(),panel.grid.major = element_blank(),panel.background = element_blank()
)
pd <- position_dodge(width = 0.7)
#######################################################################################################
#MAIN PLOT
#######################################################################################################
mainPart<-
ggplot(dat, aes(x=-grpN,y=est, ymin=lcl, ymax=ucl, group=1)) +
scale_y_continuous(name=NULL, breaks=seq(rng[1], rng[2], scale.rng), limits=c(rng[1], rng[2]), expand=c(0,0)) +
ylab('Ion Concentration') +
ggtitle('Mean with 95% HDI')+
#geom_segment(aes(x=xstart, xend=0, y=0, yend=0), linetype=3, alpha=0.01) +
geom_linerange(aes(linetype="1"),position=pd) +
geom_point(aes(shape="1"), fill="white",position=pd) +
coord_flip() +
scale_x_continuous(limits=c(xstart,xend), expand=c(0,0))+xlab(NULL)+
ThemeMain
#######################################################################################################
#varnameS
#######################################################################################################
# ystart & yend are arbitrary. [0, 1] is
# convinient for setting relative coordinates of
# columns
ystart = 0
yend = 1
p1 <-
ggplot(dat, aes(x = -varnameN, y = 0)) +
coord_flip() +
scale_y_continuous(limits = c(ystart, yend)) +
BlankSettings+
scale_x_continuous(limits = c(xstart, xend), expand = c(0, 0)) +
xlab(NULL) +
ylab('') +
ggtitle('')
studyList<-
p1 +
with(unique(dat[is.na(dat$varname),c("grpN","grp")]), annotate("text",label=grp, x=-grpN,y=0, fontface='bold', hjust=0)) + #Variable Group varnames
with(dat[!is.na(dat$var),],annotate("text",label=varname,x=-grpN,y=0.04, hjust=0)) #Variables
#######################################################################################################
#EFFECTS
#######################################################################################################
f<-function(x) round(x,2)
dat$msmt<-paste(f(dat$est),' [',f(dat$lcl),', ',f(dat$ucl),']',sep='')
effectSizes<-p1+
annotate("text",x=-dat$grpN, y=0.25,label=ifelse(is.na(dat$varname)==T,'',dat$msmt))
grid.arrange(ggplotGrob(studyList), ggplotGrob(mainPart),
ggplotGrob(effectSizes), ncol = 3, widths = unit(c(0.19,
0.4, 0.41), "npc"))
+ theme(plot.title = element_text(size=22))
Here is the full set of things you can change in element_text:
element_text(family = NULL, face = NULL, colour = NULL, size = NULL,
hjust = NULL, vjust = NULL, angle = NULL, lineheight = NULL,
color = NULL)
Using the theme with plot.title is the easiest way. But there is another option using ggtext which provides Markdown (element_markdown) and HTML rendering for ggplot2. So you can use HTML tags to change the "font-size" of the title for example. The advantage of this is that you can change parts of title easily, so you can give certain parts of the title a different font-size or colors using HTML tags.
Here is a reproducible example:
library(ggplot2)
library(ggtext)
# font size using html
ggplot(mtcars, aes(x = cyl)) +
geom_bar() +
ggtitle("<span style='font-size: 22pt;'>This is bigger text</font>") +
theme(plot.title = element_markdown())
# Change parts of title font size
ggplot(mtcars, aes(x = cyl)) +
geom_bar() +
ggtitle("<span style='font-size: 22pt;'>This is bigger text,</font>
<span style='font-size: 10pt;'>This is smaller text.</font>") +
theme(plot.title = element_markdown())
Created on 2022-08-24 with reprex v2.0.2
As you can see, the title has two different font-sizes. For extra information and examples you can check the link above.

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