Home > Software engineering >  R: Discrete axis labels with multiple colors in ggplot2
R: Discrete axis labels with multiple colors in ggplot2

Time:08-10

I am creating a figure using ggplot2 and I need the axis labels to be different colors. Is this possible to do with ggplot2 only (no other pkgs) AND is officially supported by ggplot2. (This is code that will be added to a package, and I can't add another dependency, and nervous to implement something that is not officially supported in case it breaks!)

This post (customize ggplot2 axis labels with different colors) has a couple of solutions for this problem. But each of them have an issue for my use case: 1. Using vectorized colors results in a scary warning from ggplot2 about this not being officially supported (method implemented below), or 2. They require the ggtext package.

Thank you!

library(ggplot2)

mtcars |> 
  dplyr::mutate(
    color = 
      c("BLUE", "RED") |> rep_len(dplyr::n()) |> 
      factor()
  ) |> 
  ggplot(aes(x = mpg, y = color))  
  geom_point()  
  theme(axis.text.y.left = element_text(color = c("blue", "red")))  
  labs(y = "")
#> Warning: Vectorized input to `element_text()` is not officially supported.
#> Results may be unexpected or may change in future versions of ggplot2.

Created on 2022-08-09 by the reprex package (v2.0.1)

CodePudding user response:

The vectorised input may not be supported by element_text(), but ggplot2 does support custom theme elements (ie, element_markdown()). Hence, you can write your own custom theme elements that just handles the vectorisation of the colour variable. Now, you don't have a scary warning, don't depend on ggtext, but do have the maintainer's responsibility to ensure this keeps working. If you're worried that the good folks of ggplot2 might change element_text() or element_grob.element_text(), you can write out your class' methods in full, instead of relying that the ggplot2 classes stay the same into perpetuity.

library(ggplot2)

element_text2 <- function(..., color = NULL) {
  # Explicitly don't pass colour
  # Note: user can still pass `colour`, but I'm not here to write perfect code,
  # just to give a working example
  elem <- element_text(...) 
  elem$colour <- color # Assign after element is constructed
  class(elem) <- c("element_text2", "element_text", "element") # Re-class
  elem
}

# S3 Method for your custom class' drawing code
element_grob.element_text2 <- function(element, label = "", ...,
                                       colour = NULL) {
  # Repeat colour to match length of label, if colour exists
  if (length(colour)) {
    colour <- rep_len(colour, length(label))
  }
  # Re-class to old class
  class(element) <- c("element_text", "element")
  # Call element_grob.element_text method
  element_grob(element, label = label, ..., colour = colour)
}

mtcars |> 
  dplyr::mutate(
    color = 
      c("BLUE", "RED") |> rep_len(dplyr::n()) |> 
      factor()
  ) |> 
  ggplot(aes(x = mpg, y = color))  
  geom_point()  
  theme(axis.text.y.left = element_text2(color = c("blue", "red")))  
  labs(y = "")

Created on 2022-08-09 by the reprex package (v2.0.1)

  • Related