Skip to content

Commit

Permalink
Pass type arguments through dots (#267)
Browse files Browse the repository at this point in the history
* pass on certain named arguments from dots to type

* add 'function' to supported 'types' with dispatch in switch()

* attempt to explain new argument processing

* document extended ... processing (and omit ribbon.alpha)

* alphabetically sorted switch (and aligned at =), added type_lines

* add abline, hline, vline types

* stats::approx was not imported properly yet (from type_ridge PR)

---------

Co-authored-by: Grant McDermott <grant.mcdermott@gmail.com>
  • Loading branch information
zeileis and grantmcdermott authored Nov 27, 2024
1 parent d76f4f1 commit 7ee0725
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 74 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ importFrom(graphics,strwidth)
importFrom(graphics,text)
importFrom(graphics,title)
importFrom(methods,as)
importFrom(stats,approx)
importFrom(stats,as.formula)
importFrom(stats,density)
importFrom(stats,dnorm)
Expand Down
62 changes: 33 additions & 29 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,39 @@ where the formatting is also better._

## 0.2.1.99 (dev version)

Breaking changes:

- As part of our new plot `type` logic (see below), character-based shortcuts
like `type = "p"`, `type = "hist"`, etc. are reserved for default behaviour.
In turn, this means that any type-specific arguments for customizing behaviour
should no longer be passed through the main `tinyplot(...)` function (i.e.,
via `...`), since they will be ignored. Rather, these ancilliary arguments
must now be passed explicitly as part of the corresponding `type_*()` function
to the `type` argument. For example, say that you want to change the default
number of breaks in a histogram plot. Whereas previously you could have called,
say, `tinyplot(Nile, type = "hist", breaks = 30)`, now you should instead
call `tinyplot(Nile, type = type_hist(breaks = 30))`. We're sorry for
introducing a breaking change, but again this should only impact plots that
deviate from the default behaviour. Taking a longer-term view, this new `type`
logic ensures that users can better control how their plots behave, avoids
guesswork on our side, and should help to reduce the overall maintenance burden
of the package.
- `ribbon.alpha` is deprecated in `tinyplot()`. Use the `alpha` argument of the
`type_ribbon()` function instead: `tinyplot(..., type = type_ribbon(alpha = 0.5))`

New plot `type` logic and functional equivalents:

- Alongside the standard character shortcuts (`"p"`, `"l"`, etc.), the `type`
argument now accepts functional `type_*()` equivalents (`type_points()`,
`type_lines()`, etc.). These functional plot types enable a variety of
additional features, as well as a more disciplined approach to explicit
argument passing for customized type behaviour. (#222 @vincentarelbundock)
- Users can define their own custom types by creating `type_<typename>()`
functions.
New plot `type` processing and some breaking changes:

- In previous versions of `tinyplot` the plot `type` was specified by character
arguments, i.e., either the standard character shortcuts (such `"p"`, `"l"`,
etc.) or labels (such as `"hist"`, `"boxplot"`, etc.). In addition, the
`type` argument now accepts functional `type_*()` equivalents (e.g.,
`type_hist()`) which enable a variety of additional features, as well as a
more disciplined approach to explicit argument passing for customized type
behavior. (#222 @vincentarelbundock)
- All plot types provided in the package can be specified either by a character
label or the corresponding function. Thus, the following two are equivalent:
`tinyplot(Nile, type = "hist")` and `tinyplot(Nile, type = type_hist())`.
- The main advantage of the function specification is that many more plot types
can be supported (see list below) and users can define their own custom types
by creating `type_<typename>()` functions.
- Enabling these new features comes at the cost of a different approach for
specifying ancilliary arguments of the type function. It is recommended to
pass such arguments explicitly to the `type_*()` function call, e.g., as in
`tinyplot(Nile, type = type_hist(breaks = 30))`. In many situations it is
still possible to use the character specification plus extra arguments
instead, e.g., as in `tinyplot(Nile, type = "hist", breaks = 30)`. However,
this only works if the ancilliary type arguments do not match or even
partially match any of the arguments of the `tinyplot()` function itself.
This is why the former approach is recommended (unless using only the
default type).
- Due to this change in the processing of the ancilliary type arguments there
are a few breaking changes but we have tried to minimize them. One argument
that was deprecated explicitly is `ribbon.alpha` in `tinyplot()`. Use the
`alpha` argument of the `type_ribbon()` function instead:
`tinyplot(..., type = type_ribbon(alpha = 0.5))`. Note that it is not
equivalent to use `tinyplot(..., type = "ribbon", alpha = 0.5)` because the
latter matches the `alpha` argument of `tinyplot()` (rather than of
`type_ribbon()`) and modifies the `palette` rather than the ribbon only.
- More details are provided in the dedicated
[Plot types vignette](https://grantmcdermott.com/tinyplot/vignettes/types.html)
on the website.
Expand Down
80 changes: 45 additions & 35 deletions R/sanitize.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,71 @@ sanitize_ribbon.alpha = function(ribbon.alpha) {



sanitize_type = function(type, x, y) {
sanitize_type = function(type, x, y, dots) {
if (inherits(type, "tinyplot_type")) {
return(type)
}

types = c(
"area", "boxplot", "density", "jitter", "ribbon", "pointrange", "hist", "ridge",
"histogram", "errorbar", "polygon", "polypath", "rect", "qq", "segments", "points",
"p", "l", "o", "b", "c", "h", "j", "s", "S", "n", "loess", "spline", "lm", "glm",
"spineplot"
"p", "l", "o", "b", "c", "h", "j", "s", "S", "n",
"density",
"abline", "area", "boxplot", "errorbar", "function", "glm", "hist",
"histogram", "hline", "j", "jitter", "lines", "lm", "loess", "pointrange",
"points", "polygon", "polypath", "qq", "rect", "ribbon", "ridge",
"segments", "spineplot", "spline", "vline"
)
assert_choice(type, types, null.ok = TRUE)

if (is.null(type)) {
if (!is.null(x) && is.factor(x) && !is.factor(y)) {
# enforce boxplot type for y ~ factor(x)
return(type_boxplot())
type = type_boxplot
} else if (is.factor(y)) {
# enforce spineplot type for factor(y) ~ x
return(type_spineplot())
type = type_spineplot
} else {
type = "p"
}
} else if (type %in% c("hist", "histogram")) {
type = "histogram"
} else if (type %in% c("j", "jitter")) {
type = return(type_jitter())
}

type_fun = switch(type,
"points" = type_points(),
"segments" = type_segments(),
"area" = type_area(),
"rect" = type_rect(),
"polypath" = type_polypath(),
"polygon" = type_polygon(),
"pointrange" = type_pointrange(),
"errorbar" = type_errorbar(),
"boxplot" = type_boxplot(),
"ribbon" = type_ribbon(),
"histogram" = type_histogram(),
"spineplot" = type_spineplot(),
"j" = type_jitter(),
"jitter" = type_jitter(),
"loess" = type_loess(),
"ridge" = type_ridge(),
"qq" = type_qq(),
"spline" = type_spline(),
"glm" = type_glm(),
"lm" = type_lm(),
NULL # Default case
if (is.character(type)) type = switch(type,
"abline" = type_abline,
"area" = type_area,
"boxplot" = type_boxplot,
"errorbar" = type_errorbar,
"function" = type_function,
"glm" = type_glm,
"hist" = type_histogram,
"histogram" = type_histogram,
"hline" = type_hline,
"j" = type_jitter,
"jitter" = type_jitter,
"lines" = type_lines,
"lm" = type_lm,
"loess" = type_loess,
"pointrange" = type_pointrange,
"points" = type_points,
"polygon" = type_polygon,
"polypath" = type_polypath,
"qq" = type_qq,
"rect" = type_rect,
"ribbon" = type_ribbon,
"ridge" = type_ridge,
"segments" = type_segments,
"spineplot" = type_spineplot,
"spline" = type_spline,
"vline" = type_vline,
type # default case
)
if (inherits(type_fun, "tinyplot_type")) {
return(type_fun)

if (is.function(type)) {
args = intersect(names(formals(type)), names(dots))
args = if (length(args) >= 1L) dots[args] else list()
type = do.call(type, args)
type$dots = dots[setdiff(names(dots), names(args))]
}

if (inherits(type, "tinyplot_type")) return(type)

out = list(draw = NULL, data = NULL, name = type)
return(out)
Expand Down
16 changes: 11 additions & 5 deletions R/tinyplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,7 @@
#' automatic looping will be centered at the global line width value (i.e.,
# ` par("lwd")`) and pad on either side of that.
#' @param bg background fill color for the open plot symbols 21:25 (see
#' `points.default`), as well as ribbon and area plot types. For the latter
#' group---including filled density plots---an automatic alpha transparency
#' adjustment will be applied (see the `ribbon.alpha` argument further below).
#' `points.default`), as well as ribbon and area plot types.
#' Users can also supply either one of two special convenience arguments that
#' will cause the background fill to inherit the automatic grouped coloring
#' behaviour of `col`:
Expand Down Expand Up @@ -298,7 +296,14 @@
#' specified.
#' @param xaxt,yaxt character specifying the type of x-axis and y-axis, respectively.
#' See `axes` for the possible values.
#' @param xaxs,yaxs,... other graphical parameters (see \code{\link[graphics]{par}}).
#' @param xaxs,yaxs character specifying the style of the interval calculation used
#' for the x-axis and y-axis, respectively. See \code{\link[graphics]{par}}
#' for the possible values.
#' @param ... other graphical parameters. If `type` is a character specification
#' (such as `"hist"`) then any argument names that match those from the corresponding
#' `type_*()` function (such as \code{\link{type_hist}}) are passed on to that.
#' All remaining arguments from `...` can be further graphical parameters, see
#' \code{\link[graphics]{par}}).
#'
#' @returns No return value, called for side effect of producing a plot.
#'
Expand Down Expand Up @@ -574,7 +579,8 @@ tinyplot.default = function(
# sanitize arguments

# type factories vs. strings
type = sanitize_type(type, x, y)
type = sanitize_type(type, x, y, dots)
if ("dots" %in% names(type)) dots = type$dots
type_data = type$data
type_draw = type$draw
type = type$name
Expand Down
2 changes: 1 addition & 1 deletion R/type_ridge.R
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ segmented_raster = function(x, y, ymin = 0, breaks = range(x), probs = NULL, man
}

## auxiliary function for determining quantiles based on density function
#' @importFrom stats median
#' @importFrom stats approx median
quantile.density = function(x, probs = seq(0, 1, 0.25), ...) {
## sanity check for probabilities
if (any(probs < 0 | probs > 1)) stop("'probs' outside [0,1]")
Expand Down
14 changes: 10 additions & 4 deletions man/tinyplot.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7ee0725

Please sign in to comment.