1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
#' Set scale limits
#'
#' This is a shortcut for supplying the `limits` argument to the individual
#' scales. By default, any values outside the limits specified are replaced with
#' `NA`. Be warned that this will remove data outside the limits and this can
#' produce unintended results. For changing x or y axis limits \strong{without}
#' dropping data observations, see [coord_cartesian()].
#'
#' @param ... For `xlim()` and `ylim()`: Two numeric values, specifying the left/lower
#' limit and the right/upper limit of the scale. If the larger value is given first,
#' the scale will be reversed. You can leave one value as `NA` if you want to compute
#' the corresponding limit from the range of the data.
#'
#' For `lims()`: A name--value pair. The name must be an aesthetic, and the value
#' must be either a length-2 numeric, a character, a factor, or a date/time.
#' A numeric value will create a continuous scale. If the larger value comes first,
#' the scale will be reversed. You can leave one value as `NA` if you want
#' to compute the corresponding limit from the range of the data.
#' A character or factor value will create a discrete scale.
#' A date-time value will create a continuous date/time scale.
#'
#' @seealso To expand the range of a plot to always include
#' certain values, see [expand_limits()]. For other types of data, see
#' [scale_x_discrete()], [scale_x_continuous()], [scale_x_date()].
#'
#' @export
#' @examples
#' # Zoom into a specified area
#' ggplot(mtcars, aes(mpg, wt)) +
#' geom_point() +
#' xlim(15, 20)
#'
#' # reverse scale
#' ggplot(mtcars, aes(mpg, wt)) +
#' geom_point() +
#' xlim(20, 15)
#'
#' # with automatic lower limit
#' ggplot(mtcars, aes(mpg, wt)) +
#' geom_point() +
#' xlim(NA, 20)
#'
#' # You can also supply limits that are larger than the data.
#' # This is useful if you want to match scales across different plots
#' small <- subset(mtcars, cyl == 4)
#' big <- subset(mtcars, cyl > 4)
#'
#' ggplot(small, aes(mpg, wt, colour = factor(cyl))) +
#' geom_point() +
#' lims(colour = c("4", "6", "8"))
#'
#' ggplot(big, aes(mpg, wt, colour = factor(cyl))) +
#' geom_point() +
#' lims(colour = c("4", "6", "8"))
#'
#' # There are two ways of setting the axis limits: with limits or
#' # with coordinate systems. They work in two rather different ways.
#'
#' set.seed(1)
#' last_month <- Sys.Date() - 0:59
#' df <- data.frame(
#' date = last_month,
#' price = c(rnorm(30, mean = 15), runif(30) + 0.2 * (1:30))
#' )
#'
#' p <- ggplot(df, aes(date, price)) +
#' geom_line() +
#' stat_smooth()
#'
#' p
#'
#' # Setting the limits with the scale discards all data outside the range.
#' p + lims(x= c(Sys.Date() - 30, NA), y = c(10, 20))
#'
#' # For changing x or y axis limits **without** dropping data
#' # observations use [coord_cartesian()]. Setting the limits on the
#' # coordinate system performs a visual zoom.
#' p + coord_cartesian(xlim =c(Sys.Date() - 30, NA), ylim = c(10, 20))
#'
lims <- function(...) {
args <- list2(...)
if (!all(has_name(args))) {
cli::cli_abort("All arguments must be named.")
}
env <- current_env()
Map(limits, args, names(args), rep(list(env), length(args)))
}
#' @export
#' @rdname lims
xlim <- function(...) {
limits(c(...), "x", call = current_call())
}
#' @export
#' @rdname lims
ylim <- function(...) {
limits(c(...), "y", call = current_call())
}
#' Generate correct scale type for specified limits
#'
#' @param lims vector of limits
#' @param var name of variable
#' @keywords internal
#' @examples
#' ggplot2:::limits(c(1, 5), "x")
#' ggplot2:::limits(c(5, 1), "x")
#' ggplot2:::limits(c("A", "b", "c"), "x")
#' ggplot2:::limits(c("A", "b", "c"), "fill")
#' ggplot2:::limits(as.Date(c("2008-01-01", "2009-01-01")), "x")
limits <- function(lims, var, call = caller_env()) UseMethod("limits")
#' @export
limits.numeric <- function(lims, var, call = caller_env()) {
if (length(lims) != 2) {
cli::cli_abort("{.arg {var}} must be a two-element vector.", call = call)
}
if (!any(is.na(lims)) && lims[1] > lims[2]) {
trans <- "reverse"
} else {
trans <- "identity"
}
make_scale("continuous", var, limits = lims, transform = trans, call = call)
}
make_scale <- function(type, var, ..., call = NULL) {
name <- paste("scale_", var, "_", type, sep = "")
scale <- match.fun(name)
sc <- scale(...)
sc$call <- call %||% parse_expr(paste0(name, "()"))
sc
}
#' @export
limits.character <- function(lims, var, call = caller_env()) {
make_scale("discrete", var, limits = lims, call = call)
}
#' @export
limits.factor <- function(lims, var, call = caller_env()) {
make_scale("discrete", var, limits = as.character(lims), call = call)
}
#' @export
limits.Date <- function(lims, var, call = caller_env()) {
if (length(lims) != 2) {
cli::cli_abort("{.arg {var}} must be a two-element vector.", call = call)
}
make_scale("date", var, limits = lims, call = call)
}
#' @export
limits.POSIXct <- function(lims, var, call = caller_env()) {
if (length(lims) != 2) {
cli::cli_abort("{.arg {var}} must be a two-element vector.", call = call)
}
make_scale("datetime", var, limits = lims, call = call)
}
#' @export
limits.POSIXlt <- function(lims, var, call = caller_env()) {
if (length(lims) != 2) {
cli::cli_abort("{.arg {var}} must be a two-element vector.", call = call)
}
make_scale("datetime", var, limits = as.POSIXct(lims), call = call)
}
#' Expand the plot limits, using data
#'
#' Sometimes you may want to ensure limits include a single value, for all
#' panels or all plots. This function is a thin wrapper around
#' [geom_blank()] that makes it easy to add such values.
#'
#' @param ... named list of aesthetics specifying the value (or values) that
#' should be included in each scale.
#' @export
#' @examples
#' p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
#' p + expand_limits(x = 0)
#' p + expand_limits(y = c(1, 9))
#' p + expand_limits(x = 0, y = 0)
#'
#' ggplot(mtcars, aes(mpg, wt)) +
#' geom_point(aes(colour = cyl)) +
#' expand_limits(colour = seq(2, 10, by = 2))
#' ggplot(mtcars, aes(mpg, wt)) +
#' geom_point(aes(colour = factor(cyl))) +
#' expand_limits(colour = factor(seq(2, 10, by = 2)))
expand_limits <- function(...) {
data <- list2(...)
# unpack data frame columns
data_dfs <- vapply(data, is.data.frame, logical(1))
data <- unlist(c(list(data[!data_dfs]), data[data_dfs]), recursive = FALSE)
# Repeat vectors up to max length and collect to data frame
n_rows <- max(lengths(data))
data <- lapply(data, rep, length.out = n_rows)
data <- data_frame0(!!!data)
geom_blank(aes_all(names(data)), data, inherit.aes = FALSE)
}
|