ChatGPT解决这个技术问题 Extra ChatGPT

How to add a ggplot2 subtitle with different size and colour?

I'm using ggplot2 to improve precipitation barplots.

Here's a reproducible example of what I want to achieve:

library(ggplot2)
library(gridExtra)
secu <- seq(1, 16, by=2)
melt.d <- data.frame(y=secu, x=LETTERS[1:8])
m <- ggplot(melt.d, aes(x=x, y=y)) +
  geom_bar(fill="darkblue") + 
  labs(x="Weather    stations", y="Accumulated Rainfall [mm]") +
  opts(axis.text.x=theme_text(angle=-45, hjust=0, vjust=1),
       title=expression("Rainfall"), plot.margin = unit(c(1.5, 1, 1, 1), "cm"),
       plot.title = theme_text(size = 25, face = "bold", colour = "black", vjust = 5))
z <- arrangeGrob(m, sub = textGrob("Location", x = 0, hjust = -3.5, vjust = -33, gp = gpar(fontsize = 18, col = "gray40"))) #Or guessing x and y with just option
z

I don't know how to avoid using guessing numbers on hjust and vjust on ggplot2? Is there a better way to put a subtitle (not just using \n, but a subtitle with different text color and size)?

I need to be able to use with ggsave to have a pdf file.

Here are two related questions:

Add a footnote citation outside of plot area in R?

How can I add a subtitle and change the font size of ggplot plots in R?

Thanks for any help.

The vjust=-33 worked for me on Linux. I know that sub is meant to go below the plot, but it was the only way I got what I wanted.
for some reason this makes my plot really small and creates a huge space below the graph
@hrbrmstr s answer seems to be the way to go nowadays

t
theforestecologist

The latest ggplot2 builds (i.e., 2.1.0.9000 or newer) have subtitles and below-plot captions as built-in functionality. That means you can do this:

library(ggplot2) # 2.1.0.9000+ 

secu <- seq(1, 16, by=2)
melt.d <- data.frame(y=secu, x=LETTERS[1:8])

m <-  ggplot(melt.d, aes(x=x, y=y))
m <- m + geom_bar(fill="darkblue", stat="identity")
m <- m + labs(x="Weather    stations", 
              y="Accumulated Rainfall [mm]",
              title="Rainfall",
              subtitle="Location")
m <- m + theme(axis.text.x=element_text(angle=-45, hjust=0, vjust=1)) 
m <- m + theme(plot.title=element_text(size=25, hjust=0.5, face="bold", colour="maroon", vjust=-1))
m <- m + theme(plot.subtitle=element_text(size=18, hjust=0.5, face="italic", color="black"))
m

