vinchinzu
vinchinzu

Reputation: 614

How to add multiple captions in ggplot2 outside of the main graph area

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

Upvotes: 8

Views: 8126

Answers (3)

User143534045
User143534045

Reputation: 11

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

Upvotes: 1

tjebo
tjebo

Reputation: 23767

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)

Upvotes: 2

James Allison
James Allison

Reputation: 181

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

Upvotes: 18

Related Questions