Skip to content

Commit b0e7586

Browse files
authored
Ignoring AsIs objects (again) (#5477)
* Write some utility functions * ignore data around scale operations * Simple test for expose/ignore * Add news bullet * use '.'-prefix * export ignore/expose functions
1 parent ad540b7 commit b0e7586

File tree

6 files changed

+127
-0
lines changed

6 files changed

+127
-0
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ S3method(widthDetails,zeroGrob)
152152
export("%+%")
153153
export("%+replace%")
154154
export(.data)
155+
export(.expose_data)
156+
export(.ignore_data)
155157
export(.pt)
156158
export(.stroke)
157159
export(AxisSecondary)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# ggplot2 (development version)
22

3+
* Plot scales now ignore `AsIs` objects constructed with `I(x)`, instead of
4+
invoking the identity scale. This allows these columns to co-exist with other
5+
layers that need a non-identity scale for the same aesthetic. Also, it makes
6+
it easy to specify relative positions (@teunbrand, #5142).
7+
38
* The `fill` aesthetic in many geoms now accepts grid's patterns and gradients.
49
For developers of layer extensions, this feature can be enabled by switching
510
from `fill = alpha(fill, alpha)` to `fill = fill_alpha(fill, alpha)` when

R/plot-build.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ ggplot_build.ggplot <- function(plot) {
5151

5252
# Compute aesthetics to produce data with generalised variable names
5353
data <- by_layer(function(l, d) l$compute_aesthetics(d, plot), layers, data, "computing aesthetics")
54+
data <- .ignore_data(data)
5455

5556
# Transform all scales
5657
data <- lapply(data, scales$transform_df)
@@ -62,6 +63,7 @@ ggplot_build.ggplot <- function(plot) {
6263

6364
layout$train_position(data, scale_x(), scale_y())
6465
data <- layout$map_position(data)
66+
data <- .expose_data(data)
6567

6668
# Apply and map statistics
6769
data <- by_layer(function(l, d) l$compute_statistic(d, layout), layers, data, "computing stat")
@@ -79,6 +81,7 @@ ggplot_build.ggplot <- function(plot) {
7981
# Reset position scales, then re-train and map. This ensures that facets
8082
# have control over the range of a plot: is it generated from what is
8183
# displayed, or does it include the range of underlying data
84+
data <- .ignore_data(data)
8285
layout$reset_scales()
8386
layout$train_position(data, scale_x(), scale_y())
8487
layout$setup_panel_params()
@@ -97,6 +100,7 @@ ggplot_build.ggplot <- function(plot) {
97100
# Only keep custom guides if there are no non-position scales
98101
plot$guides <- plot$guides$get_custom()
99102
}
103+
data <- .expose_data(data)
100104

101105
# Fill in defaults etc.
102106
data <- by_layer(function(l, d) l$compute_geom_2(d), layers, data, "setting up geom aesthetics")

R/utilities.R

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,69 @@ is_bang <- function(x) {
598598
is_call(x, "!", n = 1)
599599
}
600600

601+
# Puts all columns with 'AsIs' type in a '.ignore' column.
602+
603+
604+
605+
#' Ignoring and exposing data
606+
#'
607+
#' The `.ignore_data()` function is used to hide `<AsIs>` columns during
608+
#' scale interactions in `ggplot_build()`. The `.expose_data()` function is
609+
#' used to restore hidden columns.
610+
#'
611+
#' @param data A list of `<data.frame>`s.
612+
#'
613+
#' @return A modified list of `<data.frame>s`
614+
#' @export
615+
#' @keywords internal
616+
#' @name ignoring_data
617+
#'
618+
#' @examples
619+
#' data <- list(
620+
#' data.frame(x = 1:3, y = I(1:3)),
621+
#' data.frame(w = I(1:3), z = 1:3)
622+
#' )
623+
#'
624+
#' ignored <- .ignore_data(data)
625+
#' str(ignored)
626+
#'
627+
#' .expose_data(ignored)
628+
.ignore_data <- function(data) {
629+
if (!is_bare_list(data)) {
630+
data <- list(data)
631+
}
632+
lapply(data, function(df) {
633+
is_asis <- vapply(df, inherits, logical(1), what = "AsIs")
634+
if (!any(is_asis)) {
635+
return(df)
636+
}
637+
df <- unclass(df)
638+
# We trust that 'df' is a valid data.frame with equal length columns etc,
639+
# so we can use the more performant `new_data_frame()`
640+
new_data_frame(c(
641+
df[!is_asis],
642+
list(.ignored = new_data_frame(df[is_asis]))
643+
))
644+
})
645+
}
646+
647+
# Restores all columns packed into the '.ignored' column.
648+
#' @rdname ignoring_data
649+
#' @export
650+
.expose_data <- function(data) {
651+
if (!is_bare_list(data)) {
652+
data <- list(data)
653+
}
654+
lapply(data, function(df) {
655+
is_ignored <- which(names(df) == ".ignored")
656+
if (length(is_ignored) == 0) {
657+
return(df)
658+
}
659+
df <- unclass(df)
660+
new_data_frame(c(df[-is_ignored], df[[is_ignored[1]]]))
661+
})
662+
}
663+
601664
is_triple_bang <- function(x) {
602665
if (!is_bang(x)) {
603666
return(FALSE)

man/ignoring_data.Rd

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-utilities.R

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,21 @@ test_that("resolution() gives correct answers", {
177177
# resolution has a tolerance
178178
expect_equal(resolution(c(1, 1 + 1000 * .Machine$double.eps, 2)), 1)
179179
})
180+
181+
test_that("expose/ignore_data() can round-trip a data.frame", {
182+
183+
# Plain data.frame
184+
df <- data_frame0(a = 1:3, b = 4:6, c = LETTERS[1:3], d = LETTERS[4:6])
185+
expect_equal(list(df), .ignore_data(df))
186+
expect_equal(list(df), .expose_data(df))
187+
188+
# data.frame with ignored columns
189+
df <- data_frame0(a = 1:3, b = I(4:6), c = LETTERS[1:3], d = I(LETTERS[4:6]))
190+
test <- .ignore_data(df)[[1]]
191+
expect_equal(names(test), c("a", "c", ".ignored"))
192+
expect_equal(names(test$.ignored), c("b", "d"))
193+
194+
test <- .expose_data(test)[[1]]
195+
expect_equal(test, df[, c("a", "c", "b", "d")])
196+
197+
})

0 commit comments

Comments
 (0)