Error in (function (el, elname) : "plot.subtitle" is not a valid theme element name.
Please read answers: " latest ggplot2 builds (i.e., 2.1.0.9000 or newer)"
t
theforestecologist

Ignore this answer ggplot2 version 2.2.0 has title and subtitle functionality. See @hrbrmstr's answer below.

You could use nested atop functions inside an expression to get different sizes.

EDIT Updated code for ggplot2 0.9.3

m <-  ggplot(melt.d, aes(x=x, y=y)) + 
     geom_bar(fill="darkblue", stat = "identity") + 
     labs(x="Weather    stations", y="Accumulated Rainfall [mm]") + 
     ggtitle(expression(atop("Rainfall", atop(italic("Location"), "")))) +
     theme(axis.text.x = element_text(angle=-45, hjust=0, vjust=1), 
     #plot.margin = unit(c(1.5, 1, 1, 1), "cm"), 
     plot.title = element_text(size = 25, face = "bold", colour = "black", vjust = -1))

https://i.stack.imgur.com/Ape84.png


Hi, this is an amazing solution. I would like to use it but instead of the atop(italic("Location") I would like to have an object: atop(italic(my_string_vector). I tried that but then the subtitle evaluated to (my_string_vector). How to force this expression to use the string value and do not treat the provided text literally?
in case you are having troubles using variables you should use bquote instead of expression, see here
@Konrad To use objects, replace expression with bquote and wrap the objects with .(), like this, for a main title stored in an object called "main.title" and for a subtitle stored in an object called "sub.title": ggtitle(bquote(atop(.(main.title), atop(italic(.(sub.title)), "")))) Credit goes to Didzis Elferts's answer here: stackoverflow.com/questions/19957536/…
b
baptiste

it's not too hard to add grobs to the gtable and make a fancy title that way,

library(ggplot2)
library(grid)
library(gridExtra)
library(magrittr)
library(gtable)

p <- ggplot() + 
  theme(plot.margin = unit(c(0.5, 1, 1, 1), "cm"))

lg <- list(textGrob("Rainfall", x=0, hjust=0, 
                    gp = gpar(fontsize=24, fontfamily="Skia", face=2, col="turquoise4")),
               textGrob("location", x=0, hjust=0, 
                        gp = gpar(fontsize=14, fontfamily="Zapfino", fontface=3, col="violetred1")),
           pointsGrob(pch=21, gp=gpar(col=NA, cex=0.5,fill="steelblue")))

margin <- unit(0.2, "line")
tg <- arrangeGrob(grobs=lg, layout_matrix=matrix(c(1,2,3,3), ncol=2),
                  widths = unit.c(grobWidth(lg[[1]]), unit(1,"null")),
                  heights = do.call(unit.c, lapply(lg[c(1,2)], grobHeight)) + margin)

grid.newpage()
ggplotGrob(p) %>%
  gtable_add_rows(sum(tg$heights), 0) %>%
  gtable_add_grob(grobs=tg, t = 1, l = 4)  %>%
  grid.draw()

https://i.stack.imgur.com/ozDSD.png


A
Aren Cambre

It appears opts is deprecated as of ggplot 2 0.9.1 and no longer functional. This worked for me with the latest versions as of today: + ggtitle(expression(atop("Top line", atop(italic("2nd line"), "")))).


This also works without the final , "" - what is that part for?
Beats me. I may have copied an example I saw somewhere else.
Probably from @SandyMuspratt's answer above :P - I understand it now, atop() is something like a fraction without bars. So putting the second atop() inside the first gives you a sub-fraction, with text proportionally smaller. The "" is the bottom of the sub-fraction. It appears to be unnecessary though - perhaps atop() has a default empty string for the second parameter, or something.
It appears @SandyMuspratt's answer was modified after I posted my answer to reflect code similar to mine. :-)
S
Sandy Muspratt

This version uses a gtable function. It allows two lines of text in the title. The text, size, colour, and font face of each line can be set independently of the other. However, the function will modify a plot with a single plot panel only.

Minor edit: Updating to ggplot2 v2.0.0

# The original plot
library(ggplot2)

secu <- seq(1, 16, by = 2)
melt.d <- data.frame(y = secu, x = LETTERS[1:8])

m <- ggplot(melt.d, aes(x = x, y = y)) + 
     geom_bar(fill="darkblue", stat = "identity") + 
     labs(x = "Weather    stations", y = "Accumulated Rainfall [mm]") + 
     theme(axis.text.x = element_text(angle = -45, hjust = 0, vjust = 1))


# The function to set text, size, colour, and face
plot.title = function(plot = NULL, text.1 = NULL, text.2 = NULL, 
   size.1 = 12,  size.2 = 12,
   col.1 = "black", col.2 = "black", 
   face.1 = "plain",  face.2 = "plain") {

library(gtable)
library(grid)

gt = ggplotGrob(plot)

text.grob1 = textGrob(text.1, y = unit(.45, "npc"), 
   gp = gpar(fontsize = size.1, col = col.1, fontface = face.1))
text.grob2 = textGrob(text.2,  y = unit(.65, "npc"), 
   gp = gpar(fontsize = size.2, col = col.2, fontface = face.2))

text = matrix(list(text.grob1, text.grob2), nrow = 2)
text = gtable_matrix(name = "title", grobs = text, 
   widths = unit(1, "null"), 
   heights = unit.c(unit(1.1, "grobheight", text.grob1) + unit(0.5, "lines"), unit(1.1,  "grobheight", text.grob2) + unit(0.5, "lines")))

gt = gtable_add_grob(gt, text, t = 2, l = 4)
gt$heights[2] = sum(text$heights)

class(gt) =  c("Title", class(gt))

gt
}

# A print method for the plot
print.Title <- function(x) {
   grid.newpage()   
   grid.draw(x)
}


# Try it out - modify the original plot
p = plot.title(m, "Rainfall", "Location", 
   size.1 = 20, size.2 = 15, 
   col.1 = "red", col.2 = "blue", 
   face.2 = "italic")

p

https://i.stack.imgur.com/E7Jk1.png


b
baptiste

You could use wrap the plot in grid.arrange and pass a custom grid-based title,

https://i.stack.imgur.com/ixXoL.png

library(ggplot2)
library(gridExtra)

p <- ggplot() + 
  theme(plot.margin = unit(c(0.5, 1, 1, 1), "cm"))

tg <- grobTree(textGrob("Rainfall", y=1, vjust=1, gp = gpar(fontsize=25, face=2, col="black")),
               textGrob("location", y=0, vjust=0, gp = gpar(fontsize=12, face=3, col="grey50")),
               cl="titlegrob")
heightDetails.titlegrob <- function(x) do.call(sum,lapply(x$children, grobHeight))

grid.arrange(p, top = tg)

Current versions of ggplot use theme and and element_text instead of opts and theme_text respectively. Also, the ggplotGrob approach seems to fail in the current version of gridExtra (gridExtra_0.8.1) and ggplot2 (ggplot2_0.9.3.1)
N
Nathan

You might have noticed that Sandy's code doesn't produce a bold title for "Rainfall" - the instruction to make this bold should occur within the atop() function rather than the theme() function.

ggplot(melt.d, aes(x=x, y=y)) + 
 geom_bar(fill="darkblue", stat = "identity") + 
 labs(x="Weather    stations", y="Accumulated Rainfall [mm]") + 
 ggtitle(expression(atop(bold("Rainfall"), atop(italic("Location"), "")))) +
 theme(axis.text.x = element_text(angle=-45, hjust=0, vjust=1),
 plot.title = element_text(size = 25, colour = "black", vjust = -1))

https://i.stack.imgur.com/yUflw.png