Description
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
)
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()
prop <- 1
grid.newpage()
ggplotGrob(p) |>
gtable::gtable_filter("guide-box-right") |>
grid.draw()
Two related behaviors worth noting:
- 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))`
)
)
- 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))
)
)