mat
mat

Reputation: 2617

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)

Upvotes: 3

Views: 8390

Answers (2)

stefan
stefan

Reputation: 124863

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)

Upvotes: 3

Allan Cameron
Allan Cameron

Reputation: 174128

Yes it's (just about) possible:

gg <- 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(aes(alpha = "Grade", yintercept = 13), linetype = 2,
             color = green, size = 1, key_glyph = "pointrange") +
  geom_point(aes(student, grade, shape = "Grade"), color = blue, size = 4) +
  geom_point(aes(student, gradeHat), color = green, size = 4)  +
  scale_y_continuous(breaks = 0:20, limits = c(0, 20)) +
  scale_shape_manual(values = 19, name = NULL, guide = guide_legend(order = 1)) +
  scale_color_manual(values = redblood, name = NULL, 
                     guide = guide_legend(order = 3)) +
  scale_alpha_manual(breaks = "Grade", values = 1,
                    guide = guide_legend(order = 2,
                                         override.aes = list(color = green)),
                    name = "") +
  scale_fill_manual(values = redblood, name = NULL) +
  coord_fixed(.15) +
  theme_classic() +
  theme(legend.position = "bottom",
        legend.key.width = unit(20, "points")) 

plot(gg)

enter image description here

Upvotes: 2

Related Questions