diff --git a/NEWS.md b/NEWS.md index 750af431e5..e2c6dc04fa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # ggplot2 (development version) +* `scale_x_continuous()` and `scale_y_continuous()` gains an `n.breaks` argument + guiding the number of automatic generated breaks (@thomasp85, #3102) + * `geom_sf()` now removes rows that can't be plotted due to `NA` aesthetics (#3546, @thomasp85) diff --git a/R/scale-.r b/R/scale-.r index f89292c5ec..480c771172 100644 --- a/R/scale-.r +++ b/R/scale-.r @@ -25,6 +25,10 @@ #' each major break) #' - A numeric vector of positions #' - A function that given the limits returns a vector of minor breaks. +#' @param n.breaks An integer guiding the number of major breaks. The algorithm +#' may choose a slightly different number to ensure nice break labels. Will +#' only have an effect if `breaks = waiver()`. Use `NULL` to use the default +#' number of breaks given by the transformation. #' @param labels One of: #' - `NULL` for no labels #' - `waiver()` for the default labels computed by the @@ -78,9 +82,11 @@ #' @param super The super class to use for the constructed scale #' @keywords internal continuous_scale <- function(aesthetics, scale_name, palette, name = waiver(), - breaks = waiver(), minor_breaks = waiver(), labels = waiver(), limits = NULL, - rescaler = rescale, oob = censor, expand = waiver(), na.value = NA_real_, - trans = "identity", guide = "legend", position = "left", super = ScaleContinuous) { + breaks = waiver(), minor_breaks = waiver(), n.breaks = NULL, + labels = waiver(), limits = NULL, rescaler = rescale, + oob = censor, expand = waiver(), na.value = NA_real_, + trans = "identity", guide = "legend", position = "left", + super = ScaleContinuous) { aesthetics <- standardise_aes_names(aesthetics) @@ -116,6 +122,7 @@ continuous_scale <- function(aesthetics, scale_name, palette, name = waiver(), name = name, breaks = breaks, minor_breaks = minor_breaks, + n.breaks = n.breaks, labels = labels, guide = guide, @@ -524,6 +531,7 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, rescaler = rescale, oob = censor, minor_breaks = waiver(), + n.breaks = NULL, is_discrete = function() FALSE, @@ -535,10 +543,10 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, }, transform = function(self, x) { - new_x <- self$trans$transform(x) - axis <- if ("x" %in% self$aesthetics) "x" else "y" - check_transformation(x, new_x, self$scale_name, axis) - new_x + new_x <- self$trans$transform(x) + axis <- if ("x" %in% self$aesthetics) "x" else "y" + check_transformation(x, new_x, self$scale_name, axis) + new_x }, map = function(self, x, limits = self$get_limits()) { @@ -578,7 +586,14 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, if (zero_range(as.numeric(limits))) { breaks <- limits[1] } else if (is.waive(self$breaks)) { - breaks <- self$trans$breaks(limits) + if (!is.null(self$n.breaks) && trans_support_nbreaks(self$trans)) { + breaks <- self$trans$breaks(limits, self$n.breaks) + } else { + if (!is.null(self$n.breaks)) { + warning("Ignoring n.breaks. Use a trans object that supports setting number of breaks", call. = FALSE) + } + breaks <- self$trans$breaks(limits) + } } else if (is.function(self$breaks)) { breaks <- self$breaks(limits) } else { @@ -952,7 +967,7 @@ ScaleBinned <- ggproto("ScaleBinned", Scale, stop("Invalid breaks specification. Use NULL, not NA", call. = FALSE) } else if (is.waive(self$breaks)) { if (self$nice.breaks) { - if (!is.null(self$n.breaks) && "n" %in% names(formals(self$trans$breaks))) { + if (!is.null(self$n.breaks) && trans_support_nbreaks(self$trans)) { breaks <- self$trans$breaks(limits, n = self$n.breaks) } else { if (!is.null(self$n.breaks)) { @@ -1090,3 +1105,7 @@ check_transformation <- function(x, transformed, name, axis) { warning("Transformation introduced infinite values in ", type, " ", axis, "-axis", call. = FALSE) } } + +trans_support_nbreaks <- function(trans) { + "n" %in% names(formals(trans$breaks)) +} diff --git a/R/scale-continuous.r b/R/scale-continuous.r index 8f1cfb1217..174e0680e7 100644 --- a/R/scale-continuous.r +++ b/R/scale-continuous.r @@ -74,13 +74,15 @@ NULL #' #' @export scale_x_continuous <- function(name = waiver(), breaks = waiver(), - minor_breaks = waiver(), labels = waiver(), - limits = NULL, expand = waiver(), oob = censor, - na.value = NA_real_, trans = "identity", guide = waiver(), - position = "bottom", sec.axis = waiver()) { + minor_breaks = waiver(), n.breaks = NULL, + labels = waiver(), limits = NULL, + expand = waiver(), oob = censor, + na.value = NA_real_, trans = "identity", + guide = waiver(), position = "bottom", + sec.axis = waiver()) { sc <- continuous_scale( c("x", "xmin", "xmax", "xend", "xintercept", "xmin_final", "xmax_final", "xlower", "xmiddle", "xupper", "x0"), - "position_c", identity, name = name, breaks = breaks, + "position_c", identity, name = name, breaks = breaks, n.breaks = n.breaks, minor_breaks = minor_breaks, labels = labels, limits = limits, expand = expand, oob = oob, na.value = na.value, trans = trans, guide = guide, position = position, super = ScaleContinuousPosition @@ -93,13 +95,15 @@ scale_x_continuous <- function(name = waiver(), breaks = waiver(), #' @rdname scale_continuous #' @export scale_y_continuous <- function(name = waiver(), breaks = waiver(), - minor_breaks = waiver(), labels = waiver(), - limits = NULL, expand = waiver(), oob = censor, - na.value = NA_real_, trans = "identity", guide = waiver(), - position = "left", sec.axis = waiver()) { + minor_breaks = waiver(), n.breaks = NULL, + labels = waiver(), limits = NULL, + expand = waiver(), oob = censor, + na.value = NA_real_, trans = "identity", + guide = waiver(), position = "left", + sec.axis = waiver()) { sc <- continuous_scale( c("y", "ymin", "ymax", "yend", "yintercept", "ymin_final", "ymax_final", "lower", "middle", "upper", "y0"), - "position_c", identity, name = name, breaks = breaks, + "position_c", identity, name = name, breaks = breaks, n.breaks = n.breaks, minor_breaks = minor_breaks, labels = labels, limits = limits, expand = expand, oob = oob, na.value = na.value, trans = trans, guide = guide, position = position, super = ScaleContinuousPosition diff --git a/man/continuous_scale.Rd b/man/continuous_scale.Rd index 6c5ee2a3fb..e47020117c 100644 --- a/man/continuous_scale.Rd +++ b/man/continuous_scale.Rd @@ -5,10 +5,11 @@ \title{Continuous scale constructor} \usage{ continuous_scale(aesthetics, scale_name, palette, name = waiver(), - breaks = waiver(), minor_breaks = waiver(), labels = waiver(), - limits = NULL, rescaler = rescale, oob = censor, - expand = waiver(), na.value = NA_real_, trans = "identity", - guide = "legend", position = "left", super = ScaleContinuous) + breaks = waiver(), minor_breaks = waiver(), n.breaks = NULL, + labels = waiver(), limits = NULL, rescaler = rescale, + oob = censor, expand = waiver(), na.value = NA_real_, + trans = "identity", guide = "legend", position = "left", + super = ScaleContinuous) } \arguments{ \item{aesthetics}{The names of the aesthetics that this scale works with.} @@ -44,6 +45,11 @@ each major break) \item A function that given the limits returns a vector of minor breaks. }} +\item{n.breaks}{An integer guiding the number of major breaks. The algorithm +may choose a slightly different number to ensure nice break labels. Will +only have an effect if \code{breaks = waiver()}. Use \code{NULL} to use the default +number of breaks given by the transformation.} + \item{labels}{One of: \itemize{ \item \code{NULL} for no labels diff --git a/man/scale_continuous.Rd b/man/scale_continuous.Rd index c853b8c83d..97847343ab 100644 --- a/man/scale_continuous.Rd +++ b/man/scale_continuous.Rd @@ -12,16 +12,16 @@ \title{Position scales for continuous data (x & y)} \usage{ scale_x_continuous(name = waiver(), breaks = waiver(), - minor_breaks = waiver(), labels = waiver(), limits = NULL, - expand = waiver(), oob = censor, na.value = NA_real_, - trans = "identity", guide = waiver(), position = "bottom", - sec.axis = waiver()) + minor_breaks = waiver(), n.breaks = NULL, labels = waiver(), + limits = NULL, expand = waiver(), oob = censor, + na.value = NA_real_, trans = "identity", guide = waiver(), + position = "bottom", sec.axis = waiver()) scale_y_continuous(name = waiver(), breaks = waiver(), - minor_breaks = waiver(), labels = waiver(), limits = NULL, - expand = waiver(), oob = censor, na.value = NA_real_, - trans = "identity", guide = waiver(), position = "left", - sec.axis = waiver()) + minor_breaks = waiver(), n.breaks = NULL, labels = waiver(), + limits = NULL, expand = waiver(), oob = censor, + na.value = NA_real_, trans = "identity", guide = waiver(), + position = "left", sec.axis = waiver()) scale_x_log10(...) @@ -60,6 +60,11 @@ each major break) \item A function that given the limits returns a vector of minor breaks. }} +\item{n.breaks}{An integer guiding the number of major breaks. The algorithm +may choose a slightly different number to ensure nice break labels. Will +only have an effect if \code{breaks = waiver()}. Use \code{NULL} to use the default +number of breaks given by the transformation.} + \item{labels}{One of: \itemize{ \item \code{NULL} for no labels diff --git a/man/scale_gradient.Rd b/man/scale_gradient.Rd index d59e93fff7..78793514a7 100644 --- a/man/scale_gradient.Rd +++ b/man/scale_gradient.Rd @@ -71,6 +71,10 @@ each major break) \item A numeric vector of positions \item A function that given the limits returns a vector of minor breaks. }} + \item{n.breaks}{An integer guiding the number of major breaks. The algorithm +may choose a slightly different number to ensure nice break labels. Will +only have an effect if \code{breaks = waiver()}. Use \code{NULL} to use the default +number of breaks given by the transformation.} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels diff --git a/man/scale_size.Rd b/man/scale_size.Rd index 31c659c4e7..21bd612dfc 100644 --- a/man/scale_size.Rd +++ b/man/scale_size.Rd @@ -86,8 +86,10 @@ transformation with \code{\link[scales:trans_new]{scales::trans_new()}}.} \item{guide}{A function used to create a guide or its name. See \code{\link[=guides]{guides()}} for more information.} -\item{n.breaks}{The number of break points to create if breaks are not given -directly.} +\item{n.breaks}{An integer guiding the number of major breaks. The algorithm +may choose a slightly different number to ensure nice break labels. Will +only have an effect if \code{breaks = waiver()}. Use \code{NULL} to use the default +number of breaks given by the transformation.} \item{nice.breaks}{Logical. Should breaks be attempted placed at nice values instead of exactly evenly spaced between the limits. If \code{TRUE} (default) @@ -118,6 +120,10 @@ each major break) \item A numeric vector of positions \item A function that given the limits returns a vector of minor breaks. }} + \item{n.breaks}{An integer guiding the number of major breaks. The algorithm +may choose a slightly different number to ensure nice break labels. Will +only have an effect if \code{breaks = waiver()}. Use \code{NULL} to use the default +number of breaks given by the transformation.} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels