Search code examples
rggplot2legendggtext

R: add custom legend to ggplot


I have the following plot:

enter image description here

And would like to add a legend as follows: enter image description here

This is the code I used to generate the plot:

library(data.table)
library(ggplot2)

blue <- "#4472C4"
green <- "#548235"
red <- "#C55A11"
redblood <- "#C00000"

DT <- data.table(student = c("Jane", "Sam", "Tim", "Kate", "Claire"),
                 grade = c(10, 14, 8, 9, 19))

b0 <- 13

DT[, gradeHat := b0]
DT[, e := grade - gradeHat]
DT[, SS := sum(e**2)]

DT[, id := 1:nrow(DT)]
DT[, xmin := id]
DT[, xmax := id + abs(e)/20*3]
DT[, ymin := min(grade, gradeHat), id]
DT[, ymax := max(grade, gradeHat), id]
DT[, student := factor(student, levels = student)]

gg <- ggplot(DT) +
  geom_segment(aes(x = student, xend = student, y = grade, yend = gradeHat),
               color = redblood, size = 1.3) +
  geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
            fill = redblood, alpha = .4) +
  geom_hline(yintercept = b0, color = green, alpha = .7, size = 1, linetype = "dashed") +
  geom_point(aes(student, grade), color = blue, size = 4) +
  geom_point(aes(student, gradeHat), color = green, size = 4) +
  scale_y_continuous(breaks = 0:20, limits = c(0, 20)) +
  coord_fixed(.15) +
  theme_classic()

plot(gg)

Solution

  • Not a full solution for your problem ... but the best I could come up with at the moment:

    1. To get a legend in ggplot you have to map on aesthetics, i.e. instead of setting colors as arguments you have to map on color or fill inside aes(). To this end you can make use of placeholders or labels, e.g. fill="SS" in geom_rect.

    2. To get the colors right you can make use of scale_color/fill_manual. Using the labels makes it easy to assign the right colors.

    3. Next, to get the styling of the legend right you can make use of guide_legend and its argument override.aes to set the shapes and linetypes for the color legend.

    4. By default there is some spacing between the color and fill legend, which however can be removed in theme() via legend.spacing and legend.margin.

    5. Final part is to color the legend labels. This could be achieved via the ggtext package which via theme option legend.text = element_markdown() allows to style the legend entries using HTML and CSS.

    Unfortunately I was not able to figure out how to get the wide hat on your gradeHat.

    library(data.table)
    library(ggplot2)
    
    blue <- "#4472C4"
    green <- "#548235"
    red <- "#C55A11"
    redblood <- "#C00000"
    
    DT <- data.table(student = c("Jane", "Sam", "Tim", "Kate", "Claire"),
                     grade = c(10, 14, 8, 9, 19))
    
    b0 <- 13
    
    DT[, gradeHat := b0]
    DT[, e := grade - gradeHat]
    DT[, SS := sum(e**2)]
    
    DT[, id := 1:nrow(DT)]
    DT[, xmin := id]
    DT[, xmax := id + abs(e)/20*3]
    DT[, ymin := min(grade, gradeHat), id]
    DT[, ymax := max(grade, gradeHat), id]
    DT[, student := factor(student, levels = student)]
    
    ggplot(DT) +
      geom_segment(aes(x = student, xend = student, y = grade, yend = gradeHat, color = "error"),
                   , size = 1.3) +
      geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = "SS"), alpha = .4) +
      geom_hline(yintercept = b0, color = green, alpha = .7, size = 1, linetype = "dashed") +
      geom_point(aes(student, grade, color = "grade"), size = 4) +
      geom_point(aes(student, gradeHat, color = "gradeHat"), size = 4) +
      scale_color_manual(breaks = c("grade", "gradeHat", "error"), 
                         values = c(grade = blue, gradeHat = green, error = red),
                         labels = c(grade = glue::glue("<span style = 'color: {blue};'>grade</span>"),
                                    gradeHat = glue::glue("<span style = 'color: {green};'>gradeHat</span>"), 
                                    error = glue::glue("<span style = 'color: {red};'>error</span>"))) +
      scale_fill_manual(values = c(SS = redblood), labels = c(SS = glue::glue("<span style = 'color: #C0000066; '>SS</span>"))) +
      guides(color = guide_legend(order = 1, override.aes = list(shape = c(16, 16, NA), linetype = c("blank", "dashed", "solid")))) +
      scale_y_continuous(breaks = 0:20, limits = c(0, 20)) +
      coord_fixed(.15) +
      theme_classic() +
      theme(legend.position = "bottom", 
            legend.spacing = unit(0, "pt"), 
            legend.margin = margin(r = 0, l = 0),
            legend.text = ggtext::element_markdown()) +
      labs(color = NULL, fill = NULL)