diff --git a/DESCRIPTION b/DESCRIPTION index 4852fd28..e029311e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -40,4 +40,4 @@ Config/usethis/last-upkeep: 2025-04-23 Encoding: UTF-8 LazyLoad: yes Roxygen: list(markdown = TRUE, r6 = FALSE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 diff --git a/R/breaks.R b/R/breaks.R index c3e67493..2cbfdd72 100644 --- a/R/breaks.R +++ b/R/breaks.R @@ -19,9 +19,9 @@ #' #' @return #' All `breaks_()` functions return a function for generating breaks. These -#' functions takes, as their first argument a vector of values that represent +#' functions takes, as their first argument a vector of length 2 that represent #' the data range to provide breaks for. Some will optionally take a second -#' argument that allows you to specify the number of breaks to recieve. +#' argument that allows you to specify the number of breaks to receive. #' #' @export #' @examples @@ -62,6 +62,14 @@ breaks_width <- function(width, offset = 0) { force_all(width, offset) function(x) { + x <- suppressWarnings(range(x, na.rm = TRUE)) + x <- x[is.finite(x)] + if (length(x) == 0) { + return(x) + } + if (zero_range(as.numeric(x))) { + return(x[1]) + } x <- fullseq(x, width) for (i in offset) { x <- offset_by(x, i) @@ -92,13 +100,15 @@ breaks_width <- function(width, offset = 0) { breaks_extended <- function(n = 5, ...) { n_default <- n function(x, n = n_default) { + x <- suppressWarnings(range(x, na.rm = TRUE)) x <- x[is.finite(x)] if (length(x) == 0) { - return(numeric()) + return(x) } - - rng <- range(x) - labeling::extended(rng[1], rng[2], n, ...) + if (zero_range(as.numeric(x))) { + return(x[1]) + } + labeling::extended(x[1], x[2], n, ...) } } @@ -135,7 +145,12 @@ breaks_pretty <- function(n = 5, ...) { force_all(n, ...) n_default <- n function(x, n = n_default) { - if (length(x) > 0 && zero_range(range(as.numeric(x), na.rm = TRUE))) { + x <- suppressWarnings(range(x, na.rm = TRUE)) + x <- x[is.finite(x)] + if (length(x) == 0) { + return(x) + } + if (zero_range(as.numeric(x))) { return(x[1]) } breaks <- pretty(x, n, ...) @@ -180,8 +195,16 @@ breaks_timespan <- function( force(n) function(x) { x <- as.numeric(as.difftime(x, units = unit), units = "secs") - rng <- range(x) - diff <- rng[2] - rng[1] + x <- suppressWarnings(range(x, na.rm = TRUE)) + x <- x[is.finite(x)] + if (length(x) == 0) { + return(as.difftime(x, units = "secs")) + } + if (zero_range(x)) { + return(as.difftime(x[1], units = "secs")) + } + + diff <- x[2] - x[1] if (diff <= 2 * 60) { scale <- 1 @@ -195,10 +218,10 @@ breaks_timespan <- function( scale <- 604800 } - rng <- rng / scale + x <- x / scale breaks <- labeling::extended( - rng[1], - rng[2], + x[1], + x[2], n, Q = c(1, 2, 1.5, 4, 3), only.loose = FALSE @@ -228,7 +251,14 @@ breaks_exp <- function(n = 5, ...) { default <- extended_breaks(n = n_default, ...) function(x, n = n_default) { # Discard -Infs - x <- sort(pmax(x, 0)) + x <- suppressWarnings(range(pmax(x, 0), na.rm = TRUE)) + x <- x[is.finite(x)] + if (length(x) == 0) { + return(x) + } + if (zero_range(as.numeric(x))) { + return(x[1]) + } top <- floor(x[2]) if (top >= 3 && abs(diff(x)) >= 3) { unique(c(top - seq_len(min(top, n_default - 1)) + 1, 0)) diff --git a/man/breaks_exp.Rd b/man/breaks_exp.Rd index f90a9fd4..48db34f3 100644 --- a/man/breaks_exp.Rd +++ b/man/breaks_exp.Rd @@ -14,9 +14,9 @@ breaks that requested.} } \value{ All \code{breaks_()} functions return a function for generating breaks. These -functions takes, as their first argument a vector of values that represent +functions takes, as their first argument a vector of length 2 that represent the data range to provide breaks for. Some will optionally take a second -argument that allows you to specify the number of breaks to recieve. +argument that allows you to specify the number of breaks to receive. } \description{ This breaks function typically labels zero and the last \code{n - 1} integers of a diff --git a/man/breaks_extended.Rd b/man/breaks_extended.Rd index 15bcb68b..f24aff27 100644 --- a/man/breaks_extended.Rd +++ b/man/breaks_extended.Rd @@ -15,9 +15,9 @@ breaks that requested.} } \value{ All \code{breaks_()} functions return a function for generating breaks. These -functions takes, as their first argument a vector of values that represent +functions takes, as their first argument a vector of length 2 that represent the data range to provide breaks for. Some will optionally take a second -argument that allows you to specify the number of breaks to recieve. +argument that allows you to specify the number of breaks to receive. } \description{ Uses Wilkinson's extended breaks algorithm as implemented in the diff --git a/man/breaks_log.Rd b/man/breaks_log.Rd index ea28d2f9..704f5666 100644 --- a/man/breaks_log.Rd +++ b/man/breaks_log.Rd @@ -14,9 +14,9 @@ breaks_log(n = 5, base = 10) } \value{ All \code{breaks_()} functions return a function for generating breaks. These -functions takes, as their first argument a vector of values that represent +functions takes, as their first argument a vector of length 2 that represent the data range to provide breaks for. Some will optionally take a second -argument that allows you to specify the number of breaks to recieve. +argument that allows you to specify the number of breaks to receive. } \description{ This algorithm starts by looking for integer powers of \code{base}. If that diff --git a/man/breaks_pretty.Rd b/man/breaks_pretty.Rd index c1b46802..eff236b1 100644 --- a/man/breaks_pretty.Rd +++ b/man/breaks_pretty.Rd @@ -14,9 +14,9 @@ breaks that requested.} } \value{ All \code{breaks_()} functions return a function for generating breaks. These -functions takes, as their first argument a vector of values that represent +functions takes, as their first argument a vector of length 2 that represent the data range to provide breaks for. Some will optionally take a second -argument that allows you to specify the number of breaks to recieve. +argument that allows you to specify the number of breaks to receive. } \description{ Uses default R break algorithm as implemented in \code{\link[=pretty]{pretty()}}. This is diff --git a/man/breaks_timespan.Rd b/man/breaks_timespan.Rd index 8ca4b61c..0f05c865 100644 --- a/man/breaks_timespan.Rd +++ b/man/breaks_timespan.Rd @@ -14,9 +14,9 @@ breaks that requested.} } \value{ All \code{breaks_()} functions return a function for generating breaks. These -functions takes, as their first argument a vector of values that represent +functions takes, as their first argument a vector of length 2 that represent the data range to provide breaks for. Some will optionally take a second -argument that allows you to specify the number of breaks to recieve. +argument that allows you to specify the number of breaks to receive. } \description{ As timespan units span a variety of bases (1000 below seconds, 60 for second diff --git a/man/breaks_width.Rd b/man/breaks_width.Rd index 880d04f6..028d83f7 100644 --- a/man/breaks_width.Rd +++ b/man/breaks_width.Rd @@ -25,9 +25,9 @@ Note that due to way that dates are rounded, there's no guarantee that } \value{ All \code{breaks_()} functions return a function for generating breaks. These -functions takes, as their first argument a vector of values that represent +functions takes, as their first argument a vector of length 2 that represent the data range to provide breaks for. Some will optionally take a second -argument that allows you to specify the number of breaks to recieve. +argument that allows you to specify the number of breaks to receive. } \description{ Useful for numeric, date, and date-time scales. diff --git a/man/label_number.Rd b/man/label_number.Rd index 43d97de7..0b60d3eb 100644 --- a/man/label_number.Rd +++ b/man/label_number.Rd @@ -121,7 +121,7 @@ Use \code{label_number()} to force decimal display of numbers (i.e. don't use that inserts a comma every three digits. } \examples{ -\dontshow{if (getRversion() >= "3.5") (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (getRversion() >= "3.5") withAutoprint(\{ # examplesIf} demo_continuous(c(-1e6, 1e6)) demo_continuous(c(-1e6, 1e6), labels = label_number()) demo_continuous(c(-1e6, 1e6), labels = label_comma()) diff --git a/man/scales-package.Rd b/man/scales-package.Rd index 78f7ddd5..87f8df17 100644 --- a/man/scales-package.Rd +++ b/man/scales-package.Rd @@ -30,7 +30,7 @@ Authors: Other contributors: \itemize{ - \item Posit Software, PBC (03wc8by49) [copyright holder, funder] + \item Posit Software, PBC (\href{https://ror.org/03wc8by49}{ROR}) [copyright holder, funder] } } diff --git a/tests/testthat/_snaps/breaks.md b/tests/testthat/_snaps/breaks.md new file mode 100644 index 00000000..17ec0ddb --- /dev/null +++ b/tests/testthat/_snaps/breaks.md @@ -0,0 +1,51 @@ +# break functions deal with longer input + + Code + breaks_exp()(c(1, 10, 100)) + Output + [1] 100 99 98 97 0 + +--- + + Code + breaks_extended()(c(1, 10, 100)) + Output + [1] 0 25 50 75 100 + +--- + + Code + breaks_log()(c(1, 10, 100)) + Output + [1] 1 10 100 + +--- + + Code + breaks_pretty()(c(1, 10, 100)) + Output + [1] 0 20 40 60 80 100 + +--- + + Code + breaks_timespan()(as.difftime(c(1, 10, 10), units = "days")) + Output + Time differences in secs + [1] 0 172800 345600 518400 691200 864000 + +--- + + Code + breaks_width(10)(c(1, 10, 100)) + Output + [1] 0 10 20 30 40 50 60 70 80 90 100 + +--- + + Code + breaks_width("1 day")(as.Date(c("2000-01-01", "2000-01-03", "2000-01-05"))) + Output + [1] "2000-01-01" "2000-01-02" "2000-01-03" "2000-01-04" "2000-01-05" + [6] "2000-01-06" + diff --git a/tests/testthat/test-breaks.R b/tests/testthat/test-breaks.R index 0ceddca0..c85c59f8 100644 --- a/tests/testthat/test-breaks.R +++ b/tests/testthat/test-breaks.R @@ -48,3 +48,45 @@ test_that("exponential breaks give sensible values", { x <- breaks_exp()(c(0, 100)) expect_equal(x, c(100, 99, 98, 97, 0)) }) + +test_that("breaks functions deal with length 0 input", { + expect_equal(breaks_exp()(numeric()), numeric()) + expect_equal(breaks_extended()(numeric()), numeric()) + expect_equal(breaks_log()(numeric()), numeric()) + expect_equal(breaks_pretty()(numeric()), numeric()) + expect_equal( + breaks_timespan()(as.difftime(numeric(), units = "days")), + as.difftime(numeric(), units = "secs") + ) + expect_equal(breaks_width(1)(numeric()), numeric()) + expect_equal( + breaks_width("1 day")(as.Date(character())), + as.Date(character()) + ) +}) + +test_that("break functions deal with NA input", { + expect_equal(breaks_exp()(c(10, NA)), 10) + expect_equal(breaks_extended()(c(10, NA)), 10) + expect_equal(breaks_log()(c(10, NA)), 10) + expect_equal(breaks_pretty()(c(10, NA)), 10) + expect_equal( + breaks_timespan()(as.difftime(c(10, NA), units = "days")), + as.difftime(10 * 3600 * 24, units = "secs") + ) + expect_equal(breaks_width(1)(c(10, NA)), 10) + expect_equal( + breaks_width("1 day")(as.Date(c("2000-01-01", NA))), + as.Date("2000-01-01") + ) +}) + +test_that("break functions deal with longer input", { + expect_snapshot(breaks_exp()(c(1, 10, 100))) + expect_snapshot(breaks_extended()(c(1, 10, 100))) + expect_snapshot(breaks_log()(c(1, 10, 100))) + expect_snapshot(breaks_pretty()(c(1, 10, 100))) + expect_snapshot(breaks_timespan()(as.difftime(c(1, 10, 10), units = 'days'))) + expect_snapshot(breaks_width(10)(c(1, 10, 100))) + expect_snapshot(breaks_width("1 day")(as.Date(c("2000-01-01", "2000-01-03", "2000-01-05")))) +})