Coloured text in {ggplot2}: {ggtext} vs {marquee}

An alternative to a traditional legend is using coloured text in a subtitle. In {ggplot2}, we can do this using the {ggtext} package. You can also do it using the new {marquee} package. How do they compare?

June 4, 2024

When you use colour to denote the values of a variable in a visualisation, it’s very common to add a legend showing how the colours map to different values. If you create your charts using {ggplot2}, a legend is added automatically when you add colour or fill within the aesthetic mapping.

One problem with these legends is that they take up a lot of space - space where we could be plotting data instead! An alternative to using a traditional legend, is using coloured text within a subtitle or annotation.

Note: this approach works well when the colours are used for categorical data, but not so well for continuous colour scales.

As an example, let’s go back to our favourite animal dataset. No, not penguins - though they are a close second! We’re looking at lemur data from Duke Lemur Center. The data was used as a #TidyTuesday dataset back in 2021 so we can load it in from the CSV file on GitHub:

1
lemurs <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-08-24/lemur_data.csv")

Alternatively, you could load it using the {tidytuesdayR} R package.

I’ve used this data several times for teaching, and in a previous blog post about using both R and Python in Quarto documents.

Let’s do a quick bit of data wrangling using {dplyr}, by filtering to look at only adult, collared brown lemurs, keeping only the columns we need (weight, age, and sex), renaming thos columns with slightly nicer looking variable names, and recoding the "M" and "F" values in Sex as "male" and "female":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
library(dplyr)
lemur_data <- lemurs |>
  filter(
    taxon == "ECOL",
    age_category == "adult"
  ) |>
  select(c(age_at_wt_mo, weight_g, sex)) |>
  rename(
    Age = age_at_wt_mo,
    Weight = weight_g,
    Sex = sex
  ) |>
  mutate(
    Sex = case_when(
      Sex == "M" ~ "male",
      Sex == "F" ~ "female",
    )
  )
Click here for a bonus lemur photo!

Photo of lemur sitting on the grass looking surprised
Image:unsplash.com/photos/gray-and-white-cat-on-brown-floor-6OFUMVoRdq4

Now we can create a simple scatter plot using geom_point() in {ggplot2}, by mapping Age onto the x axis, Weight onto the y axis, and colouring the points based on Sex:

1
2
3
4
library(ggplot2)
base_plot <- ggplot(data = lemur_data) +
  geom_point(
  mapping = aes(x = Age, y = Weight, colour = Sex))

Let’s also add a title and subtitle:

1
2
3
4
5
base_plot +
  labs(
    title = "How much does a collared brown lemur weigh?",
    subtitle = "This scatter plot shows how the weights of male and female lemurs change with age."
  )

Scatter plot of weight against height of lemurs coloured by female and male lemurs

This looks fine. But let’s use some nicer colours, and use coloured text instead of the legend.

Coloured text with {ggtext}

I mentioned in a recent R for the Rest of Us podacst how I used to use R for the first 95% of my plots but then added annotations and edits using other tools because it was easier. I also mentioned how that’s no longer the case - I now make (almost) 100% of my R visualations in R, and that’s largely down to the {ggtext} package. The {ggtext} package provides simple Markdown and HTML rendering for text in {ggplot2}.

Let’s start by defining a named vector of colours for male and female lemurs:

1
2
3
4
col_vec <- c(
  "male" = "#0E7C7B",
  "female" = "#922D50"
)

For more complicated examples you can, of course, use something like case_when() to map colours to values to save you from typing out the values.

Now we need to pass these colours into the subtitle text. Let’s create a variable that contains the text, called ggtext_st. We use HTML code (and a tiny bit of CSS) to format the text with colours. If you’re not familiar with HTML code, an example might look like this:

1
"<span style='color:red'>Text to appear goes here</span>"

We wrap all of the text we want to appear inside <span></span> tags. Inside the style option, we pass in CSS options such as color.

We could manually pass in the text we want to appear in the subtitle:

1
2
3
4
ggtext_st <- "This scatter plot shows how the weights of
  <span style='color:#0E7C7B'>male</span> and
  <span style='color:#922D50'>female</span> lemurs change with age."
)

However, to make sure that the colours and variables are correctly mapped in the text, we can use the {glue} package to pass in the variables from col_vec:

1
2
3
4
5
ggtext_st <- glue::glue(
  "This scatter plot shows how the weights of
  <span style='color:{col_vec[[1]]}'>{names(col_vec)[[1]]}</span> and
  <span style='color:{col_vec[[2]]}'>{names(col_vec)[[2]]}</span> lemurs change with age."
)

