1

How could one color the legend key labels with R ggplot2 and hide the keys themselves?

library(ggplot2)

ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) + 
geom_point()

In a way that the legend contains only the text labels 4, 6 and 8 colored in red, green and blue respectively.

u31889
  • 331
  • 1
  • 9
  • Add `scale_color_manual(values = c('4'='red','6'='green','8'='blue'))` to your code! – Duck Aug 13 '20 at 10:55
  • @Duck I think the OP wants the text labels to be shown in a certain colour, so for example that the actual label `4` is printed in red. – Allan Cameron Aug 13 '20 at 10:57
  • @Allan Cameron Yes, exactly (that's why I added 'and remove to key' to avoid confusion) – u31889 Aug 13 '20 at 11:02
  • [this](https://stackoverflow.com/questions/49965758/change-geom-texts-default-a-legend-to-label-string-itself/49966784#49966784) suggest another similar way ... it actually updates the legend key with the values you want. Then you could suppress the labels instead. – user20650 Aug 13 '20 at 18:47

3 Answers3

3

There's no way to do this directly using theme, since element_text won't take vectorized input. Probably easiest to fake it by turning clipping off and plotting some text where the legend should be:

library(ggplot2)

ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) + 
geom_point() +
  geom_text(data = data.frame(wt = c(6, 6, 6, 6), mpg = c(20, 22.5, 25, 27.5),
                              cyl = c(levels(factor(mtcars$cyl)), "cyl"),
                              colour = c(levels(factor(mtcars$cyl)), "cyl")),
            aes(label = colour)) +
  coord_cartesian(xlim = c(1.5, 5.5), clip = "off") +
  scale_color_manual(values = c("blue", "green", "red", "black")) +
  theme(legend.position = "none",
        plot.margin = margin(10, 50, 10, 10))

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
2

Allan's answer is also excellent, but here is a more automated way of doing it by making your own custom guide function.

library(ggplot2)

guide_textcolourguide <- function(...) {
  guide <- guide_legend(...)
  class(guide) <- c("guide", "textcolourguide", "legend")
  guide
}

guide_gengrob.textcolourguide <- function(guide, theme) {
  legend <- NextMethod()

  # Figure out what are keys and labels
  keys <- grep("^key(?!.*bg)", legend$layout$name, perl = TRUE)
  labels <- grep("^label", legend$layout$name)
  
  # Recolour the labels based on keys, assumes parallel ordering
  newlabels <- mapply(function(key, lab) {
    colour <- legend$grobs[[key]]$gp$col
    lab <- legend$grobs[[lab]]
    lab$children[[1]]$children[[1]]$gp$col <- colour
    return(lab)
  }, key = keys, lab = labels, SIMPLIFY = FALSE)
  
  # Replace labels
  legend$grobs[labels] <- newlabels
  
  # Purge keys
  gtable::gtable_filter(legend, "key", invert = TRUE)
}


ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) + 
  geom_point() +
  scale_colour_discrete(guide = "textcolourguide")

Created on 2020-08-13 by the reprex package (v0.3.0)

teunbrand
  • 33,645
  • 4
  • 37
  • 63
  • Works great as well ! – u31889 Aug 13 '20 at 11:30
  • This works excellent in R 4.0.3. But in R 4.1.1 I get the following error message: Error in get: object 'guide_textcolourguide' of mode 'function' was not found. Any hope that this can be repaired? I liked this solution so much. – WJH Dec 08 '21 at 22:30
  • I'm unsure what is causing that error, but I think it should be possible to do `guide = guide_textcolourguide()`. If that doesn't work, I've also wrapped this up into something similar [here](https://teunbrand.github.io/ggh4x/reference/guide_stringlegend.html). – teunbrand Dec 09 '21 at 00:53
  • Wonderful, the ggh4x package with 'guide = "stringlegend' in scale_color_manual solved it! Just one more question, is it somehow possible to keep the keys and have both keys and labels colored? – WJH Dec 10 '21 at 21:34
  • Definitely not with `ggh4x::guide_stringlegend()`, but I suppose someone could hack together a solution for this. – teunbrand Dec 10 '21 at 21:58
0

You could also use ggtext, by renaming the cyl variable and using element_markdown for the legend. There might be more elegant ways to make sure the factor levels match... but it is a way to programmatically provide the colors to the legend.

library(ggplot2)
library(dplyr)
library(ggtext)

setColors <- function(x, col="red") 
    paste0("<span style = 'color:", col, ";'>", x, "</span>")

mycols <- setNames(colorspace::rainbow_hcl(length(unique(mtcars$cyl))), 
                   unique(sort(mtcars$cyl)))

mtcars %>% 
    mutate(cyl = factor(setColors(cyl, col= mycols[as.character(cyl)]), 
                        unique(setColors(cyl, col = mycols[as.character(cyl)]))[
                            order(unique(cyl))])) %>% 
    ggplot(aes(wt, mpg, colour = cyl)) + 
    geom_point() +
    theme(legend.text = element_markdown()) +
    scale_color_manual(values = unname(mycols))

Created on 2020-08-13 by the reprex package (v0.3.0)

user12728748
  • 8,106
  • 2
  • 9
  • 14