diff --git a/NAMESPACE b/NAMESPACE index 01a84fde..71757a03 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -70,6 +70,7 @@ importFrom(grDevices,xy.coords) importFrom(graphics,Axis) importFrom(graphics,abline) importFrom(graphics,arrows) +importFrom(graphics,axTicks) importFrom(graphics,axis) importFrom(graphics,box) importFrom(graphics,boxplot) diff --git a/NEWS.md b/NEWS.md index 53267229..55fcde4c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,10 @@ where the formatting is also better._ - The palette argument now accepts a vector or list of manual colours, e.g. `tinyplot(..., palette = c("cyan4", "hotpink, "purple4"))`, or `tinytheme("clean", palette = c("cyan4", "hotpink, "purple4"))` (#325 @grantmcdermott) +- The new top-level `xaxl` and `yaxl` arguments allow users to format the + appearance of their axis tick labels. Several convenience strings are + supported for common cases, e.g., `tinyplot(..., xaxl = "percent")` or + `tinyplot(..., yaxl = "dollar")`, etc. (#363 @grantmcdermott) ### Bugs fixes: diff --git a/R/facet.R b/R/facet.R index e7b4e6ac..29634a01 100644 --- a/R/facet.R +++ b/R/facet.R @@ -181,18 +181,20 @@ draw_facet_window = function(grid, ...) { if (dynmar) { if (par("las") %in% 1:2) { # extra whitespace bump on the y axis - # yaxl = axTicks(2) - yaxl = axisTicks(usr = extendrange(ylim, f = 0.04), log = par("ylog")) ## overrides for ridge and some types that use integer spacing with (named) axis labels ## FXIME if (type == "ridge") { - yaxl = levels(y) + yaxlabs = levels(y) } else if (!is.null(ylabs)) { - yaxl = if (!is.null(names(ylabs))) names(ylabs) else ylabs + yaxlabs = if (!is.null(names(ylabs))) names(ylabs) else ylabs } else if (type == "boxplot" && isTRUE(flip) && !is.null(xlabs)) { - yaxl = if (!is.null(names(xlabs))) names(xlabs) else xlabs + yaxlabs = if (!is.null(names(xlabs))) names(xlabs) else xlabs + } else { + # yaxl = axTicks(2) + yaxlabs = axisTicks(usr = extendrange(ylim, f = 0.04), log = par("ylog")) } + if (!is.null(yaxl)) yaxlabs = tinylabel(yaxlabs, yaxl) # whtsbp = grconvertX(max(strwidth(yaxl, "figure")), from = "nfc", to = "lines") - 1 - whtsbp = grconvertX(max(strwidth(yaxl, "figure")), from = "nfc", to = "lines") - grconvertX(0, from = "nfc", to = "lines") - 1 + whtsbp = grconvertX(max(strwidth(yaxlabs, "figure")), from = "nfc", to = "lines") - grconvertX(0, from = "nfc", to = "lines") - 1 if (whtsbp > 0) { omar = omar + c(0, whtsbp, 0, 0) * cex_fct_adj fmar[2] = fmar[2] + whtsbp * cex_fct_adj @@ -200,10 +202,11 @@ draw_facet_window = function(grid, ...) { } if (par("las") %in% 2:3) { # extra whitespace bump on the x axis - # xaxl = axTicks(1) - xaxl = axisTicks(usr = extendrange(xlim, f = 0.04), log = par("xlog")) - whtsbp = grconvertY(max(strwidth(xaxl, "figure")), from = "nfc", to = "lines") - 1 - # whtsbp = grconvertY(max(strwidth(xaxl, "figure")), from = "nfc", to = "lines") - grconvertY(0, from = "nfc", to = "lines") - 1 + # xaxlabs = axTicks(1) + xaxlabs = axisTicks(usr = extendrange(xlim, f = 0.04), log = par("xlog")) + if (!is.null(xaxl)) xaxlabs = tinylabel(xaxlabs, xaxl) + whtsbp = grconvertY(max(strwidth(xaxlabs, "figure")), from = "nfc", to = "lines") - 1 + # whtsbp = grconvertY(max(strwidth(xaxlabs, "figure")), from = "nfc", to = "lines") - grconvertY(0, from = "nfc", to = "lines") - 1 if (whtsbp > 0) { omar = omar + c(whtsbp, 0, 0, 0) * cex_fct_adj fmar[1] = fmar[1] + whtsbp * cex_fct_adj @@ -247,18 +250,20 @@ draw_facet_window = function(grid, ...) { if (type == "spineplot") omar[4] = 2.1 # FIXME catch for spineplot RHS axis labs if (par("las") %in% 1:2) { # extra whitespace bump on the y axis - # yaxl = axTicks(2) - yaxl = axisTicks(usr = extendrange(ylim, f = 0.04), log = par("ylog")) ## overrides for ridge and some types that use integer spacing with (named) axis labels ## FXIME if (type == "ridge") { - yaxl = levels(y) + yaxlabs = levels(y) } else if (!is.null(ylabs)) { - yaxl = if (!is.null(names(ylabs))) names(ylabs) else ylabs + yaxlabs = if (!is.null(names(ylabs))) names(ylabs) else ylabs } else if (type == "boxplot" && isTRUE(flip) && !is.null(xlabs)) { - yaxl = if (!is.null(names(xlabs))) names(xlabs) else xlabs + yaxlabs = if (!is.null(names(xlabs))) names(xlabs) else xlabs + } else { + # yaxl = axTicks(2) + yaxlabs = axisTicks(usr = extendrange(ylim, f = 0.04), log = par("ylog")) } - # whtsbp = grconvertX(max(strwidth(yaxl, "figure")), from = "nfc", to = "lines") - 1 - whtsbp = grconvertX(max(strwidth(yaxl, "figure")), from = "nfc", to = "lines") - grconvertX(0, from = "nfc", to = "lines") - 1 + if (!is.null(yaxl)) yaxlabs = tinylabel(yaxlabs, yaxl) + # whtsbp = grconvertX(max(strwidth(yaxlabs, "figure")), from = "nfc", to = "lines") - 1 + whtsbp = grconvertX(max(strwidth(yaxlabs, "figure")), from = "nfc", to = "lines") - grconvertX(0, from = "nfc", to = "lines") - 1 if (whtsbp > 0) { omar[2] = omar[2] + whtsbp } @@ -266,9 +271,10 @@ draw_facet_window = function(grid, ...) { if (par("las") %in% 2:3) { # extra whitespace bump on the x axis # xaxl = axTicks(1) - xaxl = axisTicks(usr = extendrange(ylim, f = 0.04), log = par("xlog")) - whtsbp = grconvertY(max(strwidth(xaxl, "figure")), from = "nfc", to = "lines") - 1 - # whtsbp = grconvertY(max(strwidth(xaxl, "figure")), from = "nfc", to = "lines") - grconvertY(0, from = "nfc", to = "lines") - 1 + xaxlabs = axisTicks(usr = extendrange(ylim, f = 0.04), log = par("xlog")) + if (!is.null(xaxl)) xaxlabs = tinylabel(xaxlabs, xaxl) + whtsbp = grconvertY(max(strwidth(xaxlabs, "figure")), from = "nfc", to = "lines") - 1 + # whtsbp = grconvertY(max(strwidth(xaxlabs, "figure")), from = "nfc", to = "lines") - grconvertY(0, from = "nfc", to = "lines") - 1 if (whtsbp > 0) { omar[1] = omar[1] + whtsbp } @@ -327,6 +333,7 @@ draw_facet_window = function(grid, ...) { args_x = list(x, side = xside, type = xaxt, + labeller = xaxl, cex = get_tpar(c("cex.xaxs", "cex.axis"), 0.8), lwd = get_tpar(c("lwd.xaxs", "lwd.axis"), 1), lty = get_tpar(c("lty.xaxs", "lty.axis"), 1) @@ -334,6 +341,7 @@ draw_facet_window = function(grid, ...) { args_y = list(y, side = yside, type = yaxt, + labeller = yaxl, cex = get_tpar(c("cex.yaxs", "cex.axis"), 0.8), lwd = get_tpar(c("lwd.yaxs", "lwd.axis"), 1), lty = get_tpar(c("lty.yaxs", "lty.axis"), 1) diff --git a/R/tinyAxis.R b/R/tinyAxis.R index d7a402a9..28655231 100644 --- a/R/tinyAxis.R +++ b/R/tinyAxis.R @@ -1,7 +1,7 @@ #' auxiliary Axis() interface with different parameter combinations based on type #' #' @keywords internal -tinyAxis = function(x = NULL, ..., type = "standard") { +tinyAxis = function(x = NULL, ..., type = "standard", labeller = NULL) { type = match.arg(type, c("standard", "none", "labels", "ticks", "axis")) if (type == "none") { invisible(numeric(0L)) @@ -17,6 +17,14 @@ tinyAxis = function(x = NULL, ..., type = "standard") { } else { args$tick = TRUE } + if (!is.null(labeller)) { + if (!is.null(args$at)) { + args$labels = if (!is.null(args$labels)) tinylabel(args$labels, labeller) else tinylabel(args$at, labeller) + } else { + args$at = axTicks(args$side) # FIXME: log ? + args$labels = tinylabel(args$at, labeller) + } + } do.call("Axis", args) } } diff --git a/R/tinylabel.R b/R/tinylabel.R new file mode 100644 index 00000000..946dcdac --- /dev/null +++ b/R/tinylabel.R @@ -0,0 +1,51 @@ +#' Format labels +#' +#' @description Internal function for formatting label appearance, e.g. axis +#' ticks labels. This is what the top-level `xaxl` and `yaxl` arguments +#' ultimately get passed to. +#' @param x a numeric or character vector +#' @param labeller a formatting function to be applied to `x`, e.g. `abs`, +#' `topper`, etc. Can also be one of the following convenience strings, for +#' which common formatting transformations are provided: `"percent"`, +#' `"comma"`, `"dollar"`, `"euro"`, or `"sterling"`. +#' +#' @keywords internal +tinylabel = function(x, labeller = NULL) { + if (is.null(labeller)) return(x) + if (is.character(labeller)) labeller = labeller_fun((labeller)) + return(labeller(x)) +} + + +labeller_fun = function(label = c("percent", "comma", "dollar", "euro", "sterling")) { + label = match.arg(label) + + format_percent = function(x) { + sprintf("%.0f%%", x * 100) + } + + format_comma = function(x) { + prettyNum(x, big.mark = ",", scientific = FALSE) + } + + format_dollar = function(x) { + paste0("$", prettyNum(x, big.mark = ",", scientific = FALSE)) + } + + format_euro = function(x) { + paste0("\u20ac", prettyNum(x, big.mark = ",", scientific = FALSE)) + } + + format_sterling = function(x) { + paste0("\u00a3", prettyNum(x, big.mark = ",", scientific = FALSE)) + } + + switch( + label, + percent = format_percent, + comma = format_comma, + dollar = format_dollar, + euro = format_euro, + sterling = format_sterling + ) +} diff --git a/R/tinyplot.R b/R/tinyplot.R index 75447fab..cbd0838f 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -13,6 +13,11 @@ #' 'Examples' section below, or the function #' \code{\link[grDevices]{xy.coords}} for details. If supplied separately, `x` #' and `y` must be of the same length. +#' @param xmin,xmax,ymin,ymax minimum and maximum coordinates of relevant area +#' or interval plot types. Only used when the `type` argument is one of +#' `"rect"` or `"segments"` (where all four min-max coordinates are required), +#' or `"pointrange"`, `"errorbar"`, or `"ribbon"` (where only `ymin` and +#' `ymax` required alongside `x`). #' @param by grouping variable(s). The default behaviour is for groups to be #' represented in the form of distinct colours, which will also trigger an #' automatic legend. (See `legend` below for customization options.) However, @@ -126,31 +131,35 @@ #' - [`type_vline()`]: vertical line(s). #' - [`type_function()`]: arbitrary function. #' - [`type_summary()`]: summarize `y` by unique values of `x`. -#' @param xmin,xmax,ymin,ymax minimum and maximum coordinates of relevant area -#' or interval plot types. Only used when the `type` argument is one of -#' `"rect"` or `"segments"` (where all four min-max coordinates are required), -#' or `"pointrange"`, `"errorbar"`, or `"ribbon"` (where only `ymin` and -#' `ymax` required alongside `x`). -#' @param xlim the x limits (x1, x2) of the plot. Note that x1 > x2 is allowed -#' and leads to a ‘reversed axis’. The default value, NULL, indicates that -#' the range of the `finite` values to be plotted should be used. -#' @param ylim the y limits of the plot. -#' @param log a character string which contains "x" if the x axis is to be -#' logarithmic, "y" if the y axis is to be logarithmic and "xy" or "yx" if -#' both axes are to be logarithmic. -#' @param empty logical indicating whether the interior plot region should be -#' left empty. The default is `FALSE`. Setting to `TRUE` has a similar effect -#' to invoking `type = "n"` above, except that any legend artifacts owing to a -#' particular plot type (e.g., lines for `type = "l"` or squares for -#' `type = "area"`) will still be drawn correctly alongside the empty plot. In -#' contrast,`type = "n"` implicitly assumes a scatterplot and so any legend -#' will only depict points. +#' @param legend one of the following options: +#' - NULL (default), in which case the legend will be determined by the +#' grouping variable. If there is no group variable (i.e., `by` is NULL) then +#' no legend is drawn. If a grouping variable is detected, then an automatic +#' legend is drawn to the _outer_ right of the plotting area. Note that the +#' legend title and categories will automatically be inferred from the `by` +#' argument and underlying data. +#' - A convenience string indicating the legend position. The string should +#' correspond to one of the position keywords supported by the base `legend` +#' function, e.g. "right", "topleft", "bottom", etc. In addition, `tinyplot` +#' supports adding a trailing exclamation point to these keywords, e.g. +#' "right!", "topleft!", or "bottom!". This will place the legend _outside_ +#' the plotting area and adjust the margins of the plot accordingly. Finally, +#' users can also turn off any legend printing by specifying "none". +#' - Logical value, where TRUE corresponds to the default case above (same +#' effect as specifying NULL) and FALSE turns the legend off (same effect as +#' specifying "none"). +#' - A list or, equivalently, a dedicated `legend()` function with supported +#' legend arguments, e.g. "bty", "horiz", and so forth. #' @param main a main title for the plot, see also `title`. #' @param sub a subtitle for the plot. #' @param xlab a label for the x axis, defaults to a description of x. #' @param ylab a label for the y axis, defaults to a description of y. #' @param ann a logical value indicating whether the default annotation (title #' and x and y axis labels) should appear on the plot. +#' @param xlim the x limits (x1, x2) of the plot. Note that x1 > x2 is allowed +#' and leads to a ‘reversed axis’. The default value, NULL, indicates that +#' the range of the `finite` values to be plotted should be used. +#' @param ylim the y limits of the plot. #' @param axes logical or character. Should axes be drawn (`TRUE` or `FALSE`)? #' Or alternatively what type of axes should be drawn: `"standard"` (with #' axis, ticks, and labels; equivalent to `TRUE`), `"none"` (no axes; @@ -158,6 +167,22 @@ #' `"labels"` (only labels without ticks and axis line), `"axis"` (only axis #' line and labels but no ticks). To control this separately for the two #' axes, use the character specifications for `xaxt` and/or `yaxt`. +#' @param xaxt,yaxt character specifying the type of x-axis and y-axis, respectively. +#' See `axes` for the possible values. +#' @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 xaxl,yaxl A formatting function (or character string) to apply to the +#' x- or y-axis tick labels. This affects the _appearance_ of the labels only, +#' not the calculation or positioning of the tick marks. In addition to custom +#' functions, users can supply one of several convenience strings for common +#' formats: `"percent"`, `"comma"`, `"dollar"`, `"euro"`, or `"sterling"`. +#' @param log a character string which contains "x" if the x axis is to be +#' logarithmic, "y" if the y axis is to be logarithmic and "xy" or "yx" if +#' both axes are to be logarithmic. +#' @param flip logical. Should the plot orientation be flipped, so that the +#' y-axis is on the horizontal plane and the x-axis is on the vertical plane? +#' Default is FALSE. #' @param frame.plot a logical indicating whether a box should be drawn around #' the plot. Can also use `frame` as an acceptable argument alias. #' The default is to draw a frame if both axis types (set via `axes`, `xaxt`, @@ -169,7 +194,6 @@ #' arguments from base `plot()` and tries to make the process more seamless #' with better default behaviour. The default behaviour is determined by (and #' can be set globally through) the value of `tpar("grid")`. -#' @param asp the y/xy/x aspect ratio, see `plot.window`. #' @param palette one of the following options: #' - NULL (default), in which case the palette will be chosen according to #' the class and cardinality of the "by" grouping variable. For non-ordered @@ -194,25 +218,6 @@ #' If too few colours are provided for a discrete (qualitative) set of #' groups, then the colours will be recycled with a warning. For continuous #' (sequential) groups, a gradient palette will be interpolated. -#' @param legend one of the following options: -#' - NULL (default), in which case the legend will be determined by the -#' grouping variable. If there is no group variable (i.e., `by` is NULL) then -#' no legend is drawn. If a grouping variable is detected, then an automatic -#' legend is drawn to the _outer_ right of the plotting area. Note that the -#' legend title and categories will automatically be inferred from the `by` -#' argument and underlying data. -#' - A convenience string indicating the legend position. The string should -#' correspond to one of the position keywords supported by the base `legend` -#' function, e.g. "right", "topleft", "bottom", etc. In addition, `tinyplot` -#' supports adding a trailing exclamation point to these keywords, e.g. -#' "right!", "topleft!", or "bottom!". This will place the legend _outside_ -#' the plotting area and adjust the margins of the plot accordingly. Finally, -#' users can also turn off any legend printing by specifying "none". -#' - Logical value, where TRUE corresponds to the default case above (same -#' effect as specifying NULL) and FALSE turns the legend off (same effect as -#' specifying "none"). -#' - A list or, equivalently, a dedicated `legend()` function with supported -#' legend arguments, e.g. "bty", "horiz", and so forth. #' @param col plotting color. Character, integer, or vector of length equal to #' the number of categories in the `by` variable. See `col`. Note that the #' default behaviour in `tinyplot` is to vary group colors along any variables @@ -265,15 +270,6 @@ #' giving the amount by which plotting characters and symbols should be scaled #' relative to the default. Note that NULL is equivalent to 1.0, while NA #' renders the characters invisible. -#' @param restore.par a logical value indicating whether the -#' \code{\link[graphics]{par}} settings prior to calling `tinyplot` should be -#' restored on exit. Defaults to FALSE, which makes it possible to add -#' elements to the plot after it has been drawn. However, note the the outer -#' margins of the graphics device may have been altered to make space for the -#' `tinyplot` legend. Users can opt out of this persistent behaviour by -#' setting to TRUE instead. See also [get_saved_par] for another option to -#' recover the original \code{\link[graphics]{par}} settings, as well as -#' longer discussion about the trade-offs involved. #' @param subset,na.action,drop.unused.levels arguments passed to `model.frame` #' when extracting the data from `formula` and `data`. #' @param add logical. If TRUE, then elements are added to the current plot rather @@ -288,9 +284,22 @@ #' this argument is somewhat experimental and that _no_ internal checking is #' done for correctness; the provided argument is simply captured and #' evaluated as-is. See Examples. -#' @param flip logical. Should the plot orientation be flipped, so that the -#' y-axis is on the horizontal plane and the x-axis is on the vertical plane? -#' Default is FALSE. +#' @param restore.par a logical value indicating whether the +#' \code{\link[graphics]{par}} settings prior to calling `tinyplot` should be +#' restored on exit. Defaults to FALSE, which makes it possible to add +#' elements to the plot after it has been drawn. However, note the the outer +#' margins of the graphics device may have been altered to make space for the +#' `tinyplot` legend. Users can opt out of this persistent behaviour by +#' setting to TRUE instead. See also [get_saved_par] for another option to +#' recover the original \code{\link[graphics]{par}} settings, as well as +#' longer discussion about the trade-offs involved. +#' @param empty logical indicating whether the interior plot region should be +#' left empty. The default is `FALSE`. Setting to `TRUE` has a similar effect +#' to invoking `type = "n"` above, except that any legend artifacts owing to a +#' particular plot type (e.g., lines for `type = "l"` or squares for +#' `type = "area"`) will still be drawn correctly alongside the empty plot. In +#' contrast,`type = "n"` implicitly assumes a scatterplot and so any legend +#' will only depict points. #' @param file character string giving the file path for writing a plot to disk. #' If specified, the plot will not be displayed interactively, but rather sent #' to the appropriate external graphics device (i.e., @@ -319,11 +328,7 @@ #' @param height numeric giving the plot height in inches. Same considerations as #' `width` (above) apply, e.g. will default to `tpar("file.height")` if not #' specified. -#' @param xaxt,yaxt character specifying the type of x-axis and y-axis, respectively. -#' See `axes` for the possible values. -#' @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 asp the y/xy/x aspect ratio, see `plot.window`. #' @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. @@ -340,7 +345,7 @@ #' without causing unexpected changes to the output. #' #' @importFrom grDevices axisTicks adjustcolor cairo_pdf colorRampPalette extendrange palette palette.colors palette.pals hcl.colors hcl.pals xy.coords png jpeg pdf svg dev.off dev.new dev.list -#' @importFrom graphics abline arrows axis Axis box boxplot grconvertX grconvertY hist lines mtext par plot.default plot.new plot.window points polygon polypath segments rect text title +#' @importFrom graphics abline arrows axis Axis axTicks box boxplot grconvertX grconvertY hist lines mtext par plot.default plot.new plot.window points polygon polypath segments rect text title #' @importFrom utils modifyList head tail #' @importFrom stats na.omit #' @importFrom tools file_ext @@ -540,25 +545,35 @@ tinyplot = tinyplot.default = function( x = NULL, y = NULL, + xmin = NULL, + xmax = NULL, + ymin = NULL, + ymax = NULL, by = NULL, facet = NULL, facet.args = NULL, data = NULL, type = NULL, - xlim = NULL, - ylim = NULL, - log = "", + legend = NULL, main = NULL, sub = NULL, xlab = NULL, ylab = NULL, ann = par("ann"), + xlim = NULL, + ylim = NULL, axes = TRUE, + xaxt = NULL, + yaxt = NULL, + xaxs = NULL, + yaxs = NULL, + xaxl = NULL, + yaxl = NULL, + log = "", + flip = FALSE, frame.plot = NULL, - asp = NA, grid = NULL, palette = NULL, - legend = NULL, pch = NULL, lty = NULL, lwd = NULL, @@ -567,22 +582,14 @@ tinyplot.default = function( fill = NULL, alpha = NULL, cex = 1, - restore.par = FALSE, - xmin = NULL, - xmax = NULL, - ymin = NULL, - ymax = NULL, add = FALSE, draw = NULL, + empty = FALSE, + restore.par = FALSE, file = NULL, width = NULL, height = NULL, - empty = FALSE, - xaxt = NULL, - yaxt = NULL, - flip = FALSE, - xaxs = NULL, - yaxs = NULL, + asp = NA, ...) { par_first = get_saved_par("first") if (is.null(par_first)) set_saved_par("first", par()) @@ -779,10 +786,12 @@ tinyplot.default = function( palette = palette, ribbon.alpha = ribbon.alpha, xaxt = xaxt, + xaxl = xaxl, xlab = xlab, xlabs = xlabs, xlim = xlim, yaxt = yaxt, + yaxl = yaxl, ylab = ylab, ylim = ylim ) @@ -813,6 +822,9 @@ tinyplot.default = function( xaxs_cp = xaxs xaxs = yaxs yaxs = xaxs_cp + xaxl_cp = xaxl + xaxl = yaxl + yaxl = xaxl_cp if (!is.null(log)) { log = if (log == "x") "y" else if (log == "y") "x" else log } @@ -828,7 +840,7 @@ tinyplot.default = function( datapoints[["xmax"]] = if (!is.null(datapoints[["ymax"]])) datapoints[["ymax"]] else NULL datapoints[["ymax"]] = if (!is.null(xmax_cp)) xmax_cp else NULL # clean up - rm(xlim_cp, xlab_cp, xlabs_cp, xaxt_cp, xaxs_cp, x_cp, xmin_cp, xmax_cp) + rm(xlim_cp, xlab_cp, xlabs_cp, xaxt_cp, xaxs_cp, xaxl_cp, x_cp, xmin_cp, xmax_cp) } else { # We'll let boxplot(..., horizontal = TRUE) handle most of the adjustments # and just catch a few elements that we draw beforehand. @@ -1129,11 +1141,12 @@ tinyplot.default = function( y = datapoints$y, xmax = datapoints$xmax, xmin = datapoints$xmin, ymax = datapoints$ymax, ymin = datapoints$ymin, - xaxt = xaxt, xlabs = xlabs, xlim = xlim, - yaxt = yaxt, ylabs = ylabs, ylim = ylim, + xlabs = xlabs, xlim = xlim, + ylabs = ylabs, ylim = ylim, + xaxt = xaxt, xaxs = xaxs, xaxl = xaxl, + yaxt = yaxt, yaxs = yaxs, yaxl = yaxl, flip = flip, - draw = draw, - xaxs = xaxs, yaxs = yaxs + draw = draw ) list2env(facet_window_args, environment()) diff --git a/R/zzz.R b/R/zzz.R index b8a375ca..0c5ae07e 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -54,6 +54,7 @@ "split_data", "type", "x", + "xaxl", "xaxs", "xaxt", "xlabs", @@ -62,6 +63,7 @@ "xmax", "xmin", "y", + "yaxl", "yaxs", "yaxt", "ylabs", diff --git a/inst/tinytest/_tinysnapshot/tinytheme_dynamic_yaxl.svg b/inst/tinytest/_tinysnapshot/tinytheme_dynamic_yaxl.svg new file mode 100644 index 00000000..94562a16 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/tinytheme_dynamic_yaxl.svg @@ -0,0 +1,137 @@ + + + + + + + + + + + + +treatment +I(decrease/100) + + + + + + + + + +A +B +C +D +E +F +G +H + + + + + + + + +0% +20% +40% +60% +80% +100% +120% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/xaxl_yaxl.svg b/inst/tinytest/_tinysnapshot/xaxl_yaxl.svg new file mode 100644 index 00000000..8057601b --- /dev/null +++ b/inst/tinytest/_tinysnapshot/xaxl_yaxl.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + +treatment +I(decrease/100) + + + + + + + + + +a +b +c +d +e +f +g +h + + + + + + + + +0% +20% +40% +60% +80% +100% +120% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-misc.R b/inst/tinytest/test-misc.R index 6da33336..17b36adf 100644 --- a/inst/tinytest/test-misc.R +++ b/inst/tinytest/test-misc.R @@ -74,6 +74,15 @@ f = function() { } expect_snapshot_plot(f, label = "addTRUE") +# formatting axis tick labels +f = function() plt( + I(decrease/100) ~ treatment, data = OrchardSprays, + xaxl = tolower, yaxl = "percent" +) +expect_snapshot_plot(f, label = "xaxl_yaxl") + + +# saving files (here: png) if (requireNamespace("png", quietly = TRUE)) { f = function() { tmp_path = tempfile(fileext = ".png") diff --git a/inst/tinytest/test-tinytheme.R b/inst/tinytest/test-tinytheme.R index d89b92bf..f2ba29bf 100644 --- a/inst/tinytest/test-tinytheme.R +++ b/inst/tinytest/test-tinytheme.R @@ -79,6 +79,18 @@ expect_snapshot_plot(f, label = "tinytheme_dynamic_dark_facet") tinytheme() +# variation with formatted tick labels +f = function() { + tinytheme("clean") + plt( + I(decrease/100) ~ treatment, data = OrchardSprays, + yaxl = "percent" + ) + tinytheme() +} +expect_snapshot_plot(f, label = "tinytheme_dynamic_yaxl") + + # flipped jitter and boxplot use special internal logic (because of integer spacing) f = function() { diff --git a/man/tinyAxis.Rd b/man/tinyAxis.Rd index f61b56d2..fc7792d1 100644 --- a/man/tinyAxis.Rd +++ b/man/tinyAxis.Rd @@ -4,7 +4,7 @@ \alias{tinyAxis} \title{auxiliary Axis() interface with different parameter combinations based on type} \usage{ -tinyAxis(x = NULL, ..., type = "standard") +tinyAxis(x = NULL, ..., type = "standard", labeller = NULL) } \description{ auxiliary Axis() interface with different parameter combinations based on type diff --git a/man/tinylabel.Rd b/man/tinylabel.Rd new file mode 100644 index 00000000..53d8239c --- /dev/null +++ b/man/tinylabel.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tinylabel.R +\name{tinylabel} +\alias{tinylabel} +\title{Format labels} +\usage{ +tinylabel(x, labeller = NULL) +} +\arguments{ +\item{x}{a numeric or character vector} + +\item{labeller}{a formatting function to be applied to \code{x}, e.g. \code{abs}, +\code{topper}, etc. Can also be one of the following convenience strings, for +which common formatting transformations are provided: \code{"percent"}, +\code{"comma"}, \code{"dollar"}, \code{"euro"}, or \code{"sterling"}.} +} +\description{ +Internal function for formatting label appearance, e.g. axis +ticks labels. This is what the top-level \code{xaxl} and \code{yaxl} arguments +ultimately get passed to. +} +\keyword{internal} diff --git a/man/tinyplot.Rd b/man/tinyplot.Rd index f65202c2..799369d3 100644 --- a/man/tinyplot.Rd +++ b/man/tinyplot.Rd @@ -13,25 +13,35 @@ tinyplot(x, ...) \method{tinyplot}{default}( x = NULL, y = NULL, + xmin = NULL, + xmax = NULL, + ymin = NULL, + ymax = NULL, by = NULL, facet = NULL, facet.args = NULL, data = NULL, type = NULL, - xlim = NULL, - ylim = NULL, - log = "", + legend = NULL, main = NULL, sub = NULL, xlab = NULL, ylab = NULL, ann = par("ann"), + xlim = NULL, + ylim = NULL, axes = TRUE, + xaxt = NULL, + yaxt = NULL, + xaxs = NULL, + yaxs = NULL, + xaxl = NULL, + yaxl = NULL, + log = "", + flip = FALSE, frame.plot = NULL, - asp = NA, grid = NULL, palette = NULL, - legend = NULL, pch = NULL, lty = NULL, lwd = NULL, @@ -40,22 +50,14 @@ tinyplot(x, ...) fill = NULL, alpha = NULL, cex = 1, - restore.par = FALSE, - xmin = NULL, - xmax = NULL, - ymin = NULL, - ymax = NULL, add = FALSE, draw = NULL, + empty = FALSE, + restore.par = FALSE, file = NULL, width = NULL, height = NULL, - empty = FALSE, - xaxt = NULL, - yaxt = NULL, - flip = FALSE, - xaxs = NULL, - yaxs = NULL, + asp = NA, ... ) @@ -106,6 +108,12 @@ and \code{y} must be of the same length.} All remaining arguments from \code{...} can be further graphical parameters, see \code{\link[graphics]{par}}).} +\item{xmin, xmax, ymin, ymax}{minimum and maximum coordinates of relevant area +or interval plot types. Only used when the \code{type} argument is one of +\code{"rect"} or \code{"segments"} (where all four min-max coordinates are required), +or \code{"pointrange"}, \code{"errorbar"}, or \code{"ribbon"} (where only \code{ymin} and +\code{ymax} required alongside \code{x}).} + \item{by}{grouping variable(s). The default behaviour is for groups to be represented in the form of distinct colours, which will also trigger an automatic legend. (See \code{legend} below for customization options.) However, @@ -232,15 +240,27 @@ type of plot desired. } }} -\item{xlim}{the x limits (x1, x2) of the plot. Note that x1 > x2 is allowed -and leads to a ‘reversed axis’. The default value, NULL, indicates that -the range of the \code{finite} values to be plotted should be used.} - -\item{ylim}{the y limits of the plot.} - -\item{log}{a character string which contains "x" if the x axis is to be -logarithmic, "y" if the y axis is to be logarithmic and "xy" or "yx" if -both axes are to be logarithmic.} +\item{legend}{one of the following options: +\itemize{ +\item NULL (default), in which case the legend will be determined by the +grouping variable. If there is no group variable (i.e., \code{by} is NULL) then +no legend is drawn. If a grouping variable is detected, then an automatic +legend is drawn to the \emph{outer} right of the plotting area. Note that the +legend title and categories will automatically be inferred from the \code{by} +argument and underlying data. +\item A convenience string indicating the legend position. The string should +correspond to one of the position keywords supported by the base \code{legend} +function, e.g. "right", "topleft", "bottom", etc. In addition, \code{tinyplot} +supports adding a trailing exclamation point to these keywords, e.g. +"right!", "topleft!", or "bottom!". This will place the legend \emph{outside} +the plotting area and adjust the margins of the plot accordingly. Finally, +users can also turn off any legend printing by specifying "none". +\item Logical value, where TRUE corresponds to the default case above (same +effect as specifying NULL) and FALSE turns the legend off (same effect as +specifying "none"). +\item A list or, equivalently, a dedicated \code{legend()} function with supported +legend arguments, e.g. "bty", "horiz", and so forth. +}} \item{main}{a main title for the plot, see also \code{title}.} @@ -253,6 +273,12 @@ both axes are to be logarithmic.} \item{ann}{a logical value indicating whether the default annotation (title and x and y axis labels) should appear on the plot.} +\item{xlim}{the x limits (x1, x2) of the plot. Note that x1 > x2 is allowed +and leads to a ‘reversed axis’. The default value, NULL, indicates that +the range of the \code{finite} values to be plotted should be used.} + +\item{ylim}{the y limits of the plot.} + \item{axes}{logical or character. Should axes be drawn (\code{TRUE} or \code{FALSE})? Or alternatively what type of axes should be drawn: \code{"standard"} (with axis, ticks, and labels; equivalent to \code{TRUE}), \code{"none"} (no axes; @@ -261,13 +287,32 @@ equivalent to \code{FALSE}), \code{"ticks"} (only ticks and labels without axis line and labels but no ticks). To control this separately for the two axes, use the character specifications for \code{xaxt} and/or \code{yaxt}.} +\item{xaxt, yaxt}{character specifying the type of x-axis and y-axis, respectively. +See \code{axes} for the possible values.} + +\item{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.} + +\item{xaxl, yaxl}{A formatting function (or character string) to apply to the +x- or y-axis tick labels. This affects the \emph{appearance} of the labels only, +not the calculation or positioning of the tick marks. In addition to custom +functions, users can supply one of several convenience strings for common +formats: \code{"percent"}, \code{"comma"}, \code{"dollar"}, \code{"euro"}, or \code{"sterling"}.} + +\item{log}{a character string which contains "x" if the x axis is to be +logarithmic, "y" if the y axis is to be logarithmic and "xy" or "yx" if +both axes are to be logarithmic.} + +\item{flip}{logical. Should the plot orientation be flipped, so that the +y-axis is on the horizontal plane and the x-axis is on the vertical plane? +Default is FALSE.} + \item{frame.plot}{a logical indicating whether a box should be drawn around the plot. Can also use \code{frame} as an acceptable argument alias. The default is to draw a frame if both axis types (set via \code{axes}, \code{xaxt}, or \code{yaxt}) include axis lines.} -\item{asp}{the y/xy/x aspect ratio, see \code{plot.window}.} - \item{grid}{argument for plotting a background panel grid, one of either: \itemize{ \item a logical (i.e., \code{TRUE} to draw the grid), or @@ -305,28 +350,6 @@ groups, then the colours will be recycled with a warning. For continuous (sequential) groups, a gradient palette will be interpolated. }} -\item{legend}{one of the following options: -\itemize{ -\item NULL (default), in which case the legend will be determined by the -grouping variable. If there is no group variable (i.e., \code{by} is NULL) then -no legend is drawn. If a grouping variable is detected, then an automatic -legend is drawn to the \emph{outer} right of the plotting area. Note that the -legend title and categories will automatically be inferred from the \code{by} -argument and underlying data. -\item A convenience string indicating the legend position. The string should -correspond to one of the position keywords supported by the base \code{legend} -function, e.g. "right", "topleft", "bottom", etc. In addition, \code{tinyplot} -supports adding a trailing exclamation point to these keywords, e.g. -"right!", "topleft!", or "bottom!". This will place the legend \emph{outside} -the plotting area and adjust the margins of the plot accordingly. Finally, -users can also turn off any legend printing by specifying "none". -\item Logical value, where TRUE corresponds to the default case above (same -effect as specifying NULL) and FALSE turns the legend off (same effect as -specifying "none"). -\item A list or, equivalently, a dedicated \code{legend()} function with supported -legend arguments, e.g. "bty", "horiz", and so forth. -}} - \item{pch}{plotting "character", i.e., symbol to use. Character, integer, or vector of length equal to the number of categories in the \code{by} variable. See \code{pch}. In addition, users can supply a special \code{pch = "by"} convenience @@ -387,22 +410,6 @@ giving the amount by which plotting characters and symbols should be scaled relative to the default. Note that NULL is equivalent to 1.0, while NA renders the characters invisible.} -\item{restore.par}{a logical value indicating whether the -\code{\link[graphics]{par}} settings prior to calling \code{tinyplot} should be -restored on exit. Defaults to FALSE, which makes it possible to add -elements to the plot after it has been drawn. However, note the the outer -margins of the graphics device may have been altered to make space for the -\code{tinyplot} legend. Users can opt out of this persistent behaviour by -setting to TRUE instead. See also \link{get_saved_par} for another option to -recover the original \code{\link[graphics]{par}} settings, as well as -longer discussion about the trade-offs involved.} - -\item{xmin, xmax, ymin, ymax}{minimum and maximum coordinates of relevant area -or interval plot types. Only used when the \code{type} argument is one of -\code{"rect"} or \code{"segments"} (where all four min-max coordinates are required), -or \code{"pointrange"}, \code{"errorbar"}, or \code{"ribbon"} (where only \code{ymin} and -\code{ymax} required alongside \code{x}).} - \item{add}{logical. If TRUE, then elements are added to the current plot rather than drawing a new plot window. Note that the automatic legend for the added elements will be turned off. See also \link{tinyplot_add}, which provides @@ -417,6 +424,24 @@ this argument is somewhat experimental and that \emph{no} internal checking is done for correctness; the provided argument is simply captured and evaluated as-is. See Examples.} +\item{empty}{logical indicating whether the interior plot region should be +left empty. The default is \code{FALSE}. Setting to \code{TRUE} has a similar effect +to invoking \code{type = "n"} above, except that any legend artifacts owing to a +particular plot type (e.g., lines for \code{type = "l"} or squares for +\code{type = "area"}) will still be drawn correctly alongside the empty plot. In +contrast,\code{type = "n"} implicitly assumes a scatterplot and so any legend +will only depict points.} + +\item{restore.par}{a logical value indicating whether the +\code{\link[graphics]{par}} settings prior to calling \code{tinyplot} should be +restored on exit. Defaults to FALSE, which makes it possible to add +elements to the plot after it has been drawn. However, note the the outer +margins of the graphics device may have been altered to make space for the +\code{tinyplot} legend. Users can opt out of this persistent behaviour by +setting to TRUE instead. See also \link{get_saved_par} for another option to +recover the original \code{\link[graphics]{par}} settings, as well as +longer discussion about the trade-offs involved.} + \item{file}{character string giving the file path for writing a plot to disk. If specified, the plot will not be displayed interactively, but rather sent to the appropriate external graphics device (i.e., @@ -448,24 +473,7 @@ graphics windows.} \code{width} (above) apply, e.g. will default to \code{tpar("file.height")} if not specified.} -\item{empty}{logical indicating whether the interior plot region should be -left empty. The default is \code{FALSE}. Setting to \code{TRUE} has a similar effect -to invoking \code{type = "n"} above, except that any legend artifacts owing to a -particular plot type (e.g., lines for \code{type = "l"} or squares for -\code{type = "area"}) will still be drawn correctly alongside the empty plot. In -contrast,\code{type = "n"} implicitly assumes a scatterplot and so any legend -will only depict points.} - -\item{xaxt, yaxt}{character specifying the type of x-axis and y-axis, respectively. -See \code{axes} for the possible values.} - -\item{flip}{logical. Should the plot orientation be flipped, so that the -y-axis is on the horizontal plane and the x-axis is on the vertical plane? -Default is FALSE.} - -\item{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.} +\item{asp}{the y/xy/x aspect ratio, see \code{plot.window}.} \item{formula}{a \code{\link[stats]{formula}} that optionally includes grouping variable(s) after a vertical bar, e.g. \code{y ~ x | z}. One-sided