Skip to content
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

Inappropriate scoping of after_scale variables when resolving legend data #6264

Open
yjunechoe opened this issue Dec 30, 2024 · 1 comment · May be fixed by #6267
Open

Inappropriate scoping of after_scale variables when resolving legend data #6264

yjunechoe opened this issue Dec 30, 2024 · 1 comment · May be fixed by #6267

Comments

@yjunechoe
Copy link
Contributor

yjunechoe commented Dec 30, 2024

The bug concerns a case that's seemingly specific to stage(start, after_scale), when the after_scale expression uses a variable from the layer's after-scale data which is unavailable from the guide data.

As a preliminary, we know that after scale expressions are ordinarily resolved against both the guide data (first data printed by trace) and the layer data (second data printed by trace).

library(ggplot2)
trace("use_defaults", tracer = quote(print(head(data))), where = Geom)
#> Tracing function "use_defaults" in package ".GlobalEnv"
#> [1] "use_defaults"
invisible(ggplot_build(
  ggplot(mtcars, aes(am)) +
    geom_bar(aes(fill = as.factor(cyl)))
))
#> Tracing use_defaults(..., self = self) on entry 
#>      fill .id
#> 1 #F8766D   1
#> 2 #00BA38   2
#> 3 #619CFF   3
#> Tracing use_defaults(..., self = self) on entry 
#>      fill  y count      prop x flipped_aes PANEL group ymin ymax  xmin xmax
#> 1 #F8766D 19     3 0.2727273 0       FALSE     1     1   16   19 -0.45 0.45
#> 2 #F8766D 13     8 0.7272727 1       FALSE     1     1    5   13  0.55 1.45
#> 3 #00BA38 16     4 0.5714286 0       FALSE     1     2   12   16 -0.45 0.45
#> 4 #00BA38  5     3 0.4285714 1       FALSE     1     2    2    5  0.55 1.45
#> 5 #619CFF 12    12 0.8571429 0       FALSE     1     3    0   12 -0.45 0.45
#> 6 #619CFF  2     2 0.1428571 1       FALSE     1     3    0    2  0.55 1.45
untrace("use_defaults", where = Geom)
#> Untracing function "use_defaults" in package ".GlobalEnv"

One can use a variable from the after scale data like prop to remap to the fill aesthetic using stage(), like so. Note that the example turns the legend off for the layer to demonstrate the expected output:

ggplot(mtcars, aes(am)) +
  geom_bar(
    aes(
      fill = stage(
        start = as.factor(cyl),
        after_scale = alpha(fill, prop)
      )
    ),
    show.legend = FALSE
  )

Image

Now, with the legend turned back on, the plot errors, presumably because while prop exists in the after-scale data, it does not exist in the guides data.

p <- ggplot(mtcars, aes(am)) +
  geom_bar(
    aes(
      fill = stage(
        start = as.factor(cyl),
        after_scale = alpha(fill, prop)
      )
    ),
    show.legend = TRUE # or `= NA`
  )
p
#> Error: object 'prop' not found

Since aes expressions are resolved via tidy-eval, this also leads to a surprising behavior where prop gets scoped in a parent environment, when available, to resolve the guide data.

library(grid)

prop <- c(0, 0.5, 1)
grid.newpage()
ggplotGrob(p) |>
  gtable::gtable_filter("guide-box-right") |>
  grid.draw()

Image

prop <- 1
grid.newpage()
ggplotGrob(p) |>
  gtable::gtable_filter("guide-box-right") |>
  grid.draw()

Image


Two related behaviors worth noting:

  1. When only the after_scale is specified, the layer does not generate a legend at all for the aesthetic:
library(ggplot2)
ggplot(mtcars, aes(am)) +
  geom_bar(
    aes(
      group = as.factor(cyl),
      fill = after_scale(alpha(scales::hue_pal()(3)[group], prop))
       # also `stage(after_scale = alpha(scales::hue_pal()(3)[group], prop))`
    )
  )

Image

  1. When the after-scale expression in stage(start, after_scale) is valid for both sets of data, there is no error and the legend reflects the after-scale transformation.
ggplot(mtcars, aes(am)) +
  geom_bar(
    aes(
      fill = stage(as.factor(cyl), after_scale = alpha(fill, .5))
    )
  )

Image

@teunbrand
Copy link
Collaborator

Thanks for the report June!

I agree that the current situation is undesirable because it fails to build a plot with code that is reasonable.
I think the most prudent thing for ggplot2 to do is to ignore the unresolvable expressions with a warning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants