diff --git a/NAMESPACE b/NAMESPACE index 3b2a7bb7e5..72b8101453 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -60,6 +60,7 @@ S3method(ggplot_add,default) S3method(ggplot_add,guides) S3method(ggplot_add,labels) S3method(ggplot_add,list) +S3method(ggplot_add,scales_params) S3method(ggplot_add,theme) S3method(ggplot_add,uneval) S3method(ggplot_build,ggplot) diff --git a/R/plot-build.r b/R/plot-build.r index dcac060141..3e69a92f55 100644 --- a/R/plot-build.r +++ b/R/plot-build.r @@ -35,6 +35,10 @@ ggplot_build.ggplot <- function(plot) { layer_data <- lapply(layers, function(y) y$layer_data(plot$data)) scales <- plot$scales + # Make sure all scales are provided with any parameters that were separately + # provided. Note: this excludes missing but required scales, which don't yet + # exist at this time. + scales$update_scales_params() # Apply function to layer and matching data by_layer <- function(f) { out <- vector("list", length(data)) diff --git a/R/plot-construction.r b/R/plot-construction.r index 6248c4914e..78e859dfa2 100644 --- a/R/plot-construction.r +++ b/R/plot-construction.r @@ -115,6 +115,11 @@ ggplot_add.Scale <- function(object, plot, object_name) { plot } #' @export +ggplot_add.scales_params <- function(object, plot, object_name) { + plot$scales$add_params(object) + plot +} +#' @export ggplot_add.labels <- function(object, plot, object_name) { update_labels(plot, object) } diff --git a/R/scale-.r b/R/scale-.r index 801b839109..10e7517a0c 100644 --- a/R/scale-.r +++ b/R/scale-.r @@ -341,6 +341,9 @@ binned_scale <- function(aesthetics, scale_name, palette, name = waiver(), #' (for non-position scales) or when the `Layout` calculates the x and y labels #' (position scales). #' +#' - `update_params()` Method to feed scale parameters into scale object at the end of +#' plot construction. For ggplot2 internal use only. +#' #' These methods are only valid for position (x and y) scales: #' #' - `dimension()` For continuous scales, the dimension is the same concept as the limits. @@ -388,6 +391,8 @@ Scale <- ggproto("Scale", NULL, guide = "legend", position = "left", + update_params = function(self, params) { + }, is_discrete = function() { abort("Not implemented") @@ -548,6 +553,13 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale, n.breaks = NULL, trans = identity_trans(), + update_params = function(self, params) { + self$name <- params$name %||% self$name + self$breaks <- params$breaks %||% self$breaks + self$labels <- params$labels %||% self$labels + self$expand <- params$expand %||% self$expand + }, + is_discrete = function() FALSE, train = function(self, x) { diff --git a/R/scales-.r b/R/scales-.r index d56036e8cf..8a160613ab 100644 --- a/R/scales-.r +++ b/R/scales-.r @@ -8,6 +8,7 @@ scales_list <- function() { ScalesList <- ggproto("ScalesList", NULL, scales = NULL, + scales_params = list(), find = function(self, aesthetic) { vapply(self$scales, function(x) any(aesthetic %in% x$aesthetics), logical(1)) @@ -36,6 +37,33 @@ ScalesList <- ggproto("ScalesList", NULL, self$scales <- c(self$scales[!prev_aes], list(scale)) }, + # Save parameters for a scale, to be applied later + # via `update_scales_params()`. The `params` object + # should be created with `scales_params()`. + add_params = function(self, params) { + aesthetic <- params$aesthetic + self$scales_params[[aesthetic]] <- + defaults(params$params, self$scales_params[[aesthetic]]) + }, + + # update the parameters for all scales currently stored + update_scales_params = function(self) { + for (scale in self$scales) { + self$update_params_for_scale(scale) + } + }, + + # update the parameters for one specific scale object + update_params_for_scale = function(self, scale) { + # get a list of all the params objects that are possibly relevant + params_list <- self$scales_params[scale$aesthetics] + # update the scale with each params object + lapply(params_list, function(x) { + if (!is.null(x)) scale$update_params(x) + }) + invisible() + }, + n = function(self) { length(self$scales) }, @@ -100,7 +128,12 @@ scales_add_defaults <- function(scales, data, aesthetics, env) { datacols <- compact(datacols) for (aes in names(datacols)) { - scales$add(find_scale(aes, datacols[[aes]], env)) + # find the appropriate scale object + scale <- find_scale(aes, datacols[[aes]], env) + # make sure it has the latest available parameters + scales$update_params_for_scale(scale) + # add to scales list + scales$add(scale) } } @@ -115,9 +148,27 @@ scales_add_missing <- function(plot, aesthetics, env) { for (aes in aesthetics) { scale_name <- paste("scale", aes, "continuous", sep = "_") - scale_f <- find_global(scale_name, env, mode = "function") - plot$scales$add(scale_f()) + # find the appropriate scale object + scale_fun <- find_global(scale_name, env, mode = "function") + scale <- scale_fun() + # make sure it has the latest available parameters + scales$update_params_for_scale(scale) + # add to scales list + plot$scales$add(scale) } } +# create a data structure to store parameters to be added to scales later on +# @param aesthetic A single aesthetic, *not* a vector of aesthetics. +# @param ... Parameters to be provided to the scale. +scales_params <- function(aesthetic, ...) { + structure( + list( + aesthetic = aesthetic, + params = list(...) + ), + class = "scales_params" + ) +} + diff --git a/man/ggplot2-ggproto.Rd b/man/ggplot2-ggproto.Rd index 7e9cf6e132..15028293e7 100644 --- a/man/ggplot2-ggproto.Rd +++ b/man/ggplot2-ggproto.Rd @@ -458,6 +458,8 @@ and the value of \code{self$trans$minor_breaks()}. Discrete scales always return \item \code{make_title()} Hook to modify the title that is calculated during guide construction (for non-position scales) or when the \code{Layout} calculates the x and y labels (position scales). +\item \code{update_params()} Method to feed scale parameters into scale object at the end of +plot construction. For ggplot2 internal use only. } These methods are only valid for position (x and y) scales: