8

I want to add two captions for the footer. But it seems that ggplot will only take 1. Is there a workaround to add an annotation or geom_text to the bottom left and right hand corners.

library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y = mpg)) + 
   geom_point()

p + labs(caption = "left footer") +  theme(plot.caption=element_text(hjust=0))


p + labs(caption = "right footer") +  theme(plot.caption=element_text(hjust=1))

p + labs(caption = "right footer") +  theme(plot.caption=element_text(hjust=1)) + 
    labs(caption = "left footer") +  theme(plot.caption=element_text(hjust=0))
vinchinzu
  • 604
  • 1
  • 7
  • 16

3 Answers3

14

I'm very late to the party here but I also had the same issue.

A solution to your question would be to place both the captions at the same time and treat them as vectors:

p + labs(caption = c("right footer", "left footer")) + 
theme(plot.caption = element_text(hjust=c(1, 0)))
James Allison
  • 141
  • 2
  • 4
  • 1
    Great solution, should be the accepted answer. Also works for multiple captions (>2). +1 – Mr.Rlover Feb 03 '21 at 14:49
  • It raises a warning message: `Vectorized input to element_text() is not officially supported.` – ah bon Jan 06 '22 at 05:31
  • I was looking for a solution for multiple captions. I did get the same warning, but it still gave me the desired effect. This answer works. – EastBeast Mar 25 '22 at 20:59
1

Not sure if this was the case at the time of the question but, now, you can just put \n in your caption:

(your_ggplot_object) + labs(caption = 'Line 1 \n Line 2')
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • thanks for this answer. It is helpful for multiple captions one below the other. The question title did not specify this further, therefore it is valid, but the OP desired to have both right and left aligned. – tjebo Jun 12 '22 at 07:40
  • I also would recommend to use no spaces before and after `\n` - although it might be easier to read for us, it messes with the alignment and the layout is more difficult to control – tjebo Jun 12 '22 at 07:41
0

This is essentially a "annotate outside the plot area" question and a very common problem when creating plots. Here a bunch of approaches - I am showing annotation as a caption left and right aligned, but most of the given solutions can be flexibly expanded for any desired annotation effect. I have not included the solution with \n as I find it too imprecise and too much of a hack, but it can be found as a solution in this thread too.

This summarises solutions from:

library(ggplot2)

Specify two captions with two hjust in theme

p <- ggplot() 

## James Allison's solution from this thread here
## gives a warning which you can ignore. 
## I'd say my preferred solution for short captions where the 
## aim is to position them left and right. 
## Does not need y limits
p + labs(caption = c("right footer", "left footer")) + 
  theme(plot.caption = element_text(hjust=c(1, 0)))
#> Warning: Vectorized input to `element_text()` is not officially supported.
#> Results may be unexpected or may change in future versions of ggplot2.

annotate outside the plot

## You can use annotate(geom = "text") or geom_text
## This solution is very powerful and flexible and you will be getting very 
## far with it. In fact you can answer a lot of problems on stackoverflow with 
## this custom annotation

## if you use geom_text, make a data frame first
## Requires y limits relative to data 
## I like to create the y semi-programmatically with a constant sort of expansion factor which needs to be hard coded 
## I prefer to put hard coded values only in one place, therefore I define y outside, here
## as a function because I will need a slightly different value below
my_y <- function(fac){min(mtcars$disp)- fac*diff(range(mtcars$disp))}

textframe <- data.frame(x = c(-Inf, Inf),   y = my_y(.3),
  labels = c("right footer", "left footer"))

p +
  ## requires a y limit, therefore you need to add a y aesthetic 
  ## (you will have this in most cases)
  geom_point(data = mtcars, aes(mpg, disp)) +
  geom_text(data = textframe, aes(x, y, label = labels), hjust = c(0,1)) +
  ## you need to manually specify the y limits
  ## here 0.05 because this is the default expansion
  ## you will also need to modify coordinate clipping 
  coord_cartesian(ylim = c(my_y(.05), NA), clip = 'off') +
  ##  usually you need to add a plot margin, play around
  theme(plot.margin = margin(b = 30, 5,5,5, unit = 'pt'))

## same effect with a call to annotate, also requires y limits
p + geom_point(data = mtcars, aes(mpg, disp)) +
annotate(geom = "text", x = c(-Inf, Inf), y = my_y(.3), 
         label = c("right footer", "left footer"),
         ## you need to add the hjust accordingly 
         hjust = c(0,1)) +
coord_cartesian(ylim = c(my_y(.05), NA), clip = 'off') +
theme(plot.margin = margin(b = 30, 5,5,5, unit = 'pt'))

Adding text grobs

## Baptiste's solution with gridExtra, 
## an extremely powerful solution, but it requires some grid knowledge and you won't need it in most cases
## does not need y limits
library(gridExtra)
library(grid)
fthbar <- grobTree(rectGrob(gp=gpar(fill="grey50",col=NA)),
                   textGrob("right footer", x=0, hjust=0), 
                   textGrob("left footer", x=1, hjust=1, gp=gpar(col="white")), cl="ann")
heightDetails.ann <- function(x) unit(1,"line")
grid.arrange(p, bottom = fthbar)

stitch an annotation plot to the first plot

## other plot combining packages, e.g. patchwork or cowplot. 
## I find patchwork super easy to use. You will need three plots
## requires y limits, but does not need to be relative to data
## here using the text frame from above
library(patchwork)
p2 <-
p + 
  ## using geom_blank because I don't wanna drawn anything
  geom_blank(data = mtcars, aes(mpg, disp)) +
  geom_text(
  data = textframe, aes(x , y, label = labels),
  hjust = c(0, 1),
  vjust = 0
) +
  theme_void()


p/p2 + plot_layout(heights = c(1, 0.1))

Created on 2022-06-12 by the reprex package (v2.0.1)

tjebo
  • 21,977
  • 7
  • 58
  • 94