We can add the colours to our plot using scale_colour_manual(), and pass our new subtitle into the labs() function:

1
2
3
4
5
6
7
8
9
new_plot <- base_plot +
  scale_colour_manual(
    values = col_vec
  ) +
  labs(
    title = "How much does a collared brown lemur weigh?",
    subtitle = ggtext_st
  )
new_plot

Scatter plot of weight against height of lemurs coloured by female and male lemurs with coloured text in legend

That doesn’t look quite right…

That’s because we haven’t actually used the {ggtext} package yet! We need to tell {ggplot2} that we’re using HTML code in the subtitle. We do this using the theme() function and setting the plot.subtitle argument to element_textbox_simple(). You can also use element_textbox if you prefer. Another nice feature of element_textbox_simple() is that long text is automatically wrapped to the width of the plot rather than running off the end! We can also remove the existing legend at the same time:

1
2
3
4
5
6
library(ggtext)
new_plot +
  theme(
    legend.position = "none",
    plot.subtitle = element_textbox_simple()
  )

Scatter plot of weight against height of lemurs coloured by female and male lemurs with coloured text in legend

I hope you’ll agree that this looks much cleaner and tidier!

See also geom_richtext() or geom_textbox() as alternatives to geom_text().

Coloured text with {marquee}

The recently released {marquee} package provides a markdown parser and renderer for the R graphics engine - meaning it can be used to render more complex markdown text in {ggplot2} graphics.

{marquee} is built for the future so you do need the lastest version of R Graphics engines for it to work.

To add coloured text with Markdown format, we wrap the text to appear in curly brackets. After the opening curly bracket, add a . and the colour you want to the text to appear:

1
"{.red Text to appear goes here}"

As in the previous example with {ggtext}, we can use glue() to bring the colours and the labels into the subtitle text string. Since the markdown formatting is written inside curly brackets { and }, the glue() function might get confused about which brackets it’s supposed to be using and complain. To avoid this, we’ll tell glue() to use [ and ] instead.

1
2
3
4
5
marquee_st <- glue::glue(
  "This scatter plot shows how the weights of {.[col_vec[[1]]] [names(col_vec)[[1]]]} and {.[col_vec[[2]]] [names(col_vec)[[2]]]} lemurs change with age.",
  .open = "[",
  .close = "]"
)

You can alternatively use marquee_glue() ( see marquee.r-lib.org/reference/marquee_glue.html, to allow you to use curly brackets for both markdown syntax and glue. I find nested curly brackets harder to read, but that’s just a personal preference and you could use either glue::glue() or marquee::marquee_glue().

Much like {ggtext}, we need to tell {ggplot2} that we’re using {marquee} to format text, by passing element_marquee() to the plot.subtitle argument in theme():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
library(marquee)
base_plot +
  scale_colour_manual(
    values = col_vec
  ) +
  labs(
    title = "How much does a collared brown lemur weigh?",
    subtitle = marquee_st
  ) +
  theme(
    legend.position = "none",
    plot.subtitle = element_marquee(width = 1)
  )

Scatter plot of weight against height of lemurs coloured by female and male lemurs with coloured text in legend

The text wrapping using {marquee} isn’t automatic, but you can set width = 1 to wrap the text.

See also geom_marquee() as an alternative to geom_text().

Comparing {ggtext} and {marquee}

You can see that though {ggtext} and {marquee} use HTML vs Markdown, respectively, the way you add coloured text isn’t too different - the process is similar. If all you’re doing is adding coloured text, there isn’t much of a reason to choose one over the other - unless you have a strong preference for HTML or Markdown syntax. One benefit of {marquee} is that it doesn’t just work with {ggplot2} - you can also use it with other graphics packages in R. You can read more about {marquee} in the blog post by Thomas Lin Pedersen.

I, for one, am very excited about all of the new text features coming to R through {marquee} - it brings a lot more than just coloured text in subtitles. But {ggtext} still has a special place for me as the package that changed how easy it is to go from the start to finish of a visualisation using only R.


For attribution, please cite this work as:

Coloured text in {ggplot2}: {ggtext} vs {marquee}.
Nicola Rennie. June 4, 2024.
nrennie.rbind.io/blog/coloured-text-legend-ggplot-ggtext-marquee

Licence: creativecommons.org/licenses/by/4.0