1#' @export 2#' @examples 3#' ggplot(mpg, aes(displ, hwy)) + 4#' geom_point(alpha = 0.5, colour = "blue") 5#' 6#' ggplot(mpg, aes(displ, hwy)) + 7#' geom_point(colour = alpha("blue", 0.5)) 8scales::alpha 9 10"%||%" <- function(a, b) { 11 if (!is.null(a)) a else b 12} 13 14"%|W|%" <- function(a, b) { 15 if (!is.waive(a)) a else b 16} 17 18# Check required aesthetics are present 19# This is used by geoms and stats to give a more helpful error message 20# when required aesthetics are missing. 21# 22# @param character vector of required aesthetics 23# @param character vector of present aesthetics 24# @param name of object for error message 25# @keyword internal 26check_required_aesthetics <- function(required, present, name) { 27 if (is.null(required)) return() 28 29 required <- strsplit(required, "|", fixed = TRUE) 30 if (any(vapply(required, length, integer(1)) > 1)) { 31 required <- lapply(required, rep_len, 2) 32 required <- list( 33 vapply(required, `[`, character(1), 1), 34 vapply(required, `[`, character(1), 2) 35 ) 36 } else { 37 required <- list(unlist(required)) 38 } 39 missing_aes <- lapply(required, setdiff, present) 40 if (any(vapply(missing_aes, length, integer(1)) == 0)) return() 41 42 abort(glue( 43 "{name} requires the following missing aesthetics: ", 44 glue_collapse(lapply(missing_aes, glue_collapse, sep = ", ", last = " and "), sep = " or ") 45 )) 46} 47 48# Concatenate a named list for output 49# Print a `list(a=1, b=2)` as `(a=1, b=2)` 50# 51# @param list to concatenate 52# @keyword internal 53#X clist(list(a=1, b=2)) 54#X clist(par()[1:5]) 55clist <- function(l) { 56 paste(paste(names(l), l, sep = " = ", collapse = ", "), sep = "") 57} 58 59# Return unique columns 60# This is used for figuring out which columns are constant within a group 61# 62# @keyword internal 63uniquecols <- function(df) { 64 df <- df[1, sapply(df, function(x) length(unique(x)) == 1), drop = FALSE] 65 rownames(df) <- 1:nrow(df) 66 df 67} 68 69#' Convenience function to remove missing values from a data.frame 70#' 71#' Remove all non-complete rows, with a warning if `na.rm = FALSE`. 72#' ggplot is somewhat more accommodating of missing values than R generally. 73#' For those stats which require complete data, missing values will be 74#' automatically removed with a warning. If `na.rm = TRUE` is supplied 75#' to the statistic, the warning will be suppressed. 76#' 77#' @param df data.frame 78#' @param na.rm If true, will suppress warning message. 79#' @param vars Character vector of variables to check for missings in 80#' @param name Optional function name to improve error message. 81#' @param finite If `TRUE`, will also remove non-finite values. 82#' @keywords internal 83#' @export 84remove_missing <- function(df, na.rm = FALSE, vars = names(df), name = "", 85 finite = FALSE) { 86 if (!is.logical(na.rm)) { 87 abort("`na.rm` must be logical") 88 } 89 90 missing <- detect_missing(df, vars, finite) 91 92 if (any(missing)) { 93 df <- df[!missing, ] 94 if (!na.rm) { 95 if (name != "") name <- paste(" (", name, ")", sep = "") 96 str <- if (finite) "non-finite" else "missing" 97 warning_wrap( 98 "Removed ", sum(missing), " rows containing ", str, " values", name, "." 99 ) 100 } 101 } 102 103 df 104} 105detect_missing <- function(df, vars, finite = FALSE) { 106 vars <- intersect(vars, names(df)) 107 !cases(df[, vars, drop = FALSE], if (finite) is_finite else is_complete) 108} 109 110# Returns a logical vector of same length as nrow(x). If all data on a row 111# is finite (not NA, NaN, Inf, or -Inf) return TRUE; otherwise FALSE. 112cases <- function(x, fun) { 113 ok <- vapply(x, fun, logical(nrow(x))) 114 115 # Need a special case test when x has exactly one row, because rowSums 116 # doesn't respect dimensions for 1x1 matrices. vapply returns a vector (not 117 # a matrix when the input has one row. 118 if (is.vector(ok)) { 119 all(ok) 120 } else { 121 # Find all the rows where all are TRUE 122 rowSums(as.matrix(ok)) == ncol(x) 123 } 124} 125 126# Wrapper around is.finite to handle list cols 127is_finite <- function(x) { 128 if (typeof(x) == "list") { 129 !vapply(x, is.null, logical(1)) 130 } else { 131 is.finite(x) 132 } 133} 134 135is_complete <- function(x) { 136 if (typeof(x) == "list") { 137 !vapply(x, is.null, logical(1)) 138 } else { 139 !is.na(x) 140 } 141} 142 143 144#' Used in examples to illustrate when errors should occur. 145#' 146#' @param expr code to evaluate. 147#' @export 148#' @keywords internal 149#' @examples 150#' should_stop(stop("Hi!")) 151#' should_stop(should_stop("Hi!")) 152should_stop <- function(expr) { 153 res <- try(print(force(expr)), TRUE) 154 if (!inherits(res, "try-error")) { 155 abort("No error!") 156 } 157 invisible() 158} 159 160 161#' A waiver object. 162#' 163#' A waiver is a "flag" object, similar to `NULL`, that indicates the 164#' calling function should just use the default value. It is used in certain 165#' functions to distinguish between displaying nothing (`NULL`) and 166#' displaying a default value calculated elsewhere (`waiver()`) 167#' 168#' @export 169#' @keywords internal 170waiver <- function() structure(list(), class = "waiver") 171 172is.waive <- function(x) inherits(x, "waiver") 173 174 175rescale01 <- function(x) { 176 rng <- range(x, na.rm = TRUE) 177 (x - rng[1]) / (rng[2] - rng[1]) 178} 179 180binned_pal <- function(palette) { 181 function(x) { 182 palette(length(x)) 183 } 184} 185 186#' Give a deprecation error, warning, or message, depending on version number. 187#' 188#' This function is deprecated. 189#' 190#' @param version The last version of ggplot2 where this function was good 191#' (in other words, the last version where it was not deprecated). 192#' @param msg The message to print. 193#' @keywords internal 194#' @export 195gg_dep <- function(version, msg) { 196 .Deprecated() 197 v <- as.package_version(version) 198 cv <- utils::packageVersion("ggplot2") 199 text <- "{msg} (Defunct; last used in version {version})" 200 201 # If current major number is greater than last-good major number, or if 202 # current minor number is more than 1 greater than last-good minor number, 203 # give error. 204 if (cv[[1,1]] > v[[1,1]] || cv[[1,2]] > v[[1,2]] + 1) { 205 abort(glue(text)) 206 207 # If minor number differs by one, give warning 208 } else if (cv[[1,2]] > v[[1,2]]) { 209 warn(glue(text)) 210 211 # If only subminor number is greater, give message 212 } else if (cv[[1,3]] > v[[1,3]]) { 213 message(glue(text)) 214 } 215 216 invisible() 217} 218 219has_name <- function(x) { 220 nms <- names(x) 221 if (is.null(nms)) { 222 return(rep(FALSE, length(x))) 223 } 224 225 !is.na(nms) & nms != "" 226} 227 228# Use chartr() for safety since toupper() fails to convert i to I in Turkish locale 229lower_ascii <- "abcdefghijklmnopqrstuvwxyz" 230upper_ascii <- "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 231to_lower_ascii <- function(x) chartr(upper_ascii, lower_ascii, x) 232to_upper_ascii <- function(x) chartr(lower_ascii, upper_ascii, x) 233 234tolower <- function(x) { 235 abort("Please use `to_lower_ascii()`, which works fine in all locales.") 236} 237 238toupper <- function(x) { 239 abort("Please use `to_upper_ascii()`, which works fine in all locales.") 240} 241 242# Convert a snake_case string to camelCase 243camelize <- function(x, first = FALSE) { 244 x <- gsub("_(.)", "\\U\\1", x, perl = TRUE) 245 if (first) x <- firstUpper(x) 246 x 247} 248 249snakeize <- function(x) { 250 x <- gsub("([A-Za-z])([A-Z])([a-z])", "\\1_\\2\\3", x) 251 x <- gsub(".", "_", x, fixed = TRUE) 252 x <- gsub("([a-z])([A-Z])", "\\1_\\2", x) 253 to_lower_ascii(x) 254} 255 256firstUpper <- function(s) { 257 paste0(to_upper_ascii(substring(s, 1, 1)), substring(s, 2)) 258} 259 260snake_class <- function(x) { 261 snakeize(class(x)[1]) 262} 263 264empty <- function(df) { 265 is.null(df) || nrow(df) == 0 || ncol(df) == 0 || is.waive(df) 266} 267 268is.discrete <- function(x) { 269 is.factor(x) || is.character(x) || is.logical(x) 270} 271 272# This function checks that all columns of a dataframe `x` are data and returns 273# the names of any columns that are not. 274# We define "data" as atomic types or lists, not functions or otherwise. 275# The `inherits(x, "Vector")` check is for checking S4 classes from Bioconductor 276# and wether they can be expected to follow behavior typical of vectors. See 277# also #3835 278check_nondata_cols <- function(x) { 279 idx <- (vapply(x, function(x) { 280 is.null(x) || rlang::is_vector(x) || inherits(x, "Vector") 281 }, logical(1))) 282 names(x)[which(!idx)] 283} 284 285compact <- function(x) { 286 null <- vapply(x, is.null, logical(1)) 287 x[!null] 288} 289 290is.formula <- function(x) inherits(x, "formula") 291 292deparse2 <- function(x) { 293 y <- deparse(x, backtick = TRUE) 294 if (length(y) == 1) { 295 y 296 } else { 297 paste0(y[[1]], "...") 298 } 299} 300 301message_wrap <- function(...) { 302 msg <- paste(..., collapse = "", sep = "") 303 wrapped <- strwrap(msg, width = getOption("width") - 2) 304 message(paste0(wrapped, collapse = "\n")) 305} 306 307warning_wrap <- function(...) { 308 msg <- paste(..., collapse = "", sep = "") 309 wrapped <- strwrap(msg, width = getOption("width") - 2) 310 warn(glue_collapse(wrapped, "\n", last = "\n")) 311} 312 313var_list <- function(x) { 314 x <- encodeString(x, quote = "`") 315 if (length(x) > 5) { 316 x <- c(x[1:5], paste0("and ", length(x) - 5, " more")) 317 } 318 319 paste0(x, collapse = ", ") 320} 321 322dispatch_args <- function(f, ...) { 323 args <- list(...) 324 formals <- formals(f) 325 formals[names(args)] <- args 326 formals(f) <- formals 327 f 328} 329 330is_missing_arg <- function(x) identical(x, quote(expr = )) 331# Get all arguments in a function as a list. Will fail if an ellipsis argument 332# named .ignore 333# @param ... passed on in case enclosing function uses ellipsis in argument list 334find_args <- function(...) { 335 env <- parent.frame() 336 args <- names(formals(sys.function(sys.parent(1)))) 337 338 vals <- mget(args, envir = env) 339 vals <- vals[!vapply(vals, is_missing_arg, logical(1))] 340 341 modify_list(vals, list(..., `...` = NULL)) 342} 343 344# Used in annotations to ensure printed even when no 345# global data 346dummy_data <- function() new_data_frame(list(x = NA), n = 1) 347 348with_seed_null <- function(seed, code) { 349 if (is.null(seed)) { 350 code 351 } else { 352 withr::with_seed(seed, code) 353 } 354} 355 356seq_asc <- function(to, from) { 357 if (to > from) { 358 integer() 359 } else { 360 to:from 361 } 362} 363 364# Needed to trigger package loading 365#' @importFrom tibble tibble 366NULL 367 368# Check inputs with tibble but allow column vectors (see #2609 and #2374) 369as_gg_data_frame <- function(x) { 370 x <- lapply(x, validate_column_vec) 371 new_data_frame(x) 372} 373validate_column_vec <- function(x) { 374 if (is_column_vec(x)) { 375 dim(x) <- NULL 376 } 377 x 378} 379is_column_vec <- function(x) { 380 dims <- dim(x) 381 length(dims) == 2L && dims[[2]] == 1L 382} 383 384# Parse takes a vector of n lines and returns m expressions. 385# See https://github.com/tidyverse/ggplot2/issues/2864 for discussion. 386# 387# parse(text = c("alpha", "", "gamma")) 388# #> expression(alpha, gamma) 389# 390# parse_safe(text = c("alpha", "", "gamma")) 391# #> expression(alpha, NA, gamma) 392# 393parse_safe <- function(text) { 394 if (!is.character(text)) { 395 abort("`text` must be a character vector") 396 } 397 out <- vector("expression", length(text)) 398 for (i in seq_along(text)) { 399 expr <- parse(text = text[[i]]) 400 out[[i]] <- if (length(expr) == 0) NA else expr[[1]] 401 } 402 out 403} 404 405switch_orientation <- function(aesthetics) { 406 # We should have these as globals somewhere 407 x <- ggplot_global$x_aes 408 y <- ggplot_global$y_aes 409 x_aes <- match(aesthetics, x) 410 x_aes_pos <- which(!is.na(x_aes)) 411 y_aes <- match(aesthetics, y) 412 y_aes_pos <- which(!is.na(y_aes)) 413 if (length(x_aes_pos) > 0) { 414 aesthetics[x_aes_pos] <- y[x_aes[x_aes_pos]] 415 } 416 if (length(y_aes_pos) > 0) { 417 aesthetics[y_aes_pos] <- x[y_aes[y_aes_pos]] 418 } 419 aesthetics 420} 421 422#' Utilities for working with bidirectional layers 423#' 424#' These functions are what underpins the ability of certain geoms to work 425#' automatically in both directions. See the *Extending ggplot2* vignette for 426#' how they are used when implementing `Geom`, `Stat`, and `Position` classes. 427#' 428#' `has_flipped_aes()` is used to sniff out the orientation of the layer from 429#' the data. It has a range of arguments that can be used to finetune the 430#' sniffing based on what the data should look like. `flip_data()` will switch 431#' the column names of the data so that it looks like x-oriented data. 432#' `flipped_names()` provides a named list of aesthetic names that corresponds 433#' to the orientation of the layer. 434#' 435#' @section Controlling the sniffing: 436#' How the layer data should be interpreted depends on its specific features. 437#' `has_flipped_aes()` contains a range of flags for defining what certain 438#' features in the data correspond to: 439#' 440#' - `main_is_orthogonal`: This argument controls how the existence of only a `x` 441#' or `y` aesthetic is understood. If `TRUE` then the exisiting aesthetic 442#' would be then secondary axis. This behaviour is present in [stat_ydensity()] 443#' and [stat_boxplot()]. If `FALSE` then the exisiting aesthetic is the main 444#' axis as seen in e.g. [stat_bin()], [geom_count()], and [stat_density()]. 445#' - `range_is_orthogonal`: This argument controls whether the existance of 446#' range-like aesthetics (e.g. `xmin` and `xmax`) represents the main or 447#' secondary axis. If `TRUE` then the range is given for the secondary axis as 448#' seen in e.g. [geom_ribbon()] and [geom_linerange()]. 449#' - `group_has_equal`: This argument controls whether to test for equality of 450#' all `x` and `y` values inside each group and set the main axis to the one 451#' where all is equal. This test is only performed if `TRUE`, and only after 452#' less computationally heavy tests has come up empty handed. Examples are 453#' [stat_boxplot()] and [stat_ydensity] 454#' - `ambiguous`: This argument tells the function that the layer, while 455#' bidirectional, doesn't treat each axis differently. It will circumvent any 456#' data based guessing and only take hint from the `orientation` element in 457#' `params`. If this is not present it will fall back to `FALSE`. Examples are 458#' [geom_line()] and [geom_area()] 459#' - `main_is_continuous`: This argument controls how the test for discreteness 460#' in the scales should be interpreted. If `TRUE` then the main axis will be 461#' the one which is not discrete-like. Conversely, if `FALSE` the main axis 462#' will be the discrete-like one. Examples of `TRUE` is [stat_density()] and 463#' [stat_bin()], while examples of `FALSE` is [stat_ydensity()] and 464#' [stat_boxplot()] 465#' - `main_is_optional`: This argument controls the rare case of layers were the 466#' main direction is an optional aesthetic. This is only seen in 467#' [stat_boxplot()] where `x` is set to `0` if not given. If `TRUE` there will 468#' be a check for whether all `x` or all `y` are equal to `0` 469#' 470#' @param data The layer data 471#' @param params The parameters of the `Stat`/`Geom`. Only the `orientation` 472#' parameter will be used. 473#' @param main_is_orthogonal If only `x` or `y` are present do they correspond 474#' to the main orientation or the reverse. E.g. If `TRUE` and `y` is present 475#' it is not flipped. If `NA` this check will be ignored. 476#' @param range_is_orthogonal If `xmin`/`xmax` or `ymin`/`ymax` is present do 477#' they correspond to the main orientation or reverse. If `NA` this check will 478#' be ignored. 479#' @param group_has_equal Is it expected that grouped data has either a single 480#' `x` or `y` value that will correspond to the orientation. 481#' @param ambiguous Is the layer ambiguous in its mapping by nature. If so, it 482#' will only be flipped if `params$orientation == "y"` 483#' @param main_is_continuous If there is a discrete and continuous axis, does 484#' the continuous one correspond to the main orientation? 485#' @param main_is_optional Is the main axis aesthetic optional and, if not 486#' given, set to `0` 487#' @param flip Logical. Is the layer flipped. 488#' 489#' @return `has_flipped_aes()` returns `TRUE` if it detects a layer in the other 490#' orientation and `FALSE` otherwise. `flip_data()` will return the input 491#' unchanged if `flip = FALSE` and the data with flipped aesthetic names if 492#' `flip = TRUE`. `flipped_names()` returns a named list of strings. If 493#' `flip = FALSE` the name of the element will correspond to the element, e.g. 494#' `flipped_names(FALSE)$x == "x"` and if `flip = TRUE` it will correspond to 495#' the flipped name, e.g. `flipped_names(FALSE)$x == "y"` 496#' 497#' @export 498#' @keywords internal 499#' @name bidirection 500#' 501has_flipped_aes <- function(data, params = list(), main_is_orthogonal = NA, 502 range_is_orthogonal = NA, group_has_equal = FALSE, 503 ambiguous = FALSE, main_is_continuous = FALSE, 504 main_is_optional = FALSE) { 505 # Is orientation already encoded in data? 506 if (!is.null(data$flipped_aes)) { 507 not_na <- which(!is.na(data$flipped_aes)) 508 if (length(not_na) != 0) { 509 return(data$flipped_aes[[not_na[1L]]]) 510 } 511 } 512 513 # Is orientation requested in the params 514 if (!is.null(params$orientation) && !is.na(params$orientation)) { 515 return(params$orientation == "y") 516 } 517 518 x <- data$x %||% params$x 519 y <- data$y %||% params$y 520 xmin <- data$xmin %||% params$xmin 521 ymin <- data$ymin %||% params$ymin 522 xmax <- data$xmax %||% params$xmax 523 ymax <- data$ymax %||% params$ymax 524 525 # Does a single x or y aesthetic corespond to a specific orientation 526 if (!is.na(main_is_orthogonal) && xor(is.null(x), is.null(y))) { 527 return(is.null(y) == main_is_orthogonal) 528 } 529 530 has_x <- !is.null(x) 531 has_y <- !is.null(y) 532 533 # Does a provided range indicate an orientation 534 if (!is.na(range_is_orthogonal)) { 535 if (!is.null(ymin) || !is.null(ymax)) { 536 return(!range_is_orthogonal) 537 } 538 if (!is.null(xmin) || !is.null(xmax)) { 539 return(range_is_orthogonal) 540 } 541 } 542 543 # If ambiguous orientation = NA will give FALSE 544 if (ambiguous && (is.null(params$orientation) || is.na(params$orientation))) { 545 return(FALSE) 546 } 547 548 # Is there a single actual discrete position 549 y_is_discrete <- is_mapped_discrete(y) 550 x_is_discrete <- is_mapped_discrete(x) 551 if (xor(y_is_discrete, x_is_discrete)) { 552 return(y_is_discrete != main_is_continuous) 553 } 554 555 # Does each group have a single x or y value 556 if (group_has_equal) { 557 if (has_x) { 558 if (length(x) == 1) return(FALSE) 559 x_groups <- vapply(split(data$x, data$group), function(x) length(unique(x)), integer(1)) 560 if (all(x_groups == 1)) { 561 return(FALSE) 562 } 563 } 564 if (has_y) { 565 if (length(y) == 1) return(TRUE) 566 y_groups <- vapply(split(data$y, data$group), function(x) length(unique(x)), integer(1)) 567 if (all(y_groups == 1)) { 568 return(TRUE) 569 } 570 } 571 } 572 573 # default to no 574 FALSE 575} 576#' @rdname bidirection 577#' @export 578flip_data <- function(data, flip = NULL) { 579 flip <- flip %||% any(data$flipped_aes) %||% FALSE 580 if (isTRUE(flip)) { 581 names(data) <- switch_orientation(names(data)) 582 } 583 data 584} 585#' @rdname bidirection 586#' @export 587flipped_names <- function(flip = FALSE) { 588 x_aes <- ggplot_global$x_aes 589 y_aes <- ggplot_global$y_aes 590 if (flip) { 591 ret <- as.list(c(y_aes, x_aes)) 592 } else { 593 ret <- as.list(c(x_aes, y_aes)) 594 } 595 names(ret) <- c(x_aes, y_aes) 596 ret 597} 598 599split_with_index <- function(x, f, n = max(f)) { 600 if (n == 1) return(list(x)) 601 f <- as.integer(f) 602 attributes(f) <- list(levels = as.character(seq_len(n)), class = "factor") 603 unname(split(x, f)) 604} 605