diff --git a/NEWS.md b/NEWS.md index 9364b75a31..446c928ba3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,8 @@ all `values` on the legend instead, use `scale_*_manual(values = vals, limits = names(vals))`. (@teunbrand, @banfai, #4511, #4534) + +* `geom_contour()` now accepts a function in the `breaks` argument (@eliocamp, #4652). # ggplot2 3.3.5 This is a very small release focusing on fixing a couple of untenable issues diff --git a/R/geom-contour.r b/R/geom-contour.r index cc25a70da4..4cdb0cd234 100644 --- a/R/geom-contour.r +++ b/R/geom-contour.r @@ -19,9 +19,14 @@ #' @inheritParams geom_path #' @param bins Number of contour bins. Overridden by `binwidth`. #' @param binwidth The width of the contour bins. Overridden by `breaks`. -#' @param breaks Numeric vector to set the contour breaks. Overrides `binwidth` -#' and `bins`. By default, this is a vector of length ten with [pretty()] -#' breaks. +#' @param breaks One of: +#' - Numeric vector to set the contour breaks +#' - A function that takes the range of the data and binwidth as input +#' and returns breaks as output. A function can be created from a formula +#' (e.g. ~ fullseq(.x, .y)). +#' +#' Overrides `binwidth` and `bins`. By default, this is a vector of length +#' ten with [pretty()] breaks. #' @seealso [geom_density_2d()]: 2d density contours #' @export #' @examples diff --git a/R/stat-contour.r b/R/stat-contour.r index c96d69c673..695af32be9 100644 --- a/R/stat-contour.r +++ b/R/stat-contour.r @@ -144,10 +144,17 @@ StatContourFilled <- ggproto("StatContourFilled", Stat, #' @noRd #' contour_breaks <- function(z_range, bins = NULL, binwidth = NULL, breaks = NULL) { - if (!is.null(breaks)) { + breaks <- allow_lambda(breaks) + + if (is.numeric(breaks)) { return(breaks) } + breaks_fun <- fullseq + if (is.function(breaks)) { + breaks_fun <- breaks + } + # If no parameters set, use pretty bins if (is.null(bins) && is.null(binwidth)) { breaks <- pretty(z_range, 10) @@ -167,20 +174,20 @@ contour_breaks <- function(z_range, bins = NULL, binwidth = NULL, breaks = NULL) } binwidth <- diff(z_range) / (bins - 1) - breaks <- fullseq(z_range, binwidth) + breaks <- breaks_fun(z_range, binwidth) # Sometimes the above sequence yields one bin too few. # If this happens, try again. if (length(breaks) < bins + 1) { binwidth <- diff(z_range) / bins - breaks <- fullseq(z_range, binwidth) + breaks <- breaks_fun(z_range, binwidth) } return(breaks) } # if we haven't returned yet, compute breaks from binwidth - fullseq(z_range, binwidth) + breaks_fun(z_range, binwidth) } #' Compute isoband objects diff --git a/man/geom_contour.Rd b/man/geom_contour.Rd index 1277d9b84b..ae5b40a987 100644 --- a/man/geom_contour.Rd +++ b/man/geom_contour.Rd @@ -102,9 +102,16 @@ to the paired geom/stat.} \item{binwidth}{The width of the contour bins. Overridden by \code{breaks}.} -\item{breaks}{Numeric vector to set the contour breaks. Overrides \code{binwidth} -and \code{bins}. By default, this is a vector of length ten with \code{\link[=pretty]{pretty()}} -breaks.} +\item{breaks}{One of: +\itemize{ +\item Numeric vector to set the contour breaks +\item A function that takes the range of the data and binwidth as input +and returns breaks as output. A function can be created from a formula +(e.g. ~ fullseq(.x, .y)). +} + +Overrides \code{binwidth} and \code{bins}. By default, this is a vector of length +ten with \code{\link[=pretty]{pretty()}} breaks.} \item{lineend}{Line end style (round, butt, square).} diff --git a/man/geom_density_2d.Rd b/man/geom_density_2d.Rd index c0daf09211..d2961df949 100644 --- a/man/geom_density_2d.Rd +++ b/man/geom_density_2d.Rd @@ -99,9 +99,16 @@ a call to a position adjustment function.} \describe{ \item{\code{bins}}{Number of contour bins. Overridden by \code{binwidth}.} \item{\code{binwidth}}{The width of the contour bins. Overridden by \code{breaks}.} - \item{\code{breaks}}{Numeric vector to set the contour breaks. Overrides \code{binwidth} -and \code{bins}. By default, this is a vector of length ten with \code{\link[=pretty]{pretty()}} -breaks.} + \item{\code{breaks}}{One of: +\itemize{ +\item Numeric vector to set the contour breaks +\item A function that takes the range of the data and binwidth as input +and returns breaks as output. A function can be created from a formula +(e.g. ~ fullseq(.x, .y)). +} + +Overrides \code{binwidth} and \code{bins}. By default, this is a vector of length +ten with \code{\link[=pretty]{pretty()}} breaks.} }} \item{contour_var}{Character string identifying the variable to contour diff --git a/tests/testthat/test-stat-contour.R b/tests/testthat/test-stat-contour.R index 61df774756..a48a368931 100644 --- a/tests/testthat/test-stat-contour.R +++ b/tests/testthat/test-stat-contour.R @@ -32,7 +32,7 @@ test_that("contouring irregularly spaced data works", { expect_setequal(d8$y, c(2, 20/9, 16/9)) }) -test_that("contour breaks can be set manually and by bins and binwidth", { +test_that("contour breaks can be set manually and by bins and binwidth and a function", { range <- c(0, 1) expect_equal(contour_breaks(range), pretty(range, 10)) expect_identical(contour_breaks(range, breaks = 1:3), 1:3) @@ -40,6 +40,8 @@ test_that("contour breaks can be set manually and by bins and binwidth", { # shifting the range by 0.2 hits another execution branch in contour_breaks() expect_length(contour_breaks(range + 0.2, bins = 5), 6) expect_equal(resolution(contour_breaks(range, binwidth = 0.3)), 0.3) + expect_equal(contour_breaks(range), contour_breaks(range, breaks = fullseq)) + expect_equal(contour_breaks(range), contour_breaks(range, breaks = ~fullseq(.x, .y))) }) test_that("geom_contour_filled() and stat_contour_filled() result in identical layer data", {