12
votes

I am trying to increase the space between legend title and labels in ggplot2 but haven’t had any luck applying all sorts of possible solutions I could find on the web. As you can see in the reproducible example below the title text is too close to the top number. I would like to avoid the clunky solution (#6 below) of manually adding a line brake (\n) as this doesn’t allow to tweak the spacing size and a complete line is too much in my case.

I need to use a colourbar. I am aware that vjust normally takes values between 0 and 1 but I am using a value of 2 below to easier detect changes.

library(reshape2)
library(ggplot2)

# Generate test data frame
df=reshape2::melt(outer(1:4, 1:4), varnames = c("X1", "X2"))

# Declare theme
mytheme=theme_classic(base_size=15) + 
  theme(axis.title.x=element_blank(),axis.title.y=element_blank(),
        axis.text.x=element_blank(),axis.text.y=element_blank(),
        axis.ticks=element_blank()) + 
  theme(legend.position=c(0,1), legend.justification=c(0,1),
        legend.title=element_text(size="12",face = "bold"))

# Plot
p=ggplot(data=df, aes_string(x="X1", y="X2")) +
  geom_tile(aes(fill=value))+
  scale_fill_gradient(low="yellow",high="red",guide="colourbar",name="Titleggplot") +
  annotate("text",x=Inf,y=Inf,label="(a)" ,hjust=1.5, vjust=1.5, size=6) +
  mytheme
p

#*** Things I tried (building on the defaults above) that do not work

# 1 - set "vjust" in theme
mytheme=mytheme+theme(legend.title=element_text(size="12",face = "bold",vjust=2))
p=p+mytheme
p
# Result: does nothing

# 2 - set "legend.title.align" in theme
mytheme=mytheme+theme(legend.title.align=4)
p=p+mytheme
p
# Result: adjusts horizontal position but does not change vertical position

# 3 - increase margins around title object
mytheme=mytheme+theme(legend.title=element_text(margin=margin(0,0,20,0),size="12",face="bold"))
p=p+mytheme
p
# Result: does nothing

# 4 - using "guide" in scale_fill_gradient
p=ggplot(data=df, aes_string(x="X1", y="X2")) +
  geom_tile(aes(fill=value))+
  scale_fill_gradient(low="yellow",high="red",guide=guide_colorbar(title="Titleggplot",title.vjust=2)) +
  annotate("text",x=Inf,y=Inf,label="(a)" ,hjust=1.5, vjust=1.5, size=6) +
  mytheme
p
# Result: does nothing

# 5 - using "guides" as separate element
p=p+guides(fill=guide_legend(title.vjust=2))
# Restult: does nothing

# 6 - I could manually add a line break (\n) to the title
p=ggplot(data=df, aes_string(x="X1", y="X2")) +
  geom_tile(aes(fill=value))+
  scale_fill_gradient(low="yellow",high="red",guide="colourbar",name="Titleggplot\n") +
  annotate("text",x=Inf,y=Inf,label="(a)" ,hjust=1.5, vjust=1.5, size=6) +
  mytheme
p
# Result: increases the space but I can't smoothly adjust the spacing and an entire blank line is in my case too much.
1
I thought defining a margin in element_text might work, but it doesn't. Possibly a bug?Roland
@Roland good suggestion! I also tried this before without success but forgot to include extending the margins in my options above - I added this now.Raphael
There are at least two github requests related to this issue (specifying margins in the legend). Recently, the main author of the package said this was not a high priority. Perhaps you might comment on one of them there.lmo
@lmo good to know. It is my hope that the ggplot2 authors will increase the priority of solving this issue if the StackOverflow community expresses their concern here?Raphael
Not sure how often they stop by SO.lmo

1 Answers

5
votes

Digging into the grob structure of the legend, it is possible to replace the original legend title with one that has an upper and lower margin. See comments in the code below.

library(reshape2)
library(ggplot2)


# Generate test data frame
df=reshape2::melt(outer(1:4, 1:4), varnames = c("X1", "X2"))

# Declare theme
mytheme=theme_classic(base_size=15) + 
  theme(axis.title.x=element_blank(),axis.title.y=element_blank(),
        axis.text.x=element_blank(),axis.text.y=element_blank(),
        axis.ticks=element_blank()) + 
  theme(legend.position=c(0,1), legend.justification=c(0,1),
        legend.title=element_text(size="12",face = "bold"))

# Plot
p=ggplot(data=df, aes_string(x="X1", y="X2")) +
  geom_tile(aes(fill=value))+
  scale_fill_gradient(low="yellow",high="red",guide="colourbar",name="Titleggplot") +
  annotate("text",x=Inf,y=Inf,label="(a)" ,hjust=1.5, vjust=1.5, size=6) +
  mytheme
p

# Function to set upper and lower margins to legend title

TitleMargins = function(plot, Tmargin = unit(0, "mm"), Bmargin = unit(0, "mm")) { 
 library(gtable)
 library(grid)

 # Get the plot grob
 g = ggplotGrob(plot)

 # Get the legend
 index = which(g$layout$name == "guide-box")
 leg = g$grobs[[index]][[1]][[1]]

 # Get the legend title 
 title = leg$grobs[[4]]

 # Set up the heights: for the two margins and the original title
 heights <- unit.c(Tmargin, unit(1, "grobheight", title), Bmargin)

 # Set up a column of three viewports
 vp <- viewport(layout = grid.layout(3, 1,
                   heights = heights), name = "vp1")

 # The middle row, where the title text will appear, is named as 'child_vp'.
 child_vp <- viewport(layout.pos.row = 2, clip = "off", name = "child_vp")

 # Put the title into a gTree containing one grob (the title) and the three viewports
 TitleText <- gTree(children = gList(title),
                   vp = vpTree(vp, vpList(child_vp)))

 # Back to the legend: Set height for row 2 of legend to new height of TitleText
 leg$heights[2] = sum(heights)

 # Add the new TitleText grob to row 2 of legend
 leg <- gtable_add_grob(leg, TitleText, 
               t = 2, l = 2, r = 5, name = "TitleText")

 # Remove the original title
 leg$grobs <- leg$grobs[-4]
 leg$layout <- leg$layout[-4, ]

 # Put the legend back into the plot
 g$grobs[[index]][[1]][[1]] = leg

 class(g) =  c("TitleMargins", class(g))

 g

 }

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


# Try it out 
# Set your legend title margins
Tmargin = unit(0, "mm")
Bmargin = unit(3, "mm")

# Apply the function
TitleMargins(p, Tmargin, Bmargin)

enter image description here