diff --git a/NAMESPACE b/NAMESPACE index 2071390f..b985d82a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,6 +10,7 @@ export(plt_add) export(tinyplot) export(tinyplot_add) export(tpar) +export(type_abline) export(type_area) export(type_boxplot) export(type_errorbar) @@ -17,6 +18,7 @@ export(type_function) export(type_glm) export(type_hist) export(type_histogram) +export(type_hline) export(type_jitter) export(type_lines) export(type_lm) @@ -30,6 +32,7 @@ export(type_ribbon) export(type_segments) export(type_spineplot) export(type_spline) +export(type_vline) importFrom(grDevices,adjustcolor) importFrom(grDevices,as.raster) importFrom(grDevices,col2rgb) diff --git a/NEWS.md b/NEWS.md index 869b1627..b998b8a5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,8 +12,8 @@ Breaking changes: plot type) should no longer be passed via `...` in the main plotting function. Instead, these additional arguments must now be passed explicitly as part of the corresponding `type_*()` function in the `type` argument. So, for example, you -should now use `plt(Nile, type = type_histogram(breaks = 30))` instead of -`plt(Nile, type = "histogram", breaks = 30)` if you wanted to adjust the number +should now use `tinyplot(Nile, type = type_histogram(breaks = 30))` instead of +`tinyplot(Nile, type = "histogram", breaks = 30)` if you wanted to adjust the number of breaks. More details are provided below and also in the new dedicated [Plot types vignette](https://grantmcdermott.com/tinyplot/vignettes/types.html). The essential idea is that shortcut character types (here: `"histogram"`) all @@ -23,7 +23,7 @@ behaviour by passing appropriate arguments. We're sorry to introduce a breaking change, but this new approach should ensure that users have better control of how their plots behave, and avoids guesswork on our side. - `ribbon.alpha` is deprecated in `tinyplot()`. Use the `alpha` argument of the -`type_ribbon()` function instead: `plt(..., type = type_ribbon(alpha = 0.5))` +`type_ribbon()` function instead: `tinyplot(..., type = type_ribbon(alpha = 0.5))` New features: @@ -35,6 +35,12 @@ types enable a variety of additional features. (#222 @vincentarelbundock) - `type_lm()` (shortcut: `"lm"`) - `type_loess()` (shortcut: `"loess"`) - `type_spline()` (shortcut: `"spline"`) + - New function-based types + - `type_abline()`: line with intercept and slope + - `type_hline()`: horizontal line + - `type_vline()`: verticla line + - `type_function()` arbitrary function. + - `type_spineplot()` (shortcut: `"spineplot"`) type for producing spine - `type_function()` can trace arbitrary functions. - New `type_spineplot()` (shortcut: `"spineplot"`) type for producing spine plots and spinograms. These are modified versions of a histogram or mosaic @@ -49,11 +55,11 @@ types enable a variety of additional features. (#222 @vincentarelbundock) on the website.) - The new `flip` argument allows for easily flipping (swapping) the orientation of the x and y axes. This should work regardless of plot type, e.g. - `plt(~Sepal.Length | Species, data = iris, type = "density", flip = TRUE)`. + `tinyplot(~Sepal.Length | Species, data = iris, type = "density", flip = TRUE)`. (#216 @grantmcdermott) - `tpar()` gains additional `grid.col`, `grid.lty`, and `grid.lwd` arguments for fine-grained control over the appearance of the default panel grid when - `plt(..., grid = TRUE)` is called. (#237 @grantmcdermott) + `tinyplot(..., grid = TRUE)` is called. (#237 @grantmcdermott) - The new `tinyplot_add()` (alias: `plt_add()`) convenience function allows easy layering of plots without having to specify repeat arguments. (#246 @vincentarelbundock) @@ -229,7 +235,7 @@ adding transparency to plot elements and colours. Example use: background fill by passing `bg` (or its alias, `fill`) a numeric in the range `[0,1]`. This feature has the same effect as `bg = "by"` except for the added transparency. Example use: -`plt(lat ~ long | depth, data = quakes, pch = 21, cex = 2, bg = 0.2)`. (#129 +`tinyplot(lat ~ long | depth, data = quakes, pch = 21, cex = 2, bg = 0.2)`. (#129 @grantmcdermott) diff --git a/R/assertions.R b/R/assertions.R index 118637e9..5e48f97d 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -29,6 +29,23 @@ assert_choice = function(x, choice, null.ok = FALSE, name = as.character(substit stop(msg, call. = FALSE) } +check_true = function(x, null.ok = FALSE) { + if (is.null(x) && isTRUE(null.ok)) { + return(invisible(TRUE)) + } + if (isTRUE(x)) { + return(invisible(TRUE)) + } + return(FALSE) +} + +assert_true = function(x, null.ok = FALSE, name = as.character(substitute(x))) { + msg = sprintf("`%s` must be true.", name) + if (!isTRUE(check_true(x, null.ok = null.ok))) { + stop(msg, call. = FALSE) + } +} + check_string = function(x, null.ok = FALSE) { if (is.null(x) && isTRUE(null.ok)) { return(invisible(TRUE)) diff --git a/R/type_abline.R b/R/type_abline.R new file mode 100644 index 00000000..e7b6e146 --- /dev/null +++ b/R/type_abline.R @@ -0,0 +1,79 @@ +#' Add straight lines to a plot +#' +#' @inheritParams graphics::abline +#' @inheritParams tinyplot +#' @examples +#' mod = lm(mpg ~ hp, data = mtcars) +#' y = mtcars$mpg +#' yhat = predict(mod) +#' tinyplot(y, yhat, xlim = c(0, 40), ylim = c(0, 40)) +#' tinyplot_add(type = type_abline(a = 0, b = 1)) +#' @export +type_abline = function(a = 0, b = 1, col = NULL, lty = NULL, lwd = NULL) { + data_abline = function(datapoints, ...) { + if (nrow(datapoints) == 0) { + msg = "`type_abline() only works on existing plots with x and y data points." + stop(msg, call. = FALSE) + } + return(list()) + } + draw_abline = function() { + fun = function(ifacet, data_facet, icol, ilty, ilwd, ...) { + nfacets = length(data_facet) + + if (length(a) == 1) { + a = rep(a, nfacets) + } else if (length(a) != nfacets) { + msg = "Length of 'a' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (length(b) == 1) { + b = rep(b, nfacets) + } else if (length(b) != nfacets) { + msg = "Length of 'b' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(col)) { + col = icol + } + if (length(col) == 1) { + col = rep(col, nfacets) + } else if (length(col) != nfacets) { + msg = "Length of 'col' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(lty)) { + lty = if (!is.null(ilty)) ilty else 1 + } + if (length(lty) == 1) { + lty = rep(lty, nfacets) + } else if (length(lty) != nfacets) { + msg = "Length of 'lty' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(lwd)) { + lwd = if (!is.null(ilwd)) ilwd else 1 + } + if (length(lwd) == 1) { + lwd = rep(lwd, nfacets) + } else if (length(lwd) != nfacets) { + msg = "Length of 'lwd' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + abline(a = a[ifacet], b = b[ifacet], col = col[ifacet], lty = lty[ifacet], lwd = lwd[ifacet]) + } + return(fun) + } + out = list( + draw = draw_abline(), + data = data_abline, + name = "abline" + ) + class(out) = "tinyplot_type" + return(out) +} diff --git a/R/type_hline.R b/R/type_hline.R new file mode 100644 index 00000000..77d81716 --- /dev/null +++ b/R/type_hline.R @@ -0,0 +1,70 @@ +#' Trace a horizontal line on the plot +#' +#' @param h y-value(s) for horizontal line(s). Numeric of length 1 or equal to the number of facets. +#' @inheritParams graphics::abline +#' @inheritParams tinyplot +#' @examples +#' tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars) +#' tinyplot_add(type = type_hline(h = 12, col = "pink", lty = 3, lwd = 3)) +#' @export +type_hline = function(h = 0, col = NULL, lty = NULL, lwd = NULL) { + data_hline = function(datapoints, ...) { + if (nrow(datapoints) == 0) { + msg = "`type_hline() only works on existing plots with x and y data points." + stop(msg, call. = FALSE) + } + return(list()) + } + draw_hline = function() { + fun = function(ifacet, data_facet, icol, ilty, ilwd, ...) { + nfacets = length(data_facet) + + if (length(h) == 1) { + h = rep(h, nfacets) + } else if (length(h) != nfacets) { + msg = "Length of 'h' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(col)) { + col = icol + } + if (length(col) == 1) { + col = rep(col, nfacets) + } else if (length(col) != nfacets) { + msg = "Length of 'col' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(lty)) { + lty = if (!is.null(ilty)) ilty else 1 + } + if (length(lty) == 1) { + lty = rep(lty, nfacets) + } else if (length(lty) != nfacets) { + msg = "Length of 'lty' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(lwd)) { + lwd = if (!is.null(ilwd)) ilwd else 1 + } + if (length(lwd) == 1) { + lwd = rep(lwd, nfacets) + } else if (length(lwd) != nfacets) { + msg = "Length of 'lwd' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + abline(h = h[ifacet], col = col[ifacet], lty = lty[ifacet], lwd = lwd[ifacet]) + } + return(fun) + } + out = list( + draw = draw_hline(), + data = data_hline, + name = "hline" + ) + class(out) = "tinyplot_type" + return(out) +} diff --git a/R/type_vline.R b/R/type_vline.R new file mode 100644 index 00000000..a581487e --- /dev/null +++ b/R/type_vline.R @@ -0,0 +1,77 @@ +#' Trace a vertical line on the plot +#' +#' @param v x-value(s) for vertical line(s). Numeric of length 1 or equal to the number of facets. +#' @inheritParams tinyplot +#' @examples +#' tinyplot(mpg ~ hp, data = mtcars) +#' tinyplot_add(type = type_vline(150)) +#' +#' # facet-specify location and colors +#' cols = c("black", "green", "orange") +#' tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars, col = cols) +#' tinyplot_add(type = type_vline( +#' v = c(100, 150, 200), col = cols, lty = 3, lwd = 3 +#' )) +#' @export +type_vline = function(v = 0, col = "black", lty = 1, lwd = 1) { + assert_numeric(v) + data_vline = function(datapoints, ...) { + if (nrow(datapoints) == 0) { + msg = "`type_vline() only works on existing plots with x and y data points." + stop(msg, call. = FALSE) + } + return(list()) + } + draw_vline = function() { + fun = function(ifacet, data_facet, icol, ilty, ilwd, ...) { + nfacets = length(data_facet) + + if (length(v) == 1) { + v = rep(v, nfacets) + } else if (length(v) != nfacets) { + msg = "Length of 'v' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(col)) { + col = icol + } + if (length(col) == 1) { + col = rep(col, nfacets) + } else if (length(col) != nfacets) { + msg = "Length of 'col' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(lty)) { + lty = if (!is.null(ilty)) ilty else 1 + } + if (length(lty) == 1) { + lty = rep(lty, nfacets) + } else if (length(lty) != nfacets) { + msg = "Length of 'lty' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + if (is.null(lwd)) { + lwd = if (!is.null(ilwd)) ilwd else 1 + } + if (length(lwd) == 1) { + lwd = rep(lwd, nfacets) + } else if (length(lwd) != nfacets) { + msg = "Length of 'lwd' must be 1 or equal to the number of facets" + stop(msg, call. = FALSE) + } + + abline(v = v[ifacet], col = col[ifacet], lty = lty[ifacet], lwd = lwd[ifacet]) + } + return(fun) + } + out = list( + draw = draw_vline(), + data = data_vline, + name = "vline" + ) + class(out) = "tinyplot_type" + return(out) +} diff --git a/altdoc/quarto_website.yml b/altdoc/quarto_website.yml index b8a0a49f..dd2e97ca 100644 --- a/altdoc/quarto_website.yml +++ b/altdoc/quarto_website.yml @@ -23,7 +23,6 @@ website: file: vignettes/types.qmd - text: Gallery file: vignettes/gallery.qmd - # - section: $ALTDOC_MAN_BLOCK - section: Reference contents: - section: Plotting functions @@ -36,12 +35,15 @@ website: file: man/draw_legend.qmd - section: Plot types contents: - # - section: Shapes contents: - - text: type_area, type_ribbon + - text: type_area file: man/type_ribbon.qmd - - text: type_errorbar, type_pointrange + - text: type_ribbon + file: man/type_ribbon.qmd + - text: type_errorbar + file: man/type_errorbar.qmd + - text: type_pointrange file: man/type_errorbar.qmd - text: type_lines file: man/type_lines.qmd @@ -55,7 +57,6 @@ website: file: man/type_rect.qmd - text: type_segments file: man/type_segments.qmd - # - section: Visualizations contents: - text: type_boxplot @@ -66,7 +67,14 @@ website: file: man/type_jitter.qmd - text: type_spineplot file: man/type_spineplot.qmd - # + - section: Functions + contents: + - text: type_abline + file: man/type_abline.qmd + - text: type_hline + file: man/type_hline.qmd + - text: type_vline + file: man/type_vline.qmd - section: Models contents: - text: type_glm diff --git a/inst/tinytest/_tinysnapshot/hline.svg b/inst/tinytest/_tinysnapshot/hline.svg new file mode 100644 index 00000000..af5b5c31 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/hline.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + +factor(cyl) +4 +6 +8 + + + + + + + +hp +mpg + + + + + + + + + + + + + + + + +50 +150 +250 + + + + + + +10 +15 +20 +25 +30 + +4 + + + + + + + + + + + + + + + + + +50 +150 +250 + + + + + + +10 +15 +20 +25 +30 + +6 + + + + + + + + + + + + + + + + + +50 +150 +250 + + + + + + +10 +15 +20 +25 +30 + +8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/vline_vector.svg b/inst/tinytest/_tinysnapshot/vline_vector.svg new file mode 100644 index 00000000..98f670c8 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/vline_vector.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + +factor(cyl) +4 +6 +8 + + + + + + + +hp +mpg + + + + + + + + + + + + + + + + +50 +150 +250 + + + + + + +10 +15 +20 +25 +30 + +4 + + + + + + + + + + + + + + + + + +50 +150 +250 + + + + + + +10 +15 +20 +25 +30 + +6 + + + + + + + + + + + + + + + + + +50 +150 +250 + + + + + + +10 +15 +20 +25 +30 + +8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-abline.R b/inst/tinytest/test-abline.R new file mode 100644 index 00000000..98c4c92d --- /dev/null +++ b/inst/tinytest/test-abline.R @@ -0,0 +1,37 @@ +source("helpers.R") +using("tinysnapshot") + +expect_error(tinyplot(type = type_hline(h = 10)), pattern = "data points") +expect_error(tinyplot(type = type_vline(v = 10)), pattern = "data points") +expect_error(tinyplot(type = type_abline(a = 0, b = 1)), pattern = "data points") + + +f = function() { + plt(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars) + plt_add(type = type_hline(h = 12, col = "pink", lty = 3, lwd = 3)) +} +expect_snapshot_plot(f, label = "hline") + + +f = function() { + tinyplot(mpg ~ hp | factor(cyl), + facet = ~ factor(cyl), data = mtcars, + col = c("black", "green", "orange")) + tinyplot_add(type = type_vline( + v = c(100, 150, 200), lty = 3, lwd = 3, + col = c("black", "green", "orange") + )) +} +expect_snapshot_plot(f, label = "vline_vector") + + +## TODO: uncomment this when ready to test. Probably after the tinyplot_add +## refactor to save in an environment instead of global option +# f = function() { +# mod = lm(mpg ~ hp, data = mtcars) +# y = mtcars$mpg +# yhat = predict(mod) +# tinyplot(y, yhat, type = type_abline(a = 0, b = 1), xlim = c(0, 40), ylim = c(0, 40)) +# tinyplot_add(type = type_abline(a = 0, b = 1), xlim = c(0, 40), ylim = c(0, 40)) +# } +# expect_snapshot_plot(f, label = "abline") diff --git a/man/type_abline.Rd b/man/type_abline.Rd new file mode 100644 index 00000000..a25b1c94 --- /dev/null +++ b/man/type_abline.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/type_abline.R +\name{type_abline} +\alias{type_abline} +\title{Add straight lines to a plot} +\usage{ +type_abline(a = 0, b = 1, col = NULL, lty = NULL, lwd = NULL) +} +\arguments{ +\item{a, b}{the intercept and slope, single values.} + +\item{col}{plotting color. Character, integer, or vector of length equal to +the number of categories in the \code{by} variable. See \code{col}. Note that the +default behaviour in \code{tinyplot} is to vary group colors along any variables +declared in the \code{by} argument. Thus, specifying colors manually should not +be necessary unless users wish to override the automatic colors produced by +this grouping process. Typically, this would only be done if grouping +features are deferred to some other graphical parameter (i.e., passing the +"by" keyword to one of \code{pch}, \code{lty}, \code{lwd}, or \code{bg}; see below.)} + +\item{lty}{line type. Character, integer, or vector of length equal to the +number of categories in the \code{by} variable. See \code{lty}. In addition, users +can supply a special \code{lty = "by"} convenience argument, in which case the +line type will automatically loop over the number groups. This automatic +looping will begin at the global line type value (i.e., \code{par("lty")}) and +recycle as necessary.} + +\item{lwd}{line width. Numeric scalar or vector of length equal to the +number of categories in the \code{by} variable. See \code{lwd}. In addition, users +can supply a special \code{lwd = "by"} convenience argument, in which case the +line width will automatically loop over the number of groups. This +automatic looping will be centered at the global line width value (i.e.,} +} +\description{ +Add straight lines to a plot +} +\examples{ +mod = lm(mpg ~ hp, data = mtcars) +y = mtcars$mpg +yhat = predict(mod) +tinyplot(y, yhat, xlim = c(0, 40), ylim = c(0, 40)) +tinyplot_add(type = type_abline(a = 0, b = 1)) +} diff --git a/man/type_hline.Rd b/man/type_hline.Rd new file mode 100644 index 00000000..84469da7 --- /dev/null +++ b/man/type_hline.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/type_hline.R +\name{type_hline} +\alias{type_hline} +\title{Trace a horizontal line on the plot} +\usage{ +type_hline(h = 0, col = NULL, lty = NULL, lwd = NULL) +} +\arguments{ +\item{h}{y-value(s) for horizontal line(s). Numeric of length 1 or equal to the number of facets.} + +\item{col}{plotting color. Character, integer, or vector of length equal to +the number of categories in the \code{by} variable. See \code{col}. Note that the +default behaviour in \code{tinyplot} is to vary group colors along any variables +declared in the \code{by} argument. Thus, specifying colors manually should not +be necessary unless users wish to override the automatic colors produced by +this grouping process. Typically, this would only be done if grouping +features are deferred to some other graphical parameter (i.e., passing the +"by" keyword to one of \code{pch}, \code{lty}, \code{lwd}, or \code{bg}; see below.)} + +\item{lty}{line type. Character, integer, or vector of length equal to the +number of categories in the \code{by} variable. See \code{lty}. In addition, users +can supply a special \code{lty = "by"} convenience argument, in which case the +line type will automatically loop over the number groups. This automatic +looping will begin at the global line type value (i.e., \code{par("lty")}) and +recycle as necessary.} + +\item{lwd}{line width. Numeric scalar or vector of length equal to the +number of categories in the \code{by} variable. See \code{lwd}. In addition, users +can supply a special \code{lwd = "by"} convenience argument, in which case the +line width will automatically loop over the number of groups. This +automatic looping will be centered at the global line width value (i.e.,} +} +\description{ +Trace a horizontal line on the plot +} +\examples{ +tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars) +tinyplot_add(type = type_hline(h = 12, col = "pink", lty = 3, lwd = 3)) +} diff --git a/man/type_vline.Rd b/man/type_vline.Rd new file mode 100644 index 00000000..d52a892c --- /dev/null +++ b/man/type_vline.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/type_vline.R +\name{type_vline} +\alias{type_vline} +\title{Trace a vertical line on the plot} +\usage{ +type_vline(v = 0, col = "black", lty = 1, lwd = 1) +} +\arguments{ +\item{v}{x-value(s) for vertical line(s). Numeric of length 1 or equal to the number of facets.} + +\item{col}{plotting color. Character, integer, or vector of length equal to +the number of categories in the \code{by} variable. See \code{col}. Note that the +default behaviour in \code{tinyplot} is to vary group colors along any variables +declared in the \code{by} argument. Thus, specifying colors manually should not +be necessary unless users wish to override the automatic colors produced by +this grouping process. Typically, this would only be done if grouping +features are deferred to some other graphical parameter (i.e., passing the +"by" keyword to one of \code{pch}, \code{lty}, \code{lwd}, or \code{bg}; see below.)} + +\item{lty}{line type. Character, integer, or vector of length equal to the +number of categories in the \code{by} variable. See \code{lty}. In addition, users +can supply a special \code{lty = "by"} convenience argument, in which case the +line type will automatically loop over the number groups. This automatic +looping will begin at the global line type value (i.e., \code{par("lty")}) and +recycle as necessary.} + +\item{lwd}{line width. Numeric scalar or vector of length equal to the +number of categories in the \code{by} variable. See \code{lwd}. In addition, users +can supply a special \code{lwd = "by"} convenience argument, in which case the +line width will automatically loop over the number of groups. This +automatic looping will be centered at the global line width value (i.e.,} +} +\description{ +Trace a vertical line on the plot +} +\examples{ +tinyplot(mpg ~ hp, data = mtcars) +tinyplot_add(type = type_vline(150)) + +# facet-specify location and colors +cols = c("black", "green", "orange") +tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars, col = cols) +tinyplot_add(type = type_vline( + v = c(100, 150, 200), col = cols, lty = 3, lwd = 3 +)) +}