ggplot function to add text just below legend

2019-06-24 09:02发布

In R I want to make a function which takes an ggplot object and some text and returns and ggplot object by adds text just below the legend (in the right side of the plot, while keeping legend on the right side).

myplot = ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) + 
           geom_line()

I want to add text "mean of Sepal.Width = 3.05" (and a box around it) just after the legend. I looked at related questions but they change the position of legend to bottom and do not work as a function rather prints the plot.

标签: r plot ggplot2
2条回答
Fickle 薄情
2楼-- · 2019-06-24 09:09

A couple of possibilities.

The first uses annotate(), and involves positioning the text by trial and error. The x position is adjusted using hjust, the y position is selected to be a little below the legend. Note: no border around the text.

The second assumes a border is required. The combined text and box is constructed using grid. Then the grob is positioned using annotation_custom(). ymin and ymax are set to be a little below the legend. xmin and xmax are set by trial and error to get the text and the legend to align.

Both methods involve plotting outside the plot panel, so clipping to the plot panel needs to be turned off. But if the text size or length changes, the position of the label needs to be adjusted.

The third method is reasonably robust to changes to text length and size. Similar to method 2, the combined text and box grob is constructed using grid. Then, using gtable functions, the grob is attached to the legend (to the middle column of the legend).

# 1.
library(ggplot2)
library(grid)
library(gtable)

# The label
label = "Mean of Sepal.Width = 3.05"

# The plot - Note the extra margin space for the label
myplot = ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) + 
    geom_line() +   
    annotate("text", x = Inf, y = 2.9, label = label, hjust = -0.08, size = 3) +
    theme(plot.margin = unit(c(.5,6,.5,.5),"lines"),
          legend.background = element_rect(colour = "black"))

# Turn off clipping to the plot panel
g = ggplotGrob(myplot)
g$layout$clip[g$layout$name == "panel"] = "off"
grid.draw(g)


# 2.
# Construct the label grob - a combination of text and box
textgrob = textGrob(label, gp = gpar(cex = .75), )
width = unit(1, "grobwidth",textgrob) + unit(10, "points")
height = unit(1, "grobheight", textgrob)+ unit(10, "points")
rectgrob = rectGrob(gp=gpar(colour = "black", fill = NA), height = height, width = width)
labelGrob = gTree("labelGrob", children = gList(rectgrob, textgrob))

# The plot - Note the extra margin space for the label
myplot = ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) + 
    geom_line() +   
    annotation_custom(labelGrob,  
       xmin = 1.137*max(iris$Sepal.Length), xmax = 1.137*max(iris$Sepal.Length), 
       ymin = 2.9, ymax = 2.9) +
    theme(plot.margin = unit(c(0.5, 6, 0.5, 0.5), "lines"),
          legend.background = element_rect(colour = "black"))

# Turn off clipping to the plot panel
g = ggplotGrob(myplot)
g$layout$clip[g$layout$name == "panel"] = "off"
grid.draw(g)




#3.
# The label
label = "Mean of\nSepal.Width = 3.05"
# Try a different label
# label = "a"

# The plot
myplot = ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) + 
    geom_line() +   
    theme(legend.background = element_rect(colour = "black"))

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

# Construct the label grob 
xpos = 5
textgrob = textGrob(x = unit(xpos, "points"), label, gp = gpar(cex = .75), just = "left")
width = unit(1, "grobwidth",textgrob) + unit(2*xpos, "points")  # twice the x position
height = unit(1, "grobheight", textgrob)+ unit(2*xpos, "points")
rectgrob = rectGrob(x = unit(0, "points"), just = "left", 
    gp = gpar(colour = "black", fill = NA), height = height, width = width)
labelGrob = gTree("labelGrob", children = gList(rectgrob, textgrob))

# Add the label grob to a new row added to the legend
pos = subset(leg$layout, grepl("guides", name), t:r)

leg = gtable_add_rows(leg, height, pos = pos$t+1)
leg = gtable_add_grob(leg, labelGrob, t = pos$t+2, l = pos$l)

# Adjust the middle width of the legend to be the maximum of the original width 
# or the width of the grob
leg$widths[pos$l] = max(width, leg$widths[pos$l])

# Add some space between the two parts of the legend
leg$heights[pos$t+1] = unit(5, "pt")

# Return the modified legend to the origial plot
g$grobs[[which(g$layout$name == "guide-box")]] = leg

# Adjust the width of the column containing the legend to be the maximum 
# of the original width or the width of the label
g$widths[g$layout[grepl("guide-box", g$layout$name), "l"]] = max(width, sum(leg$widths))

# Draw the plot
grid.newpage()
grid.draw(g)

enter image description here

查看更多
看我几分像从前
3楼-- · 2019-06-24 09:26

Another easy possibility is to use a caption:

myplot = ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) + 
           geom_line() +
           labs(caption = "Mean of Sepal.Width = 3.05")

It's not really right below the legend, though:

Output

查看更多
登录 后发表回答