Skip to content

view facet label text #4979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bergsmat opened this issue Sep 6, 2022 · 3 comments · Fixed by #5924
Closed

view facet label text #4979

bergsmat opened this issue Sep 6, 2022 · 3 comments · Fixed by #5924
Labels
facets 💎 feature a feature request or enhancement

Comments

@bergsmat
Copy link

bergsmat commented Sep 6, 2022

labeller() and related functionality give huge flexibility for controlling strips, thanks! But I've been asked to write an extension that programmatically modifies strip text font size in the event that a strip has many lines. At present it is hard to examine strip text in advance. Apparently labeller() is called once during build_strip() and the result is immediately rendered as grobs. Stealing ideas from https://github.com/yjunechoe/ggtrace I can do invisible(ggplotGrob(x)) while running trace() on build_strip(), but the latter is not exported so there is going to be a package check warning that will probably keep the solution from ever posting on CRAN.

I completely understand if you do not want to export build_strip(). Is there an alternative way, existing or otherwise (feature request), to view the facet label text of a ggplot object before rendering?

debug(ggplot2:::build_strip) # labels near line 12
ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_grid(cyl ~ am + gear)
@teunbrand
Copy link
Collaborator

You can intercept the text rendering at the element drawing stage. You can wrap element_text() with a class of your own, make an S3 method for element_grob.your_class() and in there, tweak text rendering as you see fit. Example below:

library(ggplot2)
library(rlang)

# Subclass element_text
element_text_refit <- function(...) {
  elem <- element_text(...)
  class(elem) <- c("element_text_refit", class(elem))
  elem
}

# S3 method for `element_grob()`
element_grob.element_text_refit <- function(
    element, label = "", size = NULL, ...
) {
  n_lines <- stringr::str_count(label, pattern = "\n") + 1
  size <- rep_len(size %||% element$size, length(n_lines)) / n_lines
  class(element) <- setdiff(class(element), "element_text_refit")
  element_grob(element, label = label, size = size, ...)
}

# Example data with various number of lines on the strip
df <- iris
levels(df$Species) <- c("Setosa", "Iris\nversicolor", "The plant\nIris\nvirginica")

ggplot(df, aes(Sepal.Width, Sepal.Length)) +
  geom_point() +
  facet_wrap(~ Species) +
  theme(
    strip.text = element_text_refit(size = 36)
  )

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

@bergsmat
Copy link
Author

bergsmat commented Sep 7, 2022

@teunbrand Very nice, thanks! I was able to implement your solution and it does indeed avoid reliance on non-exported functions. I have the remaining problem, though, that element-wise rescaling does not let me accommodate the case where one margin has multiple facets, such as the column facets in the example below (trivial here, but a real problem for certain facet_wrap cases). Viewing the labels in totality (as seen in build_strip() allows me to anticipate -- not just that individual strips have multiple lines, but also -- that there may be multiple strips on a margin. Perhaps facet_grid(cyl ~ paste(sep = '\n', am, gear)) but that feels strained. Suggestions welcome.

library(ggplot2)
ggplot(mtcars, aes(wt, mpg)) + 
  geom_point() + 
  facet_grid(
    cyl ~ am + gear, 
    labeller = label_both
  ) +
  theme_bw()

Created on 2022-09-06 with reprex v2.0.2

@teunbrand
Copy link
Collaborator

So here is an unsupported method how you could wrangle the facet text from a build ggplot2 object:

library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 4.2.2
p <- ggplot(mtcars, aes(wt, mpg)) + 
  geom_point() + 
  facet_grid(
    cyl ~ am + gear, 
    labeller = label_both
  ) +
  theme_bw()

b <- ggplot_build(p)

layout <- b$layout$layout
col_vars <- unique(layout[names(b$layout$facet_params$cols)])
row_vars <- unique(layout[names(b$layout$facet_params$rows)])

b$layout$facet_params$labeller(col_vars)
#> [[1]]
#> [1] "am: 0" "am: 0" "am: 1" "am: 1"
#> 
#> [[2]]
#> [1] "gear: 3" "gear: 4" "gear: 4" "gear: 5"
b$layout$facet_params$labeller(row_vars)
#> [[1]]
#> [1] "cyl: 4" "cyl: 6" "cyl: 8"

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

A question that deserves some considering is whether ggplot2 should provide programmatic access to strip labels. The use case presented here is one thing, but another consideration is that it might make it easier for packages like BrailleR to describe a plot.

@teunbrand teunbrand added feature a feature request or enhancement facets 💎 labels Jan 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
facets 💎 feature a feature request or enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants