diff --git a/NAMESPACE b/NAMESPACE index ffa51202..805ff18c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,7 +6,9 @@ S3method(tinyplot,formula) export(draw_legend) export(get_saved_par) export(plt) +export(plt_add) export(tinyplot) +export(tinyplot_add) export(tpar) export(type_area) export(type_boxplot) diff --git a/R/tinyplot.R b/R/tinyplot.R index 4535a790..4cb5aa32 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -536,6 +536,14 @@ tinyplot.default = function( ... ) { + # save for plt_add() + options(tinyplot_last_call = match.call(tinyplot, + call = sys.call(sys.parent()), expand.dots = TRUE)) + + ## TODO: remove the global option above and move to this when density is refactored + # cal = match.call(call = sys.call(sys.parent()), expand.dots = TRUE) + # assign(".last_call", cal, envir = get(".tinyplot_env", envir = parent.env(environment()))) + dots = list(...) if (isTRUE(add)) legend = FALSE diff --git a/R/tinyplot_add.R b/R/tinyplot_add.R new file mode 100644 index 00000000..ff70336c --- /dev/null +++ b/R/tinyplot_add.R @@ -0,0 +1,52 @@ +#' Add new elements to the current `tinyplot` +#' +#' @description +#' This function grabs the last `tinyplot` call, sets `add=TRUE`, and +#' overwrites some of the arguments with those explicitly supplied by the +#' user. Then, the call is evaluated again to add a layer to an existing plot. +#' +#' @details +#' Note: the automatic legend for the added elements will be turned off. +#' +#' @param ... All named arguments override arguments from the previous calls. +#' Arguments not supplied to `tinyplot_add()` remain unchanged from the previous call. +#' +#' @examples +#' library(tinyplot) +#' +#' tinyplot(Sepal.Width ~ Sepal.Length | Species, +#' facet = ~Species, +#' data = iris, +#' type = type_lm()) +#' +#' tinyplot_add(type = "p") +#' +#' @returns No return value, called for side effect of producing a plot. +#' +#' @export +tinyplot_add <- function(...) { + cal = getOption("tinyplot_last_call", default = NULL) + + ## TODO: remove the global option above and move to this when density is refactored + # cal = get(".last_call", envir = get(".tinyplot_env", envir = parent.env(environment()))) + + if (is.null(cal)) { + stop("No previous tinyplot call found.") + } + + args = list(...) + for (n in names(args)) { + if (n != "") { + cal[[n]] = args[[n]] + } + } + cal[["add"]] = TRUE + eval(cal) +} + + + +#' @export +#' @name plt_add +#' @rdname tinyplot_add +plt_add = tinyplot_add diff --git a/R/zzz.R b/R/zzz.R index 3ce682a2..9c630a2c 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -44,6 +44,7 @@ assign(".saved_par_before", NULL, envir = get(".tinyplot_env", envir = parent.env(environment()))) assign(".saved_par_after", NULL, envir = get(".tinyplot_env", envir = parent.env(environment()))) + assign(".last_call", NULL, envir = get(".tinyplot_env", envir = parent.env(environment()))) globalVariables(c( "add", diff --git a/altdoc/quarto_website.yml b/altdoc/quarto_website.yml index 4e990550..a350a2b9 100644 --- a/altdoc/quarto_website.yml +++ b/altdoc/quarto_website.yml @@ -28,10 +28,12 @@ website: contents: - section: Plotting functions contents: - - text: draw_legend - file: man/draw_legend.qmd - text: tinyplot file: man/tinyplot.qmd + - text: tinyplot_add + file: man/tinyplot_add.qmd + - text: draw_legend + file: man/draw_legend.qmd - section: Plot types contents: # diff --git a/inst/tinytest/_tinysnapshot/tinyplot_add.svg b/inst/tinytest/_tinysnapshot/tinyplot_add.svg new file mode 100644 index 00000000..ce047d68 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/tinyplot_add.svg @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + +Species +setosa +versicolor +virginica + + + + + + + +Sepal.Length +Sepal.Width + + + + + + + + + + + + + + + + + + +4.5 +5.5 +6.5 +7.5 + + + + + + +2.0 +2.5 +3.0 +3.5 +4.0 + +setosa + + + + + + + + + + + + + + + + + + + +4.5 +5.5 +6.5 +7.5 + + + + + + +2.0 +2.5 +3.0 +3.5 +4.0 + +versicolor + + + + + + + + + + + + + + + + + + + +4.5 +5.5 +6.5 +7.5 + + + + + + +2.0 +2.5 +3.0 +3.5 +4.0 + +virginica + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-tinyplot_add.R b/inst/tinytest/test-tinyplot_add.R new file mode 100644 index 00000000..9467a012 --- /dev/null +++ b/inst/tinytest/test-tinyplot_add.R @@ -0,0 +1,14 @@ +source("helpers.R") +using("tinysnapshot") + +## error message cannot be tested in this suite +# expect_error(tinyplot_add(type = "p"), pattern = "No previous tinyplot") + +f = function() { + tinyplot(Sepal.Width ~ Sepal.Length | Species, + facet = ~Species, + data = iris, + type = "p") + tinyplot_add(type = type_lm()) +} +expect_snapshot_plot(f, label = "tinyplot_add") diff --git a/man/tinyplot_add.Rd b/man/tinyplot_add.Rd new file mode 100644 index 00000000..64242739 --- /dev/null +++ b/man/tinyplot_add.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tinyplot_add.R +\name{tinyplot_add} +\alias{tinyplot_add} +\alias{plt_add} +\title{Add new elements to the current \code{tinyplot}} +\usage{ +tinyplot_add(...) + +plt_add(...) +} +\arguments{ +\item{...}{All named arguments override arguments from the previous calls. +Arguments not supplied to \code{tinyplot_add()} remain unchanged from the previous call.} +} +\value{ +No return value, called for side effect of producing a plot. +} +\description{ +This function grabs the last \code{tinyplot} call, sets \code{add=TRUE}, and +overwrites some of the arguments with those explicitly supplied by the +user. Then, the call is evaluated again to add a layer to an existing plot. +} +\details{ +Note: the automatic legend for the added elements will be turned off. +} +\examples{ +library(tinyplot) + +tinyplot(Sepal.Width ~ Sepal.Length | Species, + facet = ~Species, + data = iris, + type = type_lm()) + +tinyplot_add(type = "p") + +} diff --git a/vignettes/introduction.qmd b/vignettes/introduction.qmd index c7216668..65d7d81f 100644 --- a/vignettes/introduction.qmd +++ b/vignettes/introduction.qmd @@ -409,12 +409,7 @@ tinyplot( ylim = c(50, 100), main = "Actual and predicted air temperatures" ) -tinyplot( - Temp ~ Day | Month, aq, - facet = "by", facet.args = list(bg = "grey90"), - palette = "dark2", - add = TRUE -) +tinyplot_add(type = "p") ``` Note that the `facet` argument also accepts a _two-sided_ formula for arranging @@ -438,6 +433,27 @@ tinyplot( The `facet.args` customizations can also be set globally via the `tpar()` function, which provides a nice segue to our penultimate section. +## Layers + +In many contexts, it is convenient to build plots step-by-step, adding layers with different elements on top of a base. + +The `tinyplot()` function includes an `add=TRUE` argument that adds element to an existing plot, instead of drawing a new window. This argument is useful, but a bit verbose, as it requires users to make very similar successive calls, with many shared arguments. + +For convenience, the package also includes a `tinyplot_add()` function (alias `plt_add()`), which captures the last `tinyplot()` call, keeps all the same arguments, and modifies just the arguments that the user explicitly wants to change. + +In the following example, we first draw linear regression lines with facets and group coloring. Then, we add the original data points using `tinyplot_add()`. Notice that the same grouping and data options are carried over to points, without having to specify them in the `tinyplot_add()` function. + +```{r} +library(tinyplot) + +tinyplot(Sepal.Width ~ Sepal.Length | Species, + facet = ~Species, + data = iris, + type = "p") + +tinyplot_add(type = type_lm()) +``` + ## Themes Customizing your plots further is straightforward, whether that is done directly