Search code examples
rggplot2facet-wrapfacet-grid

How do I wrap facet labels in ggplot while retaining the variable name?


I am plotting a graph in ggplot with multiple facets. I want to wrap the facet labels and I know I can do this with the label_wrap_gen function, but this obscures the variable name and only shows the value. How can I wrap the label while still retaining the facet variable?

library(ggplot2)

# Create example data
df <- data.frame(x = rnorm(100), y = rnorm(100), 
     a = sample(c("Group 1", "Group 2"), 50, replace = TRUE),
     b = sample(c("Group 1", "Group 2"), 50, replace = TRUE))

label_wrap_gen wraps the labels:

# Create plot with facet wrap and custom labeller
ggplot(df, aes(x, y)) +
  geom_point() +
  facet_grid(a~b, labeller = label_wrap_gen(10))

label_both retains the variable names:

# Create plot with facet wrap and custom labeller
ggplot(df, aes(x, y)) +
  geom_point() +
  facet_grid(a~b, labeller = label_both)

Is there a solution that accomplishes both?


Solution

  • I think there are two easy options: modify the labels so that they include the variable name; or craft a new function that combines the utility of label_both and label_wrap_gen.

    I'll change the width= from 10 to 6 so that we see the effects.

    Unchanged:

    ggplot(df, aes(x, y)) +
      geom_point() +
      facet_grid(a~b, labeller = label_both)
    

    ggplot2 original, both but not wrapped

    Option 1: change the faceting variables

    This is perhaps the simplest:

    transform(df, a = paste("a:", a), b = paste("b:", b)) |>
      ggplot(aes(x, y)) +
      geom_point() +
      facet_grid(a~b, labeller = label_wrap_gen(6))
    

    ggplot2, wrapped labels with both name and value

    I used transform, one can use $<- or dplyr::mutate or one of several other ways to do it. This can be generalized so that it can be done programmatically, if needed. When doing something like this, I really prefer to do it inline as above, and not changing the data at-rest in df; doing the latter risks me applying them twice and/or having them present in post-plot renders (more plots, tables) where the combination is not desired. Doing this inline is a little "safer" in that regard.

    Option 2: custom function

    label_wrap_gen_both <- function(width = 25, multi_line = TRUE, sep = ": ") {
      fun <- function(labels) {
        value <- label_value(labels, multi_line = multi_line)
        variable <- ggplot2:::label_variable(labels, multi_line = multi_line)
        if (multi_line) {
            out <- vector("list", length(value))
            for (i in seq_along(out)) {
                out[[i]] <- paste(variable[[i]], value[[i]], sep = sep)
            }
        }
        else {
            value <- inject(paste(!!!value, sep = ", "))
            variable <- inject(paste(!!!variable, sep = ", "))
            out <- Map(paste, variable, value, sep = sep)
            out <- list(unname(unlist(out)))
        }
        lapply(out, function(st)
          vapply(strwrap(st, width = width, simplify = FALSE), paste, character(1), collapse = "\n"))
      }
      structure(fun, class = "labeller")
    }
    
    ggplot(df, aes(x, y)) +
      geom_point() +
      facet_grid(a~b, labeller = label_wrap_gen_both(6))
    

    (same plot as above)