ggplot2 icon indicating copy to clipboard operation
ggplot2 copied to clipboard

[Feature request] Let `element_text()` change text values

Open wurli opened this issue 2 years ago • 2 comments

This would allow a user to supply a function to element_text() which would be applied to the text itself.

Hear me out. Often, to make plots slightly prettier, labels are transformed from data values (e.g. Sepal.Length, setosa) to more human-readable versions (e.g. Sepal Length, Setosa). This often follows a few simple rules, e.g. spaces instead of underscores, appropriate capitalisation etc. Currently there's two main ways to do achieve this:

  1. Use labs() if variable names are the problem. This isn't easy to do programmatically and can be annoying, especially if you just want to do something simple like switch underscores for spaces
  2. Pre-format the data (e.g. using dplyr::mutate()) if data values are the problem. I think this is slightly easier, but is still not ideal.

I think being able to do this in one step via element_text(), perhaps via a transform argument, would be really nice - although it feels like it would somewhat break with the pattern of element_ functions only changing formatting. Here's an example of how it might be used:

library(dplyr, warn.conflicts = FALSE)
library(stringr)

# Without `transform` argument:
iris |> 
  mutate(Species = stringr::str_to_title(Species)) |> 
  ggplot(aes(Sepal.Length, Sepal.Width, colour = Species)) +
  geom_point() +
  labs(
    x = "Sepal Length",
    y = "Sepal Width"
  )

# With `transform` argument:
ggplot(iris, aes(Sepal.Length, Sepal.Width, colour = Species)) +
  geom_point() +
  theme(
    text = element_text(
      transform = \(x) x |> str_replace_all("\\.", " ") |> str_to_title()
    )
  )

This would also be really nice for defining custom theme_ functions.

Possibly something that should just have its own package, but my view is that it would be useful and general enough to justify inclusion in ggplot2 itself.

wurli avatar Aug 10 '22 15:08 wurli

It is not too difficult to just build this on top of element_text() if you want.

library(ggplot2)
library(stringr)

element_text_transform <- function(..., transform = identity) {
  elem <- element_text(...)
  elem$transform <- rlang::as_function(transform)
  class(elem) <- c("element_text_transform", class(elem))
  elem
}

element_grob.element_text_transform <- function(element, label = "", ...) {
  label <- element$transform(label)
  NextMethod()
}

ggplot(iris, aes(Sepal.Width, Sepal.Length, colour = Species)) +
  geom_point() +
  theme(
    legend.text = element_text_transform(
      transform = ~ str_replace_all(.x, "\\.", " ") |> str_to_title()
    )
  )

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

teunbrand avatar Aug 14 '22 14:08 teunbrand

It is not too difficult to just build this on top of element_text() if you want.

library(ggplot2)
library(stringr)

element_text_transform <- function(..., transform = identity) {
  elem <- element_text(...)
  elem$transform <- rlang::as_function(transform)
  class(elem) <- c("element_text_transform", class(elem))
  elem
}

element_grob.element_text_transform <- function(element, label = "", ...) {
  label <- element$transform(label)
  NextMethod()
}

ggplot(iris, aes(Sepal.Width, Sepal.Length, colour = Species)) +
  geom_point() +
  theme(
    legend.text = element_text_transform(
      transform = ~ str_replace_all(.x, "\\.", " ") |> str_to_title()
    )
  )

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

Very nice! This does require some knowledge of {ggplot2}’s inner workings though. But nice to know it can be done so simply :)

wurli avatar Aug 14 '22 17:08 wurli

Hi Jacob, giving this issue some thoughts more recently; I think this feature might better fit an extension package rather than ggplot2 itself. The example I gave earlier should be a reasonable template for implementing an element_text() extension.

Even though it is unlikely to be implemented in ggplot2, I would like to thank you for suggesting the feature and hope you reach out again if you have other ideas for features.

teunbrand avatar Jan 07 '23 16:01 teunbrand

This is a very useful trick! A quirk of this method is that it seems to only work for concrete fields like legend.text or axis.title.x, not for abstract fields like axis.title.

I would also put in an argument to reconsider adding this feature to the standard interface. As is, you have to manually specify labels for them to look good, and this is what most programmer do in my experience. Sure, this takes some time. But the much larger problem is that you can change what is being plotted and forget to change the labels. This can easily lead to creating incorrectly labeled plots, which is of course a serious problem. Having an easy way to automatically reformat variable names would avoid this situation.

fredcallaway avatar Apr 28 '23 12:04 fredcallaway