diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index c7c2fcf5..39b163af 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -34,6 +34,16 @@ jobs: steps: - uses: actions/checkout@v6 + # r-ci's run.sh (hotfix a07614a, 2026-08-19) seds /etc/apt/apt-mirrors.txt + # unconditionally to drop the flaky azure mirror. That file only exists on + # GitHub-hosted runner images, not in containers, so the sed exits 2 and + # kills the step under `bash -e`. (r2u4ci escapes it via the /etc/r2u_ci + # stamp, which makes Bootstrap() skip BootstrapLinux entirely; drd has no + # such stamp.) An empty file is inert here -- containers never used the + # azure mirror. Drop once upstream guards the sed with `test -f`. + - name: Work around r-ci apt-mirrors.txt assumption + run: touch /etc/apt/apt-mirrors.txt + - name: Setup uses: eddelbuettel/github-actions/r-ci@master diff --git a/NEWS.md b/NEWS.md index 2703a9c0..33c0724d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,18 @@ where the formatting is also better._ ## Development version +### Breaking changes + +- `type_lines()` and its shortcut equivalents like `"l"` and `"b"` now order + categorical `x` data by (coerced) factor levels, rather than simple order of + appearance. This resolves a longstanding tension between line types and + other types like `type_points()`, which have always ordered the `x` axis by + implied factor levels. It also improves layering consistency via `plt_add()` + and co. so that plots are identical, regardless of whether lines are layered + on top of points, or vice versa. Note that you can still select into the + old behaviour by passing the (new) `xlevels = "asis"` argument as an + explicit override; see "Other new features" below. (#683 @grantmcdermott) + ### New features #### New plot types @@ -15,7 +27,7 @@ where the formatting is also better._ - `type_tile()` / `"tile"` for tile plots, i.e. a grid of rectangles whose fill encodes a third variable. (#677 @grantmcdermott) - `type_heatmap()` / `"heatmap"` builds on `type_tile()`, adding a `scale` - argument that scales the fill values *within* each category of one axis. This + argument that scales the fill values _within_ each category of one axis. This is analogous to base R's `heatmap()` function, and like the latter it z-scores along the chosen margin by default. It also reverses the y-axis by default, so that the first row sits at the top (again matching `heatmap()`); pass an @@ -23,21 +35,31 @@ where the formatting is also better._ #### Other new features +- `type_points()`, `type_lines()`, `type_errorbar()`, and `type_pointrange()` + gain an `xlevels` argument for reordering a categorical `x` variable on the + fly (matching existing functionality for `type_barplot()` and several other + types). Values can be a character vector of level names, a numeric vector of + level indexes (e.g., `3:1`), or the new `"asis"` keyword, which takes the + categories in the order that they appear in the data. The latter option is + also the default for `type_errorbar()` and `type_pointrange()`, thus + preserving existing behaviour since these two types are typically fed + coefficient table data where the row order is intentional. + (#683 @grantmcdermott) - Custom plot types have more control over the surrounding plot machinery, via a new `type_hints` mechanism. A type can declare properties about itself---that it draws its own axes, needs a secondary right-hand axis, uses proportional limits, fills its legend key from `col`, and so on---and **tinyplot** adjusts - margins, axis limits and legend keys accordingly. Previously this behaviour was - hard-coded against the names of built-in types, so it was unavailable to custom - types. See + margins, axis limits and legend keys accordingly. Previously this behaviour + was hard-coded against the names of built-in types, so it was unavailable to + custom types. See [Advanced customization](https://grantmcdermott.com/tinyplot/vignettes/types.html#type-hints) in the `Types` vignette for the list of supported hints. (#543 @grantmcdermott) -- New `axes` argument for `facet.args`, giving explicit control over which facets - draw their own axes: `"all"`, `"outer"` (drop redundant interior axes), or - `"none"`. Previously this was only achievable as a side effect of +- New `axes` argument for `facet.args`, giving explicit control over which + facets draw their own axes: `"all"`, `"outer"` (drop redundant interior + axes), or `"none"`. Previously this was only achievable as a side effect of `frame.plot = FALSE`, so `facet.args = list(axes = "outer")` now allows - redundant axes to be dropped while *keeping* the facet frames. (#661, #673 - @grantmcdermott) + redundant axes to be dropped while _keeping_ the facet frames. + (#661, #673 @grantmcdermott) - The same behaviour can be set globally via the new `facet.axes` parameter (note the reverse order), e.g. `tpar(facet.axes = "outer")`, which also makes it available to themes. A per-call `facet.args = list(axes = ...)` @@ -53,7 +75,7 @@ where the formatting is also better._ labelled is left alone. Shared bandwidths are reported once and named as joint, individual bandwidths per group. (#287 @haomeng797-ship-it) - New `cex.xaxs` and `cex.yaxs` graphical parameters allow the x- and y-axis - tick labels to be sized independently, e.g. `tpar(cex.yaxs = 0.6)` to shrink a + tick labels to be sized independently, e.g. `tpar(cex.yaxs = 0.6)` to shrink a long list of category names on the y-axis without also shrinking the x-axis. Both default to `NULL`, in which case the shared `cex.axis` value is used, so existing plots are unaffected. (#677 @grantmcdermott) @@ -70,8 +92,22 @@ where the formatting is also better._ not just the axes: `type = "h"` draws horizontal segments to the baseline, and the step types `"s"` and `"S"` swap which coordinate moves first. (#675 @haomeng797-ship-it) +- Line types now keep the category labels on a categorical y-axis, both for + `flip = TRUE` and for a factor `y` variable. Previously the y-axis fell back + to numeric tick labels for every line type except `"p"`. + (#679 @grantmcdermott) +- Added layers now align on the category that each row belongs to, rather than + on the row's _position_. The latter only coincided with the right answer when + the added layer's rows happened to arrive in ascending order; other rows were + permuted, and repeated categories collapsed onto a single position. + (#679 @grantmcdermott) - Fixed several bugs specific to plots with free facets (i.e., `facet.args = list(free = TRUE)`): + - A categorical y-axis no longer errors out with `'labels' is supplied and +not 'at'`. The free-facet code path listed the eligible types by name, so + any other type lost its tick positions while keeping the corresponding + labels, whether flipped (e.g. `type = "b"` with `flip = TRUE`) or not + (e.g. `type = "p"` with a factor `y` variable). (#679 @grantmcdermott) - Single-valued discrete axes no longer trigger invalid `par(usr)` values. (#668 @grantmcdermott) - User-provided `x/ylim` overrides now work correctly with flipped plots. @@ -125,9 +161,9 @@ below for easier navigation. ### Aesthetic changes -A major focus of v0.7.0 is bringing various aesthetic improvements to +A major focus of v0.7.0 is bringing various aesthetic improvements to **tinyplot**. These aesthetic improvements should carry over to all of your -(tiny)plots automatically and do not require any changes to user-facing inputs +(tiny)plots automatically and do not require any changes to user-facing inputs or the core API. From that perspective they are not a breaking change, even though some of your plots may look slightly different from before. Still, we hope that you agree the following changes result in better looking @@ -298,18 +334,19 @@ Theme fixes: - `tinyplot.data.frame()`: Supports direct plotting of data frames, alongside the new top-level function `tinypairs()`. Can be called with or without a formula. One benefit of the former is that it facilitates piping, e.g. - + ```r iris |> plt(Sepal.Length ~ Petal.Width | Species) ``` - + If no formula is provided, then the behaviour depends on the number of variables (columns) in the data frame. For example, a dataset with 3 or more - variables will yield a `pairs()`-style grid of all variable combinations. + variables will yield a `pairs()`-style grid of all variable combinations. Thanks to @mthulin for the suggestion and original implementation idea. (#613, #640 @zeileis @grantmcdermott) + - `tinyplot.matrix()`: for `matrix` objects, e.g. - + ```r plt(VADeaths, type = "b") ``` @@ -317,12 +354,13 @@ Theme fixes: The output largely mimics the base `matplot`/`matlines` equivalents, but with additional **tinyplot** functionality related to automatic legends, options for faceting, etc. (#649 @grantmcdermott) + - `tinyplot.ts()`: for `ts` time series, e.g. - + ```r plt(EuStockMarkets) ``` - + Produces a line plot by default, although users can override by passing an explicit `type` argument. Similarly, multivariate series are faceted by default, but users can also override to obtain, say, a single frame with @@ -331,6 +369,7 @@ Theme fixes: #### Other new features - New and updated top-level `tinyplot()`/`plt()` arguments: + - `cap = ` for adding a caption to your plots. Captions are drawn at the bottom of the plot and are best paired with dynamic themes (since separation from `sub` is guaranteed). Appearance is customizable via @@ -345,28 +384,31 @@ Theme fixes: names can be passed for convenience. Users can also pass a weights argument directly at the type-specific function level, but this must be a vector of correct length (no NSE). For example: - + ```r plt(y ~ x, data = dat, type = "lm", weights = w) # top-level, NSE plt(y ~ x, data = dat, type = type_lm(weights = dat$w)) # type-level, vector ``` - + In addition to NSE convenience, the top-level variant is preferred since it is correctly matched to the model frame construction with the formula method (e.g., so missing values are handled automatically). Thanks to @eleuven for the original suggestion, as well as various discussion participants for helping to frame the scope. (#639 @grantmcdermott) + - `labels = ` for passing labels to `type = "text"`. Like the new `weights` argument (above), the main benefit is the convenience of NSE, as well as the automatic handling of missing values and subsets as part of the model frame construction. For example, compare: - + ```r plt(y ~ x, data = dat, type = "text", labels = labs, subset = x < 10) plt(y ~ x, data = subset(dat, x < 10), type = type_text(labels = subset(dat, x < 10)$labs)) ``` + The `labels` arg is silently ignored for non-text types. (#639 @grantmcdermott) + - The `grid` argument (and `tpar("grid")`) now accepts character strings to control axis-specific grids at different resolutions. Uppercase letters (`"X"`, `"Y"`, `"XY"`) draw grid lines at the standard tick positions, while @@ -384,6 +426,7 @@ Theme fixes: limit and lets the data determine the other. - The string `"rev"` (or `"reverse"`) reverses the auto-computed axis range, without needing to know the data extent in advance. + - Type-specific updates: - `type_barplot()` gains an `offset` argument for shifting bar baselines away from zero. (#611, #615 @grantmcdermott @zeileis) @@ -440,14 +483,14 @@ Theme fixes: `"ridge"` types. (#635, #650 @grantmcdermott) - `tinyplot_add()` (`plt_add()`) now captures its arguments unevaluated, so arguments that rely on non-standard evaluation against `data` (e.g., - `plt_add(..., subset = <>)`) resolve correctly instead of erroring with + `plt_add(..., subset = <>)`) resolve correctly instead of erroring with "object not found". (#638 @grantmcdermott) - `plt(..., ann = FALSE)` correctly turns off title annotations now, fixing a regression that we missed from at least v0.6.0. Thanks to @bastistician for the report. (#641 @zeileis) - Fixed `bquote()` (and other unevaluated language) annotations such as `main`, `sub`, `cap`, `xlab`, and `ylab` being evaluated instead of coerced to - plotmath expressions, e.g. `plt(0, 0, main = bquote(foo == .(pi)))`. Thanks + plotmath expressions, e.g. `plt(0, 0, main = bquote(foo == .(pi)))`. Thanks (again) to @bastistician for the report. (#642 @grantmcdermott) - Line plots (`type = "l"`, and relatives like `"b"`/`"o"`) with a factor or character `x` variable now draw the category labels on the x-axis, matching @@ -488,7 +531,7 @@ Theme fixes: (#565 @grantmcdermott) - Several improvements/fixes to jittered plots and layering: - Jittered plots now support Date/POSIXt axes. Thanks to @wachtermh for the - bug report and @vincentarelbundock for the code contribution. (#327) + bug report and @vincentarelbundock for the code contribution. (#327) - `tinyplot_add(type = "jitter")` no longer errors when layered on top of boxplot, violin, or similar categorical plot types. (#560 @grantmcdermott) - Jitter layers added via `tinyplot_add()` now align correctly with grouped @@ -524,14 +567,14 @@ Theme fixes: will enable various internal enhancements, from improving the modularity and maintainability of the `tinyplot` codebase, to reducing memory overhead and performance (since we require fewer object copies). Looking ahead, we also - expect that it will make it easier to support new features and integration + expect that it will make it easier to support new features and integration with downstream packages. Most `tinyplot` users should be unaffected by these internal changes. However, users who have defined their own custom types will need to make some adjustments to match the new `settings` logic; details are provided in the updated `Types` vignette. (#473 @vincentarelbundock and @grantmcdermott) - The ancillary `fixed.pos` argument for dodged plots has been renamed to `fixed.dodge` to avoid ambiguity, especially when passed down from a top-level - `tinyplot(...)` call. (#528 @grantmcdermott) + `tinyplot(...)` call. (#528 @grantmcdermott) ### New features @@ -575,7 +618,6 @@ Theme fixes: - Custom axis titles work properly for one-sided (formula) bar plots. Thanks to @lbelzile for the report in #423. (#527 @grantmcdermott) - ### Documentation - Add a "recession bars" section to the `Tips & tricks` vignette. @@ -623,8 +665,8 @@ Theme fixes: `options()`. (#460 @zeileis) - Fixed several minor `tinylabel` bugs. (#468 @grantmcdermott) - `tinylabel(x, "%")` is more precise, preserving unique levels of `x` through - automatic decimal level determination. Thanks to @etiennebacher for the - bug report in #449. + automatic decimal level determination. Thanks to @etiennebacher for the + bug report in #449. - Numeric labellers now work on appropriate `x`/`y` variables, even if the plot type internally coerces it to factor (e.g., `"boxplot"`) - `type_text()` can now also deal with factor `x`/`y` variables by converting @@ -645,7 +687,7 @@ Theme fixes: - Move `altdoc` from `Suggests` to `Config/Needs/website`. Thanks to @etiennebacher for the suggestion and to @eddelbuettel for help with the CI implementation. -- Add a `devcontainer.json` file for remote testing. (#480 @grantmcdermott) +- Add a `devcontainer.json` file for remote testing. (#480 @grantmcdermott) ## v0.4.2 @@ -659,7 +701,7 @@ Theme fixes: ### Bug fixes - Fixed a long-standing issue whereby resizing the plot window would cause - secondary plot layers, e.g. from `plt_add()`, to become misaligned in + secondary plot layers, e.g. from `plt_add()`, to become misaligned in faceted plots (#313). This also resolves a related alignment + layering issue specific to the Positron IDE ([positron#7316](https://github.com/posit-dev/positron/issues/7316)). @@ -697,7 +739,7 @@ Theme fixes: - `"barplot"` / `type_barplot()` for bar plots. This closes out one of the last remaining canonical base plot types that we wanted to provide - a native `tinyplot` equivalent for. (#305 and #360 @zeileis and @grantmcdermott) + a native `tinyplot` equivalent for. (#305 and #360 @zeileis and @grantmcdermott) - `"violin"` / `type_violin()` for violin plots. (#354 @grantmcdermott) #### Other new features @@ -712,28 +754,30 @@ Theme fixes: - `xaxb`/`yaxb` control the manual break points of the axis tick marks. (#400 @grantmcdermott) - `xaxl`/`yaxl` apply a formatting function to change the appearance of the axis tick labels. (#363, #391 @grantmcdermott) - - These `x/yaxb` and `x/yaxl` arguments can be used in complementary fashion; - see the new (lower-level) `tinylabel` function documentation. For example: + These `x/yaxb` and `x/yaxl` arguments can be used in complementary fashion; + see the new (lower-level) `tinylabel` function documentation. For example: ```r tinyplot((0:10)/10, yaxb = c(.17, .33, .5, .67, .83), yaxl = "%") ``` - The `x/ymin` and `x/ymax` arguments can now be specified directly via the `tinyplot.formula()` method thanks to better NSE processing. For example, instead of having to write + ```r with(dat, tinyplot(x = x, y = y, by = by ymin = lwr, ymax = upr)) ``` + users can now do + ```r tinyplot(y ~ x | by, dat, ymin = lwr, ymax = upr) ``` - + Underneath the hood, this works by processing these NSE arguments as part of formula `model.frame()` and reference against the provided dataset. We plan to extend the same logic to other top-level formula arguments such as `weights` and `subset` in a future version of tinyplot. - + ### Bug fixes: - The `tinyplot(..., cex = )` argument should be respected when using @@ -841,32 +885,31 @@ _(Primary PR and author: #222 @vincentarelbundock)_ #### Support for additional plot types - - Visualizations: - - - `type_spineplot()` (shortcut: `"spineplot"`) spine plots and +- Visualizations: + + - `type_spineplot()` (shortcut: `"spineplot"`) spine plots and spinograms. These are modified versions of a histogram or mosaic plot, and are particularly useful for visualizing factor variables. (#233 @zeileis with contributions from @grantmcdermott) - - `type_qq()` (shortcut: "qq") for quantile-quantile plots. (#251 + - `type_qq()` (shortcut: "qq") for quantile-quantile plots. (#251 @vincentarelbundock) - - `type_ridge()` (shortcut: `"ridge"`) for ridge plots aka Joy plots. + - `type_ridge()` (shortcut: `"ridge"`) for ridge plots aka Joy plots. (#252 @vincentarelbundock, @zeileis, and @grantmcdermott) - - `type_rug()` (shortcut: `"rug"`) adds a rug to an existing plot. (#276 + - `type_rug()` (shortcut: `"rug"`) adds a rug to an existing plot. (#276 @grantmcdermott) - - `type_text()` (shortcut: `"text"`) adds text annotations. (@vincentarelbundock) - - - Models: - - `type_glm()` (shortcut: `"glm"`) (@vincentarelbundock) - - `type_lm()` (shortcut: `"lm"`) (@vincentarelbundock) - - `type_loess()` (shortcut: `"loess"`) (@vincentarelbundock) - - `type_spline()` (shortcut: `"spline"`) (#241 @grantmcdermott) - - - Functions: - - `type_abline()`: line(s) with intercept and slope (#249 @vincentarelbundock) - - `type_hline()`: horizontal line(s) (#249 @vincentarelbundock) - - `type_vline()`: vertical line(s) (#249 @vincentarelbundock) - - `type_function()`: arbitrary function. (#250 @vincentarelbundock) - - `type_summary()`: summarize values of `y` along unique values of `x` (#274 + - `type_text()` (shortcut: `"text"`) adds text annotations. (@vincentarelbundock) + +- Models: + - `type_glm()` (shortcut: `"glm"`) (@vincentarelbundock) + - `type_lm()` (shortcut: `"lm"`) (@vincentarelbundock) + - `type_loess()` (shortcut: `"loess"`) (@vincentarelbundock) + - `type_spline()` (shortcut: `"spline"`) (#241 @grantmcdermott) +- Functions: + - `type_abline()`: line(s) with intercept and slope (#249 @vincentarelbundock) + - `type_hline()`: horizontal line(s) (#249 @vincentarelbundock) + - `type_vline()`: vertical line(s) (#249 @vincentarelbundock) + - `type_function()`: arbitrary function. (#250 @vincentarelbundock) + - `type_summary()`: summarize values of `y` along unique values of `x` (#274 @grantmcdermott) #### Themes @@ -891,32 +934,30 @@ _(Primary PR and authors: #258 @vincentarelbundock and @grantmcdermott)_ #### Other new features - New `tinyplot()` arguments: - - `flip ` allows for easily flipping (swapping) the orientation - of the x and y axes. This should work regardless of plot type, e.g. - `tinyplot(~Sepal.Length | Species, data = iris, type = "density", flip = TRUE)`. - (#216 @grantmcdermott) + - `flip ` allows for easily flipping (swapping) the orientation + of the x and y axes. This should work regardless of plot type, e.g. + `tinyplot(~Sepal.Length | Species, data = iris, type = "density", flip = TRUE)`. + (#216 @grantmcdermott) - `draw = ` allows users to pass arbitrary drawing functions that - are evaluated as-is, before the main plotting elements. A core use case is - drawing common annotations across every facet of a faceted plot, e.g. text or - threshold lines. (#245 @grantmcdermott) + are evaluated as-is, before the main plotting elements. A core use case is + drawing common annotations across every facet of a faceted plot, e.g. text or + threshold lines. (#245 @grantmcdermott) - `facet.args` gains a `free = ` sub-argument for independently - scaling the axes limits of individual facets. (#253 @grantmcdermott) - + scaling the axes limits of individual facets. (#253 @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 `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) + easy layering of plots without having to specify repeat arguments. (#246 + @vincentarelbundock) ### Breaking changes - There are a few breaking changes to grouped density plots. - The joint smoothing bandwidth is now computed using an observation-weighted - mean (as opposed to a simple mean). Users can customize this joint bandwidth + mean (as opposed to a simple mean). Users can customize this joint bandwidth by invoking the new `type_density(joint.bw =