From 2c05fef7a774ba700643a5b3b5aa6f54720c8a3c Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Mon, 18 Dec 2023 23:22:00 -0600 Subject: [PATCH 01/16] initial experiment with allowing unit()s in geoms --- DESCRIPTION | 1 + NAMESPACE | 5 +++++ R/geom-point.R | 52 +++++++++++++++++++++++++++++++++++++--------- R/position-.R | 4 ++-- R/scale-.R | 6 +++--- R/utilities-unit.R | 51 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 15 deletions(-) create mode 100755 R/utilities-unit.R diff --git a/DESCRIPTION b/DESCRIPTION index 349f905e59..5a3700204a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -280,5 +280,6 @@ Collate: 'utilities-patterns.R' 'utilities-resolution.R' 'utilities-tidy-eval.R' + 'utilities-unit.R' 'zxx.R' 'zzz.R' diff --git a/NAMESPACE b/NAMESPACE index 967573b174..640d5d3989 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -92,6 +92,7 @@ S3method(limits,character) S3method(limits,factor) S3method(limits,numeric) S3method(makeContext,dotstackGrob) +S3method(makeContext,ggplot2_pointsGrob) S3method(merge_element,default) S3method(merge_element,element) S3method(merge_element,element_blank) @@ -138,6 +139,8 @@ S3method(vec_cast,mapped_discrete.factor) S3method(vec_cast,mapped_discrete.integer) S3method(vec_cast,mapped_discrete.logical) S3method(vec_cast,mapped_discrete.mapped_discrete) +S3method(vec_proxy,simpleUnit) +S3method(vec_proxy,unit) S3method(vec_ptype2,character.mapped_discrete) S3method(vec_ptype2,double.mapped_discrete) S3method(vec_ptype2,factor.mapped_discrete) @@ -147,6 +150,8 @@ S3method(vec_ptype2,mapped_discrete.double) S3method(vec_ptype2,mapped_discrete.factor) S3method(vec_ptype2,mapped_discrete.integer) S3method(vec_ptype2,mapped_discrete.mapped_discrete) +S3method(vec_restore,simpleUnit) +S3method(vec_restore,unit) S3method(widthDetails,titleGrob) S3method(widthDetails,zeroGrob) export("%+%") diff --git a/R/geom-point.R b/R/geom-point.R index 1b39a11d46..75364cb70e 100644 --- a/R/geom-point.R +++ b/R/geom-point.R @@ -121,21 +121,26 @@ GeomPoint <- ggproto("GeomPoint", Geom, if (is.character(data$shape)) { data$shape <- translate_shape_string(data$shape) } - coords <- coord$transform(data, panel_params) + stroke_size <- coords$stroke - stroke_size[is.na(stroke_size)] <- 0 + if (!is.unit(stroke_size)) stroke_size <- unit(stroke_size * .stroke, "pt") + stroke_size <- transform_unit(stroke_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) + stroke_size[is.na(stroke_size)] <- unit(0, "pt") + + font_size <- coords$size + if (!is.unit(font_size)) font_size <- unit(font_size * .pt, "pt") + font_size <- transform_unit(font_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) + ggname("geom_point", - pointsGrob( + ggplot2_pointsGrob( coords$x, coords$y, pch = coords$shape, - gp = gpar( - col = alpha(coords$colour, coords$alpha), - fill = fill_alpha(coords$fill, coords$alpha), - # Stroke is added around the outside of the point - fontsize = coords$size * .pt + stroke_size * .stroke / 2, - lwd = coords$stroke * .stroke / 2 - ) + col = alpha(coords$colour, coords$alpha), + fill = fill_alpha(coords$fill, coords$alpha), + # Stroke is added around the outside of the point + fontsize = font_size + stroke_size / 2, + lwd = stroke_size / 2 ) ) }, @@ -143,6 +148,33 @@ GeomPoint <- ggproto("GeomPoint", Geom, draw_key = draw_key_point ) +ggplot2_pointsGrob <- function( + x, y, pch = 1, vp = NULL, + fontsize = 12, lwd = 1, col = "black", fill = "white" +) { + grob( + x = x, y = y, pch = pch, vp = vp, + fontsize = fontsize, lwd = lwd, col = col, fill = fill, + cl = "ggplot2_pointsGrob" + ) +} + +#' @export +makeContext.ggplot2_pointsGrob <- function(x) { + pointsGrob( + x$x, + x$y, + pch = x$pch, + gp = gpar( + col = x$col, + fill = x$fill, + # Stroke is added around the outside of the point + fontsize = convertUnit(x$fontsize + x$lwd, unitTo = "pt", valueOnly = TRUE), + lwd = convertUnit(x$lwd, unitTo = "pt", valueOnly = TRUE) + ) + ) +} + #' Translating shape strings #' #' `translate_shape_string()` is a helper function for translating point shapes diff --git a/R/position-.R b/R/position-.R index 23d66579b4..d4e46c2a3d 100644 --- a/R/position-.R +++ b/R/position-.R @@ -81,10 +81,10 @@ transform_position <- function(df, trans_x = NULL, trans_y = NULL, ...) { scales <- aes_to_scale(names(df)) if (!is.null(trans_x)) { - df[scales == "x"] <- lapply(df[scales == "x"], trans_x, ...) + df[scales == "x"] <- lapply(df[scales == "x"], function(x) transform_unit(x, trans_x, ...)) } if (!is.null(trans_y)) { - df[scales == "y"] <- lapply(df[scales == "y"], trans_y, ...) + df[scales == "y"] <- lapply(df[scales == "y"], function(y) transform_unit(y, trans_y, ...)) } class(df) <- oldclass diff --git a/R/scale-.R b/R/scale-.R index d4776ca5ea..66334cb99b 100644 --- a/R/scale-.R +++ b/R/scale-.R @@ -608,8 +608,8 @@ check_breaks_labels <- function(breaks, labels, call = NULL) { default_transform <- function(self, x) { transformation <- self$get_transformation() - new_x <- transformation$transform(x) - check_transformation(x, new_x, self$transformation$name, call = self$call) + new_x <- transform_unit(x, transformation$transform) + if (!is.unit(x)) check_transformation(x, new_x, self$transformation$name, call = self$call) new_x } @@ -667,7 +667,7 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, }, rescale = function(self, x, limits = self$get_limits(), range = limits) { - self$rescaler(x, from = range) + transform_unit(x, self$rescaler, from = range) }, get_limits = function(self) { diff --git a/R/utilities-unit.R b/R/utilities-unit.R new file mode 100755 index 0000000000..e26c5c47f7 --- /dev/null +++ b/R/utilities-unit.R @@ -0,0 +1,51 @@ +#' transform x via the function trans. If x is a grid::unit(), apply the +#' transformation only to "native" units within x. +#' @noRd +transform_unit <- function(x, trans, ...) { + if (!is.unit(x)) { + return(trans(x, ...)) + } + + transform_unit_recursively(x, trans, ...) +} + +transform_unit_recursively = function(x, trans, ...) { + is_native <- unitType(x) == "native" + if (any(is_native)) { + x[is_native] <- unit(trans(as.numeric(x[is_native]), ...), "native") + } + + is_recursive <- unitType(x) %in% c("sum", "min", "max") + if (any(is_recursive)) { + x[is_recursive] <- do.call(unit.c, lapply(x[is_recursive], function(x_i) { + oldclass <- class(x_i) + x_i <- unclass(x_i) + x_i[[1]][[2]] <- transform_unit_recursively(x_i[[1]][[2]], trans, ...) + class(x_i) <- oldclass + x_i + })) + } + + x +} + +#' @export +vec_proxy.unit <- function(x, ...) { + unclass(x) +} + +#' @export +vec_restore.unit <- function(x, ...) { + class(x) <- c("unit", "unit_v2") + x +} + +#' @export +vec_proxy.simpleUnit <- function(x, ...) { + x +} + +#' @export +vec_restore.simpleUnit <- function(x, ...) { + x +} From 594995fe0321e39b32b857e15ef71aa561593f67 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Tue, 19 Dec 2023 22:05:14 -0600 Subject: [PATCH 02/16] convert simpleUnit to unit when proxied so bind operations work --- NAMESPACE | 1 - R/utilities-unit.R | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 640d5d3989..792db74892 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -150,7 +150,6 @@ S3method(vec_ptype2,mapped_discrete.double) S3method(vec_ptype2,mapped_discrete.factor) S3method(vec_ptype2,mapped_discrete.integer) S3method(vec_ptype2,mapped_discrete.mapped_discrete) -S3method(vec_restore,simpleUnit) S3method(vec_restore,unit) S3method(widthDetails,titleGrob) S3method(widthDetails,zeroGrob) diff --git a/R/utilities-unit.R b/R/utilities-unit.R index e26c5c47f7..4eb03c348a 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -42,10 +42,9 @@ vec_restore.unit <- function(x, ...) { #' @export vec_proxy.simpleUnit <- function(x, ...) { - x -} - -#' @export -vec_restore.simpleUnit <- function(x, ...) { - x + # turn a simpleUnit into a unit when proxied, because simpleUnit's format + # (a numeric vector with an attribute indicating the type of all entries) + # does not work properly with many operations, like binding + type <- attr(x, "unit") + lapply(x, function(x_i) list(x_i, NULL, type)) } From 42594271ec7d70efa201279ee463d87f161f31d7 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Tue, 19 Dec 2023 22:05:44 -0600 Subject: [PATCH 03/16] use vec_detect_complete instead of complete.cases for better support for custom vector types (like unit) --- R/fortify-map.R | 2 +- R/geom-path.R | 2 +- R/geom-ribbon.R | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/fortify-map.R b/R/fortify-map.R index d0dc76b716..6237ffebee 100644 --- a/R/fortify-map.R +++ b/R/fortify-map.R @@ -37,7 +37,7 @@ fortify.map <- function(model, data, ...) { names <- inject(rbind(!!!names)) df$region <- names[df$group, 1] df$subregion <- names[df$group, 2] - df[stats::complete.cases(df$lat, df$long), ] + df[vec_detect_complete(df$lat, df$long), ] } #' Create a data frame of map data diff --git a/R/geom-path.R b/R/geom-path.R index cf9e59976c..696f680031 100644 --- a/R/geom-path.R +++ b/R/geom-path.R @@ -138,7 +138,7 @@ GeomPath <- ggproto("GeomPath", Geom, # Drop missing values at the start or end of a line - can't drop in the # middle since you expect those to be shown by a break in the line aesthetics <- c(self$required_aes, self$non_missing_aes) - complete <- stats::complete.cases(data[names(data) %in% aesthetics]) + complete <- vec_detect_complete(data[names(data) %in% aesthetics]) kept <- stats::ave(complete, data$group, FUN = keep_mid_true) data <- data[kept, ] diff --git a/R/geom-ribbon.R b/R/geom-ribbon.R index d93df77850..18569f4335 100644 --- a/R/geom-ribbon.R +++ b/R/geom-ribbon.R @@ -131,7 +131,7 @@ GeomRibbon <- ggproto("GeomRibbon", Geom, flipped_aes = FALSE, outline.type = "both") { data <- check_linewidth(data, snake_class(self)) data <- flip_data(data, flipped_aes) - if (na.rm) data <- data[stats::complete.cases(data[c("x", "ymin", "ymax")]), ] + if (na.rm) data <- data[vec_detect_complete(data[c("x", "ymin", "ymax")]), ] data <- data[order(data$group), ] # Check that aesthetics are constant @@ -148,7 +148,7 @@ GeomRibbon <- ggproto("GeomRibbon", Geom, # has distinct polygon numbers for sequences of non-NA values and NA # for NA values in the original data. Example: c(NA, 2, 2, 2, NA, NA, # 4, 4, 4, NA) - missing_pos <- !stats::complete.cases(data[c("x", "ymin", "ymax")]) + missing_pos <- !vec_detect_complete(data[c("x", "ymin", "ymax")]) ids <- cumsum(missing_pos) + 1 ids[missing_pos] <- NA From 6eaacbf896f62c08fa186810aa914651e6096188 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Wed, 20 Dec 2023 00:52:21 -0600 Subject: [PATCH 04/16] fixes to unit proxy --- R/utilities-unit.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/utilities-unit.R b/R/utilities-unit.R index 4eb03c348a..de41fd4f26 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -36,6 +36,9 @@ vec_proxy.unit <- function(x, ...) { #' @export vec_restore.unit <- function(x, ...) { + # replace NAs (NULL entries) with unit's version of NA + is_na <- vapply(x, is.null, logical(1)) + x[is_na] <- vec_proxy(unit(NA_real_, "native")) class(x) <- c("unit", "unit_v2") x } @@ -46,5 +49,5 @@ vec_proxy.simpleUnit <- function(x, ...) { # (a numeric vector with an attribute indicating the type of all entries) # does not work properly with many operations, like binding type <- attr(x, "unit") - lapply(x, function(x_i) list(x_i, NULL, type)) + lapply(unclass(x), function(x_i) list(x_i, NULL, type)) } From 21c347ed5370676d6bfc07ec817baccf293512a3 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Wed, 20 Dec 2023 00:52:51 -0600 Subject: [PATCH 05/16] ggunit subtype for easier unit expressions --- DESCRIPTION | 1 + NAMESPACE | 28 ++++++++++ R/ggunit.R | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100755 R/ggunit.R diff --git a/DESCRIPTION b/DESCRIPTION index 5a3700204a..3653d3a6ed 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -171,6 +171,7 @@ Collate: 'geom-violin.R' 'geom-vline.R' 'ggplot2-package.R' + 'ggunit.R' 'grob-absolute.R' 'grob-dotstack.R' 'grob-null.R' diff --git a/NAMESPACE b/NAMESPACE index 792db74892..ae8891787b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,16 +7,21 @@ S3method("$<-",uneval) S3method("+",gg) S3method("[",mapped_discrete) S3method("[",uneval) +S3method("[<-",ggunit) S3method("[<-",mapped_discrete) S3method("[<-",uneval) S3method("[[",ggproto) +S3method("[[<-",ggunit) S3method("[[<-",uneval) S3method(.DollarNames,ggproto) +S3method(Ops,ggunit) +S3method(Summary,ggunit) S3method(as.data.frame,mapped_discrete) S3method(as.list,ggproto) S3method(autolayer,default) S3method(autoplot,default) S3method(c,mapped_discrete) +S3method(chooseOpsMethod,ggunit) S3method(drawDetails,zeroGrob) S3method(element_grob,element_blank) S3method(element_grob,element_line) @@ -110,6 +115,7 @@ S3method(print,ggplot) S3method(print,ggplot2_bins) S3method(print,ggproto) S3method(print,ggproto_method) +S3method(print,ggunit) S3method(print,rel) S3method(print,theme) S3method(print,uneval) @@ -131,25 +137,47 @@ S3method(single_value,default) S3method(single_value,factor) S3method(summary,ggplot) S3method(vec_cast,character.mapped_discrete) +S3method(vec_cast,double.ggunit) S3method(vec_cast,double.mapped_discrete) S3method(vec_cast,factor.mapped_discrete) +S3method(vec_cast,ggunit.double) +S3method(vec_cast,ggunit.ggunit) +S3method(vec_cast,ggunit.integer) +S3method(vec_cast,ggunit.list) +S3method(vec_cast,ggunit.logical) +S3method(vec_cast,ggunit.simpleUnit) +S3method(vec_cast,ggunit.unit) +S3method(vec_cast,integer.ggunit) S3method(vec_cast,integer.mapped_discrete) +S3method(vec_cast,logical.ggunit) S3method(vec_cast,mapped_discrete.double) S3method(vec_cast,mapped_discrete.factor) S3method(vec_cast,mapped_discrete.integer) S3method(vec_cast,mapped_discrete.logical) S3method(vec_cast,mapped_discrete.mapped_discrete) +S3method(vec_cast,simpleUnit.ggunit) +S3method(vec_cast,unit.ggunit) S3method(vec_proxy,simpleUnit) S3method(vec_proxy,unit) S3method(vec_ptype2,character.mapped_discrete) +S3method(vec_ptype2,double.ggunit) S3method(vec_ptype2,double.mapped_discrete) S3method(vec_ptype2,factor.mapped_discrete) +S3method(vec_ptype2,ggunit.double) +S3method(vec_ptype2,ggunit.ggunit) +S3method(vec_ptype2,ggunit.integer) +S3method(vec_ptype2,ggunit.simpleUnit) +S3method(vec_ptype2,ggunit.unit) +S3method(vec_ptype2,integer.ggunit) S3method(vec_ptype2,integer.mapped_discrete) S3method(vec_ptype2,mapped_discrete.character) S3method(vec_ptype2,mapped_discrete.double) S3method(vec_ptype2,mapped_discrete.factor) S3method(vec_ptype2,mapped_discrete.integer) S3method(vec_ptype2,mapped_discrete.mapped_discrete) +S3method(vec_ptype2,simpleUnit.ggunit) +S3method(vec_ptype2,unit.ggunit) +S3method(vec_restore,ggunit) S3method(vec_restore,unit) S3method(widthDetails,titleGrob) S3method(widthDetails,zeroGrob) diff --git a/R/ggunit.R b/R/ggunit.R new file mode 100755 index 0000000000..c7c798fede --- /dev/null +++ b/R/ggunit.R @@ -0,0 +1,158 @@ +# constructors ------------------------------------------------------------ + +null_unit <- function() { + # grid::unit() doesn't allow zero-length vectors, + # so we have to do this manually + structure(list(), class = c("unit", "unit_v2")) +} + +new_ggunit <- function(x = null_unit()) { + class(x) <- c("ggunit", setdiff(class(x), c("ggunit", "vctrs_vctr")), "vctrs_vctr") + x +} + +ggunit <- function(x = numeric(), units = "native", data = NULL) { + x <- vec_cast(x, numeric()) + units <- vec_cast(units, character()) + data <- vec_cast(data, list()) + + if (length(x) == 0) { + x <- null_unit() + } else { + x <- unit(x, units, data = data) + } + + new_ggunit(x) +} + + +# casting ---------------------------------------------------------------- + +as_ggunit <- function(x) { + vec_cast(x, new_ggunit()) +} + +as_pt <- function(x) { + ggunit(x, "pt") +} + +as_npc <- function(x) { + ggunit(x, "npc") +} + + +# type predicates --------------------------------------------------------- + +is_ggunit <- function(x) { + inherits(x, "ggunit") +} + + +# math -------------------------------------------------------------------- + +#' @export +Ops.ggunit <- function(x, y) { + x <- vec_cast(x, new_ggunit()) + if (!missing(y)) y <- vec_cast(y, new_ggunit()) + out <- NextMethod() + new_ggunit(out) +} + +#' @export +chooseOpsMethod.ggunit = function(x, y, mx, my, cl, reverse) { + # TODO: something more comprehensive using vec_ptype2 + inherits(x, "ggunit") +} + +#' @export +Summary.ggunit <- function(..., na.rm = FALSE) { + ggunits <- vec_cast_common(..., .to = new_ggunit()) + units <- vec_cast(ggunits, list_of(null_unit())) + out <- do.call(.Generic, c(units, list(na.rm = na.rm))) + new_ggunit(out) +} + + +# assignment -------------------------------------------------------------- + +#' @export +`[<-.ggunit` <- function(x, i, ..., value) { + value <- vec_cast(value, x) + out <- NextMethod() + new_ggunit(out) +} + +#' @export +`[[<-.ggunit` <- function(x, i, ..., value) { + value <- vec_cast(value, x) + out <- NextMethod() + new_ggunit(out) +} + + +# printing ---------------------------------------------------------------- + +#' @export +print.ggunit <- function(x, ...) { + # need to manually provide this rather than relying on print.vctrs_vctr() + # to bypass the printing method for grid::unit + obj_print(x, ...) + invisible(x) +} + + +# proxies ----------------------------------------------------------------- + +#' @export +vec_restore.ggunit <- function(x, ...) { + x <- NextMethod() + class(x) <- c("ggunit", class(x), "vctrs_vctr") + x +} + + +# casting ----------------------------------------------------------------- + +#' @export +vec_ptype2.ggunit.ggunit <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.ggunit.unit <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.unit.ggunit <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.ggunit.simpleUnit <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.simpleUnit.ggunit <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.ggunit.double <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.double.ggunit <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.ggunit.integer <- function(x, y, ...) new_ggunit() +#' @export +vec_ptype2.integer.ggunit <- function(x, y, ...) new_ggunit() + +#' @export +vec_cast.ggunit.ggunit <- function(x, to, ...) x +#' @export +vec_cast.ggunit.unit <- function(x, to, ...) new_ggunit(x) +#' @export +vec_cast.unit.ggunit <- function(x, to, ...) `class<-`(x, setdiff(class(x), c("ggunit", "vctrs_vctr"))) +#' @export +vec_cast.ggunit.simpleUnit <- function(x, to, ...) new_ggunit(x) +#' @export +vec_cast.simpleUnit.ggunit <- function(x, to, ...) `class<-`(x, setdiff(class(x), c("ggunit", "vctrs_vctr"))) +#' @export +vec_cast.ggunit.integer <- function(x, to, ...) ggunit(x) +#' @export +vec_cast.integer.ggunit <- function(x, to, ...) as.integer(as.numeric(x)) +#' @export +vec_cast.ggunit.double <- function(x, to, ...) ggunit(x) +#' @export +vec_cast.double.ggunit <- function(x, to, ...) as.numeric(x) +#' @export +vec_cast.ggunit.logical <- function(x, to, ...) ggunit(x) +#' @export +vec_cast.logical.ggunit <- function(x, to, ...) as.logical(as.numeric(x)) +#' @export +vec_cast.ggunit.list <- function(x, to, ...) stop_incompatible_cast(x, to, x_arg = "x", to_arg = "to") From 158ff6e7a8fe9450a10e1f735fad663d081559ec Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Wed, 20 Dec 2023 01:08:24 -0600 Subject: [PATCH 06/16] fix Ops.ggunit for * and / --- R/ggunit.R | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/R/ggunit.R b/R/ggunit.R index c7c798fede..6b919c560f 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -50,10 +50,19 @@ is_ggunit <- function(x) { # math -------------------------------------------------------------------- +# #' @export +# Math.ggunit <- function(x, ...) { +# transform_unit(x, match.fun(.Generic), ...) +# } + #' @export Ops.ggunit <- function(x, y) { - x <- vec_cast(x, new_ggunit()) - if (!missing(y)) y <- vec_cast(y, new_ggunit()) + if (!(.Generic %in% c("*", "/"))) { + x <- vec_cast(x, new_ggunit()) + if (!missing(y)) { + y <- vec_cast(y, new_ggunit()) + } + } out <- NextMethod() new_ggunit(out) } From b4bd47b457a4192cea1bfeac34a2b97670d732b9 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Wed, 20 Dec 2023 22:42:38 -0600 Subject: [PATCH 07/16] .ignore_units / .expose_units like .ignore_data / .expose_data --- NAMESPACE | 5 ++ R/coord-.R | 6 +- R/coord-cartesian-.R | 2 +- R/coord-flip.R | 2 +- R/coord-map.R | 2 +- R/coord-munch.R | 4 +- R/coord-polar.R | 8 +-- R/coord-radial.R | 2 +- R/coord-sf.R | 2 +- R/coord-transform.R | 2 +- R/geom-point.R | 4 +- R/ggunit.R | 103 +++++++++++++++++++++++++++++-- R/position-.R | 4 +- R/scale-.R | 4 +- R/scale-type.R | 3 + R/utilities-unit.R | 140 ++++++++++++++++++++++++++++++++++++++++--- 16 files changed, 264 insertions(+), 29 deletions(-) mode change 100644 => 100755 R/coord-polar.R mode change 100644 => 100755 R/geom-point.R mode change 100644 => 100755 R/position-.R mode change 100644 => 100755 R/scale-.R diff --git a/NAMESPACE b/NAMESPACE index ae8891787b..6e1f537abb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ S3method("$",ggproto_parent) S3method("$",theme) S3method("$<-",uneval) S3method("+",gg) +S3method("[",ggunit) S3method("[",mapped_discrete) S3method("[",uneval) S3method("[<-",ggunit) @@ -90,6 +91,8 @@ S3method(heightDetails,titleGrob) S3method(heightDetails,zeroGrob) S3method(interleave,default) S3method(interleave,unit) +S3method(is.finite,ggunit) +S3method(is.infinite,ggunit) S3method(limits,Date) S3method(limits,POSIXct) S3method(limits,POSIXlt) @@ -133,6 +136,8 @@ S3method(scale_type,logical) S3method(scale_type,numeric) S3method(scale_type,ordered) S3method(scale_type,sfc) +S3method(scale_type,unit) +S3method(sign,ggunit) S3method(single_value,default) S3method(single_value,factor) S3method(summary,ggplot) diff --git a/R/coord-.R b/R/coord-.R index 8c4313baf7..26c3f9449b 100644 --- a/R/coord-.R +++ b/R/coord-.R @@ -170,7 +170,11 @@ Coord <- ggproto("Coord", panel_params }, - transform = function(data, range) NULL, + transform = function(self, data, range) { + .expose_units(self$transform_native(.ignore_units(data), range)) + }, + + transform_native = function(data, range) NULL, distance = function(x, y, panel_params) NULL, diff --git a/R/coord-cartesian-.R b/R/coord-cartesian-.R index 74f46433db..5977d4e45a 100644 --- a/R/coord-cartesian-.R +++ b/R/coord-cartesian-.R @@ -93,7 +93,7 @@ CoordCartesian <- ggproto("CoordCartesian", Coord, self$range(panel_params) }, - transform = function(data, panel_params) { + transform_native = function(data, panel_params) { data <- transform_position(data, panel_params$x$rescale, panel_params$y$rescale) transform_position(data, squish_infinite, squish_infinite) }, diff --git a/R/coord-flip.R b/R/coord-flip.R index 1f3848fb8a..c688be7cf0 100644 --- a/R/coord-flip.R +++ b/R/coord-flip.R @@ -59,7 +59,7 @@ coord_flip <- function(xlim = NULL, ylim = NULL, expand = TRUE, clip = "on") { #' @export CoordFlip <- ggproto("CoordFlip", CoordCartesian, - transform = function(data, panel_params) { + transform_native = function(data, panel_params) { data <- flip_axis_labels(data) CoordCartesian$transform(data, panel_params) }, diff --git a/R/coord-map.R b/R/coord-map.R index ee0f6ad139..649d8f1436 100644 --- a/R/coord-map.R +++ b/R/coord-map.R @@ -153,7 +153,7 @@ coord_map <- function(projection="mercator", ..., parameters = NULL, orientation #' @export CoordMap <- ggproto("CoordMap", Coord, - transform = function(self, data, panel_params) { + transform_native = function(self, data, panel_params) { trans <- mproject(self, data$x, data$y, panel_params$orientation) out <- cunion(trans[c("x", "y")], data) diff --git a/R/coord-munch.R b/R/coord-munch.R index 6f2bbb2afb..6f877fc506 100644 --- a/R/coord-munch.R +++ b/R/coord-munch.R @@ -15,6 +15,8 @@ coord_munch <- function(coord, data, range, segment_length = 0.01, is_closed = FALSE) { if (coord$is_linear()) return(coord$transform(data, range)) + data <- .ignore_units(data) + if (is_closed) { data <- close_poly(data) } @@ -44,7 +46,7 @@ coord_munch <- function(coord, data, range, segment_length = 0.01, is_closed = F runs <- vec_run_sizes(munched[, group_cols, drop = FALSE]) munched <- vec_slice(munched, -(cumsum(runs))) } - coord$transform(munched, range) + coord$transform(.expose_units(munched), range) } # For munching, only grobs are lines and polygons: everything else is diff --git a/R/coord-polar.R b/R/coord-polar.R old mode 100644 new mode 100755 index 1e30adcd2b..9d59fa843d --- a/R/coord-polar.R +++ b/R/coord-polar.R @@ -167,7 +167,7 @@ CoordPolar <- ggproto("CoordPolar", Coord, panel_params }, - transform = function(self, data, panel_params) { + transform_native = function(self, data, panel_params) { arc <- self$start + c(0, 2 * pi) dir <- self$direction data <- rename_data(self, data) @@ -316,15 +316,15 @@ rename_data <- function(coord, data) { } theta_rescale_no_clip <- function(x, range, arc = c(0, 2 * pi), direction = 1) { - rescale(x, to = arc, from = range) * direction + transform_native_units(x, function(x) rescale(x, to = arc, from = range) * direction) } theta_rescale <- function(x, range, arc = c(0, 2 * pi), direction = 1) { x <- squish_infinite(x, range) - rescale(x, to = arc, from = range) %% (2 * pi) * direction + transform_native_units(x, function(x) rescale(x, to = arc, from = range) %% (2 * pi) * direction) } r_rescale <- function(x, range, donut = c(0, 0.4)) { x <- squish_infinite(x, range) - rescale(x, donut, range) + transform_native_units(x, function(x) rescale(x, donut, range)) } diff --git a/R/coord-radial.R b/R/coord-radial.R index 70aa211898..607f94084a 100644 --- a/R/coord-radial.R +++ b/R/coord-radial.R @@ -219,7 +219,7 @@ CoordRadial <- ggproto("CoordRadial", Coord, panel_params }, - transform = function(self, data, panel_params) { + transform_native = function(self, data, panel_params) { data <- rename_data(self, data) bbox <- panel_params$bbox %||% list(x = c(0, 1), y = c(0, 1)) arc <- panel_params$arc %||% c(0, 2 * pi) diff --git a/R/coord-sf.R b/R/coord-sf.R index 331ca4f1f0..71a666e326 100644 --- a/R/coord-sf.R +++ b/R/coord-sf.R @@ -78,7 +78,7 @@ CoordSf <- ggproto("CoordSf", CoordCartesian, self$params$bbox <- bbox }, - transform = function(self, data, panel_params) { + transform_native = function(self, data, panel_params) { # we need to transform all non-sf data into the correct coordinate system source_crs <- panel_params$default_crs target_crs <- panel_params$crs diff --git a/R/coord-transform.R b/R/coord-transform.R index 79d651e8af..3fc8f36816 100644 --- a/R/coord-transform.R +++ b/R/coord-transform.R @@ -127,7 +127,7 @@ CoordTrans <- ggproto("CoordTrans", Coord, ) }, - transform = function(self, data, panel_params) { + transform_native = function(self, data, panel_params) { # trans_x() and trans_y() needs to keep Inf values because this can be called # in guide_transform.axis() trans_x <- function(data) { diff --git a/R/geom-point.R b/R/geom-point.R old mode 100644 new mode 100755 index 75364cb70e..b6715ceefc --- a/R/geom-point.R +++ b/R/geom-point.R @@ -125,12 +125,12 @@ GeomPoint <- ggproto("GeomPoint", Geom, stroke_size <- coords$stroke if (!is.unit(stroke_size)) stroke_size <- unit(stroke_size * .stroke, "pt") - stroke_size <- transform_unit(stroke_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) + stroke_size <- transform_native_units(stroke_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) stroke_size[is.na(stroke_size)] <- unit(0, "pt") font_size <- coords$size if (!is.unit(font_size)) font_size <- unit(font_size * .pt, "pt") - font_size <- transform_unit(font_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) + font_size <- transform_native_units(font_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) ggname("geom_point", ggplot2_pointsGrob( diff --git a/R/ggunit.R b/R/ggunit.R index 6b919c560f..46231d5545 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -52,7 +52,7 @@ is_ggunit <- function(x) { # #' @export # Math.ggunit <- function(x, ...) { -# transform_unit(x, match.fun(.Generic), ...) +# transform_native_units(x, match.fun(.Generic), ...) # } #' @export @@ -63,8 +63,28 @@ Ops.ggunit <- function(x, y) { y <- vec_cast(y, new_ggunit()) } } - out <- NextMethod() - new_ggunit(out) + if (.Generic == "==") { + len <- max(length(x), length(y)) + out <- logical(len) + x <- rep_len(x, len) + y <- rep_len(y, len) + type_x = unitType(x) + type_y = unitType(y) + + is_same_atomic <- type_x == type_y & !type_x %in% c("sum", "min", "max") + out[is_same_atomic] <- as.numeric(x[is_same_atomic]) == as.numeric(y[is_same_atomic]) + + # determine equality in otherwise incomparable units where possible (unequal signs, 0s, Infs) + x_not_atomic <- x[!is_same_atomic] + y_not_atomic <- y[!is_same_atomic] + sign_x_not_atomic <- sign(x_not_atomic) + sign_y_not_atomic <- sign(y_not_atomic) + out[!is_same_atomic] <- sign_x_not_atomic == sign_y_not_atomic & + ifelse(sign_x_not_atomic == 0 | (is.infinite(x_not_atomic) & is.infinite(y_not_atomic)), TRUE, NA) + } else { + out <- new_ggunit(NextMethod()) + } + return(out) } #' @export @@ -81,9 +101,76 @@ Summary.ggunit <- function(..., na.rm = FALSE) { new_ggunit(out) } +ggunit_math_function <- function(x, out_type, atomic_f, sum_f = function(x) NA, min_f = function(x) NA, max_f = function(x) NA) { + out <- out_type(length(x)) + type <- unitType(x) + + is_atomic <- !type %in% c("sum", "min", "max") + out[is_atomic] <- atomic_f(as.numeric(x[is_atomic])) + + for (t in c("sum", "min", "max")) { + is_type <- t == type + f <- get(paste0(t, "_f")) + out[is_type] <- vapply(x[is_type], FUN.VALUE = out_type(1), function(x_i) { + components <- vec_cast(unclass(x_i)[[1]][[2]], new_ggunit()) + f(components) + }) + } + + out +} + +is_pos_Inf <- function(x) (is.infinite(x) & sign(x) == 1) %in% TRUE +is_neg_Inf <- function(x) (is.infinite(x) & sign(x) == -1) %in% TRUE + +#' @export +is.infinite.ggunit <- function(x) { + ggunit_math_function(x, logical, + atomic_f = is.infinite, + sum_f = function(x) any(is.infinite(x)), + min_f = function(x) all(is.infinite(x)) || any(is_neg_Inf(x)), + max_f = function(x) all(is.infinite(x)) || any(is_pos_Inf(x)) + ) +} + +#' @export +is.finite.ggunit <- function(x) { + ggunit_math_function(x, logical, + atomic_f = is.finite, + sum_f = function(x) all(is.finite(x)), + min_f = function(x) any(is.finite(x)) && all(is_pos_Inf(x[!is_finite])), + max_f = function(x) any(is.finite(x)) && all(is_neg_Inf(x[!is_finite])) + ) +} + +#' @export +sign.ggunit <- function(x) { + ggunit_math_function(x, numeric, + atomic_f = sign, + sum_f = function(x) { + unique_sign <- unique(sign(x)) + if (length(unique_sign) == 1) unique_sign else NA_real_ + }, + min_f = function(x) { + sign_x <- sign(x) + if (isTRUE(any(sign_x == -1))) -1 else min(sign(x)) + }, + max_f = function(x) { + sign_x <- sign(x) + if (isTRUE(any(sign(x) == 1))) 1 else max(sign(x)) + } + ) +} + # assignment -------------------------------------------------------------- +#' @export +`[.ggunit` <- function(x, i) { + if (missing(i)) return(x) + vec_slice(x, i) +} + #' @export `[<-.ggunit` <- function(x, i, ..., value) { value <- vec_cast(value, x) @@ -164,4 +251,12 @@ vec_cast.ggunit.logical <- function(x, to, ...) ggunit(x) #' @export vec_cast.logical.ggunit <- function(x, to, ...) as.logical(as.numeric(x)) #' @export -vec_cast.ggunit.list <- function(x, to, ...) stop_incompatible_cast(x, to, x_arg = "x", to_arg = "to") +vec_cast.ggunit.list <- function(x, to, ...) { + is_na <- vapply(x, is.null, logical(1)) + x[is_na] <- NA + x <- vec_cast(x, list_of(new_ggunit())) + if (any(lengths(x) != 1)) { + stop_incompatible_cast(x, to, x_arg = "x", to_arg = "to", details = "All elements of the list must be length-1 ggunits or NULL.") + } + list_unchop(x, ptype = new_ggunit()) +} diff --git a/R/position-.R b/R/position-.R old mode 100644 new mode 100755 index d4e46c2a3d..2d5ab2659b --- a/R/position-.R +++ b/R/position-.R @@ -81,10 +81,10 @@ transform_position <- function(df, trans_x = NULL, trans_y = NULL, ...) { scales <- aes_to_scale(names(df)) if (!is.null(trans_x)) { - df[scales == "x"] <- lapply(df[scales == "x"], function(x) transform_unit(x, trans_x, ...)) + df[scales == "x"] <- lapply(df[scales == "x"], function(x) transform_native_units(x, trans_x, ...)) } if (!is.null(trans_y)) { - df[scales == "y"] <- lapply(df[scales == "y"], function(y) transform_unit(y, trans_y, ...)) + df[scales == "y"] <- lapply(df[scales == "y"], function(y) transform_native_units(y, trans_y, ...)) } class(df) <- oldclass diff --git a/R/scale-.R b/R/scale-.R old mode 100644 new mode 100755 index 66334cb99b..822d6977d0 --- a/R/scale-.R +++ b/R/scale-.R @@ -608,7 +608,7 @@ check_breaks_labels <- function(breaks, labels, call = NULL) { default_transform <- function(self, x) { transformation <- self$get_transformation() - new_x <- transform_unit(x, transformation$transform) + new_x <- transform_native_units(x, transformation$transform) if (!is.unit(x)) check_transformation(x, new_x, self$transformation$name, call = self$call) new_x } @@ -667,7 +667,7 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, }, rescale = function(self, x, limits = self$get_limits(), range = limits) { - transform_unit(x, self$rescaler, from = range) + transform_native_units(x, self$rescaler, from = range) }, get_limits = function(self) { diff --git a/R/scale-type.R b/R/scale-type.R index 2feaa69c82..cffac38ec9 100644 --- a/R/scale-type.R +++ b/R/scale-type.R @@ -102,3 +102,6 @@ scale_type.double <- function(x) "continuous" #' @export scale_type.hms <- function(x) "time" + +#' @export +scale_type.unit <- function(x) "continuous" diff --git a/R/utilities-unit.R b/R/utilities-unit.R index de41fd4f26..ea89a07f75 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -1,12 +1,14 @@ #' transform x via the function trans. If x is a grid::unit(), apply the #' transformation only to "native" units within x. #' @noRd -transform_unit <- function(x, trans, ...) { +transform_native_units <- function(x, trans, ...) { if (!is.unit(x)) { return(trans(x, ...)) } - transform_unit_recursively(x, trans, ...) + x <- collapse_native_units(x) + native_units(x) <- trans(native_units(x), ...) + x } transform_unit_recursively = function(x, trans, ...) { @@ -18,17 +20,141 @@ transform_unit_recursively = function(x, trans, ...) { is_recursive <- unitType(x) %in% c("sum", "min", "max") if (any(is_recursive)) { x[is_recursive] <- do.call(unit.c, lapply(x[is_recursive], function(x_i) { - oldclass <- class(x_i) - x_i <- unclass(x_i) - x_i[[1]][[2]] <- transform_unit_recursively(x_i[[1]][[2]], trans, ...) - class(x_i) <- oldclass - x_i + unit_components(x_i) <- transform_unit_recursively(unit_components(x_i), trans, ...) })) } x } +unit_components <- function(x) { + unclass(x)[[1]][[2]] +} + +`unit_components<-` <- function(x, value) { + if (inherits(value, "simpleUnit")) { + # force the value to be a list form of unit, not a numeric vector form + value <- vec_restore(vec_proxy(value), new_ggunit()) + } + oldclass <- class(x) + x <- unclass(x) + x[[1]][[2]] <- value + class(x) <- oldclass + x +} + +collapse_native_units <- function(x) { + x <- vec_cast(x, new_ggunit()) + type <- unitType(x) + is_recursive <- type %in% c("sum", "min", "max") + x[is_recursive] <- .mapply(list(x[is_recursive], type[is_recursive]), NULL, FUN = function(x_i, f) { + f <- match.fun(f) + components <- unit_components(x_i) + is_native <- unitType(components) == "native" + if (any(is_native)) { + x_i <- f(ggunit(f(as.numeric(components[is_native]))), components[!is_native]) + } + x_i + }) + x +} + +native_units <- function(x) { + x <- vec_cast(x, new_ggunit()) + .get_native_units(x)$values +} + +.get_native_units <- function(x) { + values <- rep_len(NA_real_, length(x)) + type <- unitType(x) + + is_native <- type == "native" + values[is_native] <- as.numeric(x[is_native]) + + is_recursive <- unitType(x) %in% c("sum", "min", "max") + if (any(is_recursive)) { + for (i in which(is_recursive)) { + out <- .get_native_units(unit_components(x[[i]])) + native_i <- which(out$is_native) + if (length(native_i) > 1) { + cli::cli_abort("More than one native unit in {x[[i]]}") + } else if (length(native_i) == 1) { + values[[i]] <- out$values[[native_i]] + is_native[[i]] <- TRUE + } + } + } + + list(values = values, is_native = is_native) +} + +`native_units<-` <- function(x, values) { + .set_native_units(x, values)$x +} + +.set_native_units <- function(x, values) { + len <- max(length(x), length(values)) + x <- rep_len(x, len) + values <- rep_len(values, len) + type <- unitType(x) + + is_native <- type == "native" + if (any(is_native)) { + x[is_native] <- unit(values[is_native], "native") + } + + is_recursive <- unitType(x) %in% c("sum", "min", "max") + if (any(is_recursive)) { + for (i in which(is_recursive)) { + out <- .set_native_units(unit_components(x), values[[i]]) + native_i <- which(out$is_native) + if (length(native_i) > 1) { + cli::cli_abort("More than one native unit in {x[[i]]}") + } else if (length(native_i) == 1) { + unit_components(x[[i]]) <- out$x + is_native[[i]] <- TRUE + } + } + } + + list(x = x, is_native = is_native) +} + +.ignore_units <- function(df, cols = c(ggplot_global$x_aes, ggplot_global$y_aes)) { + if (is.null(cols)) { + is_selected <- TRUE + } else { + is_selected <- names(df) %in% cols + } + is_unit <- vapply(df, is.unit, logical(1)) & is_selected + if (!any(is_unit)) { + return(df) + } + df <- unclass(df) + # We trust that 'df' is a valid data.frame with equal length columns etc, + # so we can use the more performant `new_data_frame()` + unit_cols <- lapply(df[is_unit], collapse_native_units) + new_data_frame(c( + df[!is_unit], + lapply(unit_cols, native_units), + list(.ignored_units = new_data_frame(unit_cols)) + )) +} + +.expose_units <- function(df) { + is_ignored <- which(names(df) == ".ignored_units") + if (length(is_ignored) == 0) { + return(df) + } + unit_col_names <- intersect(names(df), names(df[[is_ignored[1]]])) + is_unit <- which(names(df) %in% unit_col_names) + df <- unclass(df) + new_data_frame(c( + df[-c(is_ignored, is_unit)], + mapply(`native_units<-`, df[[is_ignored[1]]][unit_col_names], df[unit_col_names], SIMPLIFY = FALSE) + )) +} + #' @export vec_proxy.unit <- function(x, ...) { unclass(x) From 4f72661efa94038bce2f7c02ded47d43b506f5d3 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Thu, 21 Dec 2023 20:17:59 -0600 Subject: [PATCH 08/16] improvements to unit handling --- NAMESPACE | 7 ++++++ R/ggunit.R | 58 ++++++++++++++++++++++++++++------------------ R/utilities-unit.R | 58 ++++++++++++++++++++++++++++------------------ 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 6e1f537abb..f8b3d4d3ea 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -93,6 +93,7 @@ S3method(interleave,default) S3method(interleave,unit) S3method(is.finite,ggunit) S3method(is.infinite,ggunit) +S3method(is.na,ggunit) S3method(limits,Date) S3method(limits,POSIXct) S3method(limits,POSIXlt) @@ -161,7 +162,10 @@ S3method(vec_cast,mapped_discrete.integer) S3method(vec_cast,mapped_discrete.logical) S3method(vec_cast,mapped_discrete.mapped_discrete) S3method(vec_cast,simpleUnit.ggunit) +S3method(vec_cast,simpleUnit.unit) S3method(vec_cast,unit.ggunit) +S3method(vec_cast,unit.simpleUnit) +S3method(vec_cast,unit.unit) S3method(vec_proxy,simpleUnit) S3method(vec_proxy,unit) S3method(vec_ptype2,character.mapped_discrete) @@ -181,7 +185,10 @@ S3method(vec_ptype2,mapped_discrete.factor) S3method(vec_ptype2,mapped_discrete.integer) S3method(vec_ptype2,mapped_discrete.mapped_discrete) S3method(vec_ptype2,simpleUnit.ggunit) +S3method(vec_ptype2,simpleUnit.unit) S3method(vec_ptype2,unit.ggunit) +S3method(vec_ptype2,unit.simpleUnit) +S3method(vec_ptype2,unit.unit) S3method(vec_restore,ggunit) S3method(vec_restore,unit) S3method(widthDetails,titleGrob) diff --git a/R/ggunit.R b/R/ggunit.R index 46231d5545..764dc4f1d2 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -1,13 +1,8 @@ # constructors ------------------------------------------------------------ -null_unit <- function() { - # grid::unit() doesn't allow zero-length vectors, - # so we have to do this manually - structure(list(), class = c("unit", "unit_v2")) -} - new_ggunit <- function(x = null_unit()) { - class(x) <- c("ggunit", setdiff(class(x), c("ggunit", "vctrs_vctr")), "vctrs_vctr") + x <- vec_cast(x, null_unit()) + class(x) <- c("ggunit", class(x), "vctrs_vctr") x } @@ -63,7 +58,9 @@ Ops.ggunit <- function(x, y) { y <- vec_cast(y, new_ggunit()) } } - if (.Generic == "==") { + + if (.Generic %in% c("==", ">", "<", ">=", "<=")) { + f <- match.fun(.Generic) len <- max(length(x), length(y)) out <- logical(len) x <- rep_len(x, len) @@ -71,19 +68,31 @@ Ops.ggunit <- function(x, y) { type_x = unitType(x) type_y = unitType(y) - is_same_atomic <- type_x == type_y & !type_x %in% c("sum", "min", "max") - out[is_same_atomic] <- as.numeric(x[is_same_atomic]) == as.numeric(y[is_same_atomic]) - - # determine equality in otherwise incomparable units where possible (unequal signs, 0s, Infs) - x_not_atomic <- x[!is_same_atomic] - y_not_atomic <- y[!is_same_atomic] - sign_x_not_atomic <- sign(x_not_atomic) - sign_y_not_atomic <- sign(y_not_atomic) - out[!is_same_atomic] <- sign_x_not_atomic == sign_y_not_atomic & - ifelse(sign_x_not_atomic == 0 | (is.infinite(x_not_atomic) & is.infinite(y_not_atomic)), TRUE, NA) + is_same_type <- type_x == type_y & !type_x %in% c("sum", "min", "max") + out[is_same_type] <- f(as.numeric(x[is_same_type]), as.numeric(y[is_same_type])) + + # determine relationships in otherwise incomparable units where possible (unequal signs, 0s, Infs) + not_same_type <- !is_same_type + x_not_same_type <- x[not_same_type] + y_not_same_type <- y[not_same_type] + sign_x_not_same_type <- sign(x_not_same_type) + sign_y_not_same_type <- sign(y_not_same_type) + out[not_same_type] <- switch(.Generic, + "==" = + sign_x_not_same_type == sign_y_not_same_type & + ifelse(sign_x_not_same_type == 0 | (is.infinite(x_not_same_type) & is.infinite(y_not_same_type)), TRUE, NA), + "<" =, ">" = + ifelse(sign_x_not_same_type == sign_y_not_same_type, + ifelse(sign_x_not_same_type == 0, FALSE, NA), + f(sign_x_not_same_type, sign_y_not_same_type) + ), + "<=" = x_not_same_type < y_not_same_type | x_not_same_type == y_not_same_type, + ">=" = x_not_same_type > y_not_same_type | x_not_same_type == y_not_same_type + ) } else { out <- new_ggunit(NextMethod()) } + return(out) } @@ -138,11 +147,16 @@ is.finite.ggunit <- function(x) { ggunit_math_function(x, logical, atomic_f = is.finite, sum_f = function(x) all(is.finite(x)), - min_f = function(x) any(is.finite(x)) && all(is_pos_Inf(x[!is_finite])), - max_f = function(x) any(is.finite(x)) && all(is_neg_Inf(x[!is_finite])) + min_f = function(x) any(is.finite(x)) && all(is_pos_Inf(x[!is.finite(x)])), + max_f = function(x) any(is.finite(x)) && all(is_neg_Inf(x[!is.finite(x)])) ) } +#' @export +is.na.ggunit <- function(x) { + ggunit_math_function(x, logical, atomic_f = is.na, sum_f = anyNA, min_f = anyNA, max_f = anyNA) +} + #' @export sign.ggunit <- function(x) { ggunit_math_function(x, numeric, @@ -153,11 +167,11 @@ sign.ggunit <- function(x) { }, min_f = function(x) { sign_x <- sign(x) - if (isTRUE(any(sign_x == -1))) -1 else min(sign(x)) + if (isTRUE(any(sign_x == -1))) -1 else min(sign_x) }, max_f = function(x) { sign_x <- sign(x) - if (isTRUE(any(sign(x) == 1))) 1 else max(sign(x)) + if (isTRUE(any(sign_x == 1))) 1 else max(sign_x) } ) } diff --git a/R/utilities-unit.R b/R/utilities-unit.R index ea89a07f75..b3c5ebf6a0 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -11,31 +11,13 @@ transform_native_units <- function(x, trans, ...) { x } -transform_unit_recursively = function(x, trans, ...) { - is_native <- unitType(x) == "native" - if (any(is_native)) { - x[is_native] <- unit(trans(as.numeric(x[is_native]), ...), "native") - } - - is_recursive <- unitType(x) %in% c("sum", "min", "max") - if (any(is_recursive)) { - x[is_recursive] <- do.call(unit.c, lapply(x[is_recursive], function(x_i) { - unit_components(x_i) <- transform_unit_recursively(unit_components(x_i), trans, ...) - })) - } - - x -} - unit_components <- function(x) { unclass(x)[[1]][[2]] } `unit_components<-` <- function(x, value) { - if (inherits(value, "simpleUnit")) { - # force the value to be a list form of unit, not a numeric vector form - value <- vec_restore(vec_proxy(value), new_ggunit()) - } + # force the value to be a list form of unit, not a simpleUnit + x <- vec_cast(x, null_unit()) oldclass <- class(x) x <- unclass(x) x[[1]][[2]] <- value @@ -49,7 +31,7 @@ collapse_native_units <- function(x) { is_recursive <- type %in% c("sum", "min", "max") x[is_recursive] <- .mapply(list(x[is_recursive], type[is_recursive]), NULL, FUN = function(x_i, f) { f <- match.fun(f) - components <- unit_components(x_i) + components <- collapse_native_units(unit_components(x_i)) is_native <- unitType(components) == "native" if (any(is_native)) { x_i <- f(ggunit(f(as.numeric(components[is_native]))), components[!is_native]) @@ -60,7 +42,8 @@ collapse_native_units <- function(x) { } native_units <- function(x) { - x <- vec_cast(x, new_ggunit()) + if (is.numeric(x)) return(x) + if (!is.unit(x)) stop_input_type(x, as_cli("a {.cls unit} or a {.cls numeric}")) .get_native_units(x)$values } @@ -69,7 +52,9 @@ native_units <- function(x) { type <- unitType(x) is_native <- type == "native" - values[is_native] <- as.numeric(x[is_native]) + if (any(is_native)) { + values[is_native] <- as.numeric(x[is_native]) + } is_recursive <- unitType(x) %in% c("sum", "min", "max") if (any(is_recursive)) { @@ -89,6 +74,8 @@ native_units <- function(x) { } `native_units<-` <- function(x, values) { + if (is.numeric(x)) return(values) + if (!is.unit(x)) stop_input_type(x, as_cli("a {.cls unit} or a {.cls numeric}")) .set_native_units(x, values)$x } @@ -165,6 +152,7 @@ vec_restore.unit <- function(x, ...) { # replace NAs (NULL entries) with unit's version of NA is_na <- vapply(x, is.null, logical(1)) x[is_na] <- vec_proxy(unit(NA_real_, "native")) + class(x) <- c("unit", "unit_v2") x } @@ -177,3 +165,27 @@ vec_proxy.simpleUnit <- function(x, ...) { type <- attr(x, "unit") lapply(unclass(x), function(x_i) list(x_i, NULL, type)) } + + + +# casting ----------------------------------------------------------------- + +null_unit <- function() { + # grid::unit() doesn't allow zero-length vectors, + # so we have to do this manually + structure(list(), class = c("unit", "unit_v2")) +} + +#' @export +vec_ptype2.unit.unit <- function(x, y, ...) null_unit() +#' @export +vec_ptype2.unit.simpleUnit <- function(x, y, ...) null_unit() +#' @export +vec_ptype2.simpleUnit.unit <- function(x, y, ...) null_unit() + +#' @export +vec_cast.unit.unit <- function(x, to, ...) x +#' @export +vec_cast.unit.simpleUnit <- function(x, to, ...) vec_restore(vec_proxy(x), null_unit()) +#' @export +vec_cast.simpleUnit.unit <- function(x, to, ...) vec_restore(vec_proxy(x), null_unit()) From 6b095fa903fc8d2a1b36e97dedda518eac0b6375 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Thu, 21 Dec 2023 21:18:02 -0600 Subject: [PATCH 09/16] expose/hide units in the pipeline the same way AsIs is hidden --- R/plot-build.R | 8 ++--- R/utilities-unit.R | 77 +++++++++++++++++++++++++++------------------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/R/plot-build.R b/R/plot-build.R index 700260c281..3999bf865b 100644 --- a/R/plot-build.R +++ b/R/plot-build.R @@ -51,7 +51,7 @@ ggplot_build.ggplot <- function(plot) { # Compute aesthetics to produce data with generalised variable names data <- by_layer(function(l, d) l$compute_aesthetics(d, plot), layers, data, "computing aesthetics") - data <- .ignore_data(data) + data <- .ignore_units(.ignore_data(data)) # Transform all scales data <- lapply(data, scales$transform_df) @@ -63,7 +63,7 @@ ggplot_build.ggplot <- function(plot) { layout$train_position(data, scale_x(), scale_y()) data <- layout$map_position(data) - data <- .expose_data(data) + data <- .expose_units(.expose_data(data)) # Apply and map statistics data <- by_layer(function(l, d) l$compute_statistic(d, layout), layers, data, "computing stat") @@ -81,7 +81,7 @@ ggplot_build.ggplot <- function(plot) { # Reset position scales, then re-train and map. This ensures that facets # have control over the range of a plot: is it generated from what is # displayed, or does it include the range of underlying data - data <- .ignore_data(data) + data <- .ignore_units(.ignore_data(data)) layout$reset_scales() layout$train_position(data, scale_x(), scale_y()) layout$setup_panel_params() @@ -100,7 +100,7 @@ ggplot_build.ggplot <- function(plot) { # Only keep custom guides if there are no non-position scales plot$guides <- plot$guides$get_custom() } - data <- .expose_data(data) + data <- .expose_units(.expose_data(data)) # Fill in defaults etc. data <- by_layer(function(l, d) l$compute_geom_2(d), layers, data, "setting up geom aesthetics") diff --git a/R/utilities-unit.R b/R/utilities-unit.R index b3c5ebf6a0..6e6f1cfe45 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -18,6 +18,7 @@ unit_components <- function(x) { `unit_components<-` <- function(x, value) { # force the value to be a list form of unit, not a simpleUnit x <- vec_cast(x, null_unit()) + oldclass <- class(x) x <- unclass(x) x[[1]][[2]] <- value @@ -93,7 +94,7 @@ native_units <- function(x) { is_recursive <- unitType(x) %in% c("sum", "min", "max") if (any(is_recursive)) { for (i in which(is_recursive)) { - out <- .set_native_units(unit_components(x), values[[i]]) + out <- .set_native_units(unit_components(x[[i]]), values[[i]]) native_i <- which(out$is_native) if (length(native_i) > 1) { cli::cli_abort("More than one native unit in {x[[i]]}") @@ -107,41 +108,56 @@ native_units <- function(x) { list(x = x, is_native = is_native) } -.ignore_units <- function(df, cols = c(ggplot_global$x_aes, ggplot_global$y_aes)) { - if (is.null(cols)) { - is_selected <- TRUE - } else { - is_selected <- names(df) %in% cols - } - is_unit <- vapply(df, is.unit, logical(1)) & is_selected - if (!any(is_unit)) { - return(df) +.ignore_units <- function(data, cols = c(ggplot_global$x_aes, ggplot_global$y_aes)) { + if (is.data.frame(data)) { + return(.ignore_units(list(data), cols)[[1]]) } - df <- unclass(df) - # We trust that 'df' is a valid data.frame with equal length columns etc, - # so we can use the more performant `new_data_frame()` - unit_cols <- lapply(df[is_unit], collapse_native_units) - new_data_frame(c( - df[!is_unit], - lapply(unit_cols, native_units), - list(.ignored_units = new_data_frame(unit_cols)) - )) + + lapply(data, function(df) { + if (is.null(cols)) { + is_selected <- TRUE + } else { + is_selected <- names(df) %in% cols + } + is_unit <- vapply(df, is.unit, logical(1)) & is_selected + if (!any(is_unit)) { + return(df) + } + df <- unclass(df) + # We trust that 'df' is a valid data.frame with equal length columns etc, + # so we can use the more performant `new_data_frame()` + unit_cols <- lapply(df[is_unit], collapse_native_units) + new_data_frame(c( + df[!is_unit], + lapply(unit_cols, native_units), + list(.ignored_units = new_data_frame(unit_cols)) + )) + }) } -.expose_units <- function(df) { - is_ignored <- which(names(df) == ".ignored_units") - if (length(is_ignored) == 0) { - return(df) +.expose_units <- function(data) { + if (is.data.frame(data)) { + return(.expose_units(list(data))[[1]]) } - unit_col_names <- intersect(names(df), names(df[[is_ignored[1]]])) - is_unit <- which(names(df) %in% unit_col_names) - df <- unclass(df) - new_data_frame(c( - df[-c(is_ignored, is_unit)], - mapply(`native_units<-`, df[[is_ignored[1]]][unit_col_names], df[unit_col_names], SIMPLIFY = FALSE) - )) + + lapply(data, function(df) { + is_ignored <- which(names(df) == ".ignored_units") + if (length(is_ignored) == 0) { + return(df) + } + unit_col_names <- intersect(names(df), names(df[[is_ignored[1]]])) + is_unit <- which(names(df) %in% unit_col_names) + df <- unclass(df) + new_data_frame(c( + df[-c(is_ignored, is_unit)], + mapply(`native_units<-`, df[[is_ignored[1]]][unit_col_names], df[unit_col_names], SIMPLIFY = FALSE) + )) + }) } + +# proxies ----------------------------------------------------------------- + #' @export vec_proxy.unit <- function(x, ...) { unclass(x) @@ -167,7 +183,6 @@ vec_proxy.simpleUnit <- function(x, ...) { } - # casting ----------------------------------------------------------------- null_unit <- function() { From 21ddfd6d899cfc10ae48f01e9a6798ef3554db4e Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Thu, 21 Dec 2023 21:18:15 -0600 Subject: [PATCH 10/16] ggunit_pmin and ggunit_pmax --- R/ggunit.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/R/ggunit.R b/R/ggunit.R index 764dc4f1d2..32d2f9a32e 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -176,6 +176,16 @@ sign.ggunit <- function(x) { ) } +ggunit_pmin <- function(...) { + dots <- vec_cast(list(...), list_of(new_ggunit())) + vec_cast(.mapply(min, dots, NULL), new_ggunit()) +} + +ggunit_pmax <- function(...) { + dots <- vec_cast(list(...), list_of(new_ggunit())) + vec_cast(.mapply(max, dots, NULL), new_ggunit()) +} + # assignment -------------------------------------------------------------- From 8cd2246dbba151472cf8568a65ff57f3b018be15 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Thu, 21 Dec 2023 22:25:38 -0600 Subject: [PATCH 11/16] provide a rescale.unit implementation instead of sprinkling transform_native_units everywhere --- NAMESPACE | 1 + R/geom-point.R | 4 ++-- R/ggunit.R | 5 ----- R/position-.R | 4 ++-- R/scale-.R | 6 +++--- R/utilities-unit.R | 22 ++++++++++++++++------ 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index f8b3d4d3ea..0b1f13cd89 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -123,6 +123,7 @@ S3method(print,ggunit) S3method(print,rel) S3method(print,theme) S3method(print,uneval) +S3method(rescale,unit) S3method(scale_type,AsIs) S3method(scale_type,Date) S3method(scale_type,POSIXt) diff --git a/R/geom-point.R b/R/geom-point.R index b6715ceefc..f014853eac 100755 --- a/R/geom-point.R +++ b/R/geom-point.R @@ -125,12 +125,12 @@ GeomPoint <- ggproto("GeomPoint", Geom, stroke_size <- coords$stroke if (!is.unit(stroke_size)) stroke_size <- unit(stroke_size * .stroke, "pt") - stroke_size <- transform_native_units(stroke_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) + native_units(stroke_size) <- rescale(native_units(stroke_size), from = c(0, diff(coord$range(panel_params)$x))) stroke_size[is.na(stroke_size)] <- unit(0, "pt") font_size <- coords$size if (!is.unit(font_size)) font_size <- unit(font_size * .pt, "pt") - font_size <- transform_native_units(font_size, rescale, from = c(0, diff(coord$range(panel_params)$x))) + native_units(font_size) <- rescale(native_units(font_size), from = c(0, diff(coord$range(panel_params)$x))) ggname("geom_point", ggplot2_pointsGrob( diff --git a/R/ggunit.R b/R/ggunit.R index 32d2f9a32e..08dd49e5d3 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -45,11 +45,6 @@ is_ggunit <- function(x) { # math -------------------------------------------------------------------- -# #' @export -# Math.ggunit <- function(x, ...) { -# transform_native_units(x, match.fun(.Generic), ...) -# } - #' @export Ops.ggunit <- function(x, y) { if (!(.Generic %in% c("*", "/"))) { diff --git a/R/position-.R b/R/position-.R index 2d5ab2659b..23d66579b4 100755 --- a/R/position-.R +++ b/R/position-.R @@ -81,10 +81,10 @@ transform_position <- function(df, trans_x = NULL, trans_y = NULL, ...) { scales <- aes_to_scale(names(df)) if (!is.null(trans_x)) { - df[scales == "x"] <- lapply(df[scales == "x"], function(x) transform_native_units(x, trans_x, ...)) + df[scales == "x"] <- lapply(df[scales == "x"], trans_x, ...) } if (!is.null(trans_y)) { - df[scales == "y"] <- lapply(df[scales == "y"], function(y) transform_native_units(y, trans_y, ...)) + df[scales == "y"] <- lapply(df[scales == "y"], trans_y, ...) } class(df) <- oldclass diff --git a/R/scale-.R b/R/scale-.R index 822d6977d0..d4776ca5ea 100755 --- a/R/scale-.R +++ b/R/scale-.R @@ -608,8 +608,8 @@ check_breaks_labels <- function(breaks, labels, call = NULL) { default_transform <- function(self, x) { transformation <- self$get_transformation() - new_x <- transform_native_units(x, transformation$transform) - if (!is.unit(x)) check_transformation(x, new_x, self$transformation$name, call = self$call) + new_x <- transformation$transform(x) + check_transformation(x, new_x, self$transformation$name, call = self$call) new_x } @@ -667,7 +667,7 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, }, rescale = function(self, x, limits = self$get_limits(), range = limits) { - transform_native_units(x, self$rescaler, from = range) + self$rescaler(x, from = range) }, get_limits = function(self) { diff --git a/R/utilities-unit.R b/R/utilities-unit.R index 6e6f1cfe45..37723c29b9 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -43,8 +43,10 @@ collapse_native_units <- function(x) { } native_units <- function(x) { - if (is.numeric(x)) return(x) - if (!is.unit(x)) stop_input_type(x, as_cli("a {.cls unit} or a {.cls numeric}")) + if (!is.unit(x)) { + if (is.numeric(x)) return(x) + stop_input_type(x, as_cli("a {.cls unit} or a {.cls numeric}")) + } .get_native_units(x)$values } @@ -75,8 +77,10 @@ native_units <- function(x) { } `native_units<-` <- function(x, values) { - if (is.numeric(x)) return(values) - if (!is.unit(x)) stop_input_type(x, as_cli("a {.cls unit} or a {.cls numeric}")) + if (!is.unit(x)) { + if (is.numeric(x)) return(values) + stop_input_type(x, as_cli("a {.cls unit} or a {.cls numeric}")) + } .set_native_units(x, values)$x } @@ -124,8 +128,6 @@ native_units <- function(x) { return(df) } df <- unclass(df) - # We trust that 'df' is a valid data.frame with equal length columns etc, - # so we can use the more performant `new_data_frame()` unit_cols <- lapply(df[is_unit], collapse_native_units) new_data_frame(c( df[!is_unit], @@ -156,6 +158,14 @@ native_units <- function(x) { } +# rescale ----------------------------------------------------------------- + +#' @export +rescale.unit <- function(x, to, from, ...) { + native_units(x) <- rescale(native_units(x), to, from, ...) + x +} + # proxies ----------------------------------------------------------------- #' @export From 130866b7b0b60ff5a3a22fc74b7133a7a27fa671 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Sat, 23 Dec 2023 12:04:55 -0600 Subject: [PATCH 12/16] collapse native units in ggunit sums automatically --- R/ggunit.R | 13 +++++++++++-- R/utilities-unit.R | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/R/ggunit.R b/R/ggunit.R index 08dd49e5d3..9a8618ecab 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -88,7 +88,11 @@ Ops.ggunit <- function(x, y) { out <- new_ggunit(NextMethod()) } - return(out) + if (.Generic %in% c("+", "-")) { + out <- collapse_native_units(out) + } + + out } #' @export @@ -101,7 +105,12 @@ chooseOpsMethod.ggunit = function(x, y, mx, my, cl, reverse) { Summary.ggunit <- function(..., na.rm = FALSE) { ggunits <- vec_cast_common(..., .to = new_ggunit()) units <- vec_cast(ggunits, list_of(null_unit())) - out <- do.call(.Generic, c(units, list(na.rm = na.rm))) + args <- c(units, list(na.rm = na.rm)) + out <- switch(.Generic, + range = vec_c(do.call(min, args), do.call(max, args)), + sum =, min =, max = collapse_native_units(do.call(.Generic, args)), + do.call(.Generic, args) + ) new_ggunit(out) } diff --git a/R/utilities-unit.R b/R/utilities-unit.R index 37723c29b9..bfa4268274 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -35,7 +35,7 @@ collapse_native_units <- function(x) { components <- collapse_native_units(unit_components(x_i)) is_native <- unitType(components) == "native" if (any(is_native)) { - x_i <- f(ggunit(f(as.numeric(components[is_native]))), components[!is_native]) + x_i <- f(unit(f(as.numeric(components[is_native])), "native"), components[!is_native]) } x_i }) From 40d71b35513e5cdfb72e8ee25ce96d43e0db338a Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Sat, 23 Dec 2023 12:05:26 -0600 Subject: [PATCH 13/16] ignore units during position adjustments * must also change how df_rows() works so that it can slice nested data frames correctly --- R/performance.R | 2 +- R/plot-build.R | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/performance.R b/R/performance.R index b26b1a7072..dff75d5de1 100644 --- a/R/performance.R +++ b/R/performance.R @@ -13,7 +13,7 @@ mat_2_df <- function(x, col_names = colnames(x)) { df_col <- function(x, name) .subset2(x, name) df_rows <- function(x, i) { - cols <- lapply(x, `[`, i = i) + cols <- lapply(x, vec_slice, i = i) data_frame0(!!!cols, .size = length(i)) } diff --git a/R/plot-build.R b/R/plot-build.R index 3999bf865b..7b2f271f76 100644 --- a/R/plot-build.R +++ b/R/plot-build.R @@ -51,7 +51,7 @@ ggplot_build.ggplot <- function(plot) { # Compute aesthetics to produce data with generalised variable names data <- by_layer(function(l, d) l$compute_aesthetics(d, plot), layers, data, "computing aesthetics") - data <- .ignore_units(.ignore_data(data)) + data <- .ignore_data(.ignore_units(data)) # Transform all scales data <- lapply(data, scales$transform_df) @@ -76,12 +76,13 @@ ggplot_build.ggplot <- function(plot) { data <- by_layer(function(l, d) l$compute_geom_1(d), layers, data, "setting up geom") # Apply position adjustments + data <- .ignore_units(data) data <- by_layer(function(l, d) l$compute_position(d, layout), layers, data, "computing position") # Reset position scales, then re-train and map. This ensures that facets # have control over the range of a plot: is it generated from what is # displayed, or does it include the range of underlying data - data <- .ignore_units(.ignore_data(data)) + data <- .ignore_data(data) layout$reset_scales() layout$train_position(data, scale_x(), scale_y()) layout$setup_panel_params() From c6b2b6aec4107f8d0548566f7601c6f7791e8369 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Sat, 23 Dec 2023 12:07:18 -0600 Subject: [PATCH 14/16] ensure resolution() works with units --- R/utilities-resolution.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/utilities-resolution.R b/R/utilities-resolution.R index 28e54cd969..cf2486b83c 100644 --- a/R/utilities-resolution.R +++ b/R/utilities-resolution.R @@ -18,6 +18,9 @@ #' resolution(c(2, 10, 20, 50)) #' resolution(c(2L, 10L, 20L, 50L)) resolution <- function(x, zero = TRUE) { + if (is.unit(x)) { + x <- native_units(x) + } if (is.integer(x) || is_mapped_discrete(x) || zero_range(range(x, na.rm = TRUE))) { return(1) From 2146d06a821994745ea0d4db5045e62acc7b7a11 Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Sat, 23 Dec 2023 12:51:43 -0600 Subject: [PATCH 15/16] fix bug in collapse_native_units when sum has a multiple --- R/utilities-unit.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utilities-unit.R b/R/utilities-unit.R index bfa4268274..c5d3e6041f 100755 --- a/R/utilities-unit.R +++ b/R/utilities-unit.R @@ -35,7 +35,7 @@ collapse_native_units <- function(x) { components <- collapse_native_units(unit_components(x_i)) is_native <- unitType(components) == "native" if (any(is_native)) { - x_i <- f(unit(f(as.numeric(components[is_native])), "native"), components[!is_native]) + unit_components(x_i) <- vec_c(unit(f(as.numeric(components[is_native])), "native"), components[!is_native]) } x_i }) From 64920a24f743638e0c305c448aa9f02cbf16036f Mon Sep 17 00:00:00 2001 From: Matthew Kay Date: Sat, 23 Dec 2023 13:39:44 -0600 Subject: [PATCH 16/16] some casting fixes --- NAMESPACE | 3 +++ R/ggunit.R | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 0b1f13cd89..04c7faa034 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -147,11 +147,13 @@ S3method(vec_cast,character.mapped_discrete) S3method(vec_cast,double.ggunit) S3method(vec_cast,double.mapped_discrete) S3method(vec_cast,factor.mapped_discrete) +S3method(vec_cast,ggunit.character) S3method(vec_cast,ggunit.double) S3method(vec_cast,ggunit.ggunit) S3method(vec_cast,ggunit.integer) S3method(vec_cast,ggunit.list) S3method(vec_cast,ggunit.logical) +S3method(vec_cast,ggunit.mapped_discrete) S3method(vec_cast,ggunit.simpleUnit) S3method(vec_cast,ggunit.unit) S3method(vec_cast,integer.ggunit) @@ -159,6 +161,7 @@ S3method(vec_cast,integer.mapped_discrete) S3method(vec_cast,logical.ggunit) S3method(vec_cast,mapped_discrete.double) S3method(vec_cast,mapped_discrete.factor) +S3method(vec_cast,mapped_discrete.ggunit) S3method(vec_cast,mapped_discrete.integer) S3method(vec_cast,mapped_discrete.logical) S3method(vec_cast,mapped_discrete.mapped_discrete) diff --git a/R/ggunit.R b/R/ggunit.R index 9a8618ecab..30acd393e4 100755 --- a/R/ggunit.R +++ b/R/ggunit.R @@ -97,8 +97,8 @@ Ops.ggunit <- function(x, y) { #' @export chooseOpsMethod.ggunit = function(x, y, mx, my, cl, reverse) { - # TODO: something more comprehensive using vec_ptype2 - inherits(x, "ggunit") + # TODO: something more comprehensive? + is_ggunit(vec_ptype2(x, y)) == is_ggunit(x) } #' @export @@ -279,6 +279,12 @@ vec_cast.ggunit.logical <- function(x, to, ...) ggunit(x) #' @export vec_cast.logical.ggunit <- function(x, to, ...) as.logical(as.numeric(x)) #' @export +vec_cast.ggunit.mapped_discrete <- function(x, to, ...) ggunit(x) +#' @export +vec_cast.mapped_discrete.ggunit <- function(x, to, ...) new_mapped_discrete(as.numeric(x)) +#' @export +vec_cast.ggunit.character <- function(x, to, ...) stop_incompatible_cast(x, to, x_arg = "a", to_arg = "to") +#' @export vec_cast.ggunit.list <- function(x, to, ...) { is_na <- vapply(x, is.null, logical(1)) x[is_na] <- NA