Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,29 @@ where the formatting is also better._

### Breaking change

- Internal settings and parameters are now stored in an environment called
`settings`, which can be accessed and modified by type-specific functions.
This may require changes to users' custom type functions that previously
accessed settings as passed arguments. This change was necessary to improve
the modularity and maintainability of the codebase, and also to add downstream
flexibility. (#473 @vincentarelbundock and @grantmcdermott)
- "Breaking" change for internal development and custom types only:
The plot settings and parameters from individual `tinyplot` calls are now
stored in a dedicated (temporary) internal environment called `settings`,
which can be accessed and modified by type-specific functions. This change
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
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)

### New features

- `type_text()` gains a `family` argument for controlling the font family,
separate to the main plot text elements. (#494 @grantmcdermott)
- `type_ribbon()` gains a `dodge` argument, supporting similar functionality to
`type_errorbar()` and `type_pointrange()` for dodging overlapping groups.
(#522 @grantmcdermott)
- Expanded `dodge` argument capabilities and consistency for overlapping groups:
- Logical `dodge = TRUE` gives automatic width spacing based on the number
of groups. (#525 @grantmcdermott)
- We now enforce that numeric `dodge` values must be in the range `[0,1)`.
(#526 @grantmcdermott)
- `dodge` argument now supported in `type_ribbon()` (#522 @grantmcdermott)

### Bug fixes

Expand All @@ -45,8 +54,8 @@ where the formatting is also better._
(e.g., `tinyplot.foo`) would fail. (#515 @grantmcdermott)
- Added layers, particularly from `tinyplot_add()`, should now respect the
x-axis order of the original plot layer. This should ensure that we don't end
up with misaligned layers. For example, when adding a ribbon on top of an
errorbar plot. (#517, #520, #523 @grantmcdermott)
up with misaligned layers. For example, when ribbon is added on top of an
errorbar plot. (#517, #520, #523, #526 @grantmcdermott)


### Documentation
Expand Down
17 changes: 12 additions & 5 deletions R/align_layer.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Ensure added layers respect the x-axis order of the original plot layer
# (e.g., when adding lines or ribbons on top of errorbars)
align_layer = function(settings) {

# Retrieve xlabs from current and original layers
xlabs_layer = settings[["xlabs"]]
xlabs_orig = get("xlabs", envir = get(".tinyplot_env", envir = parent.env(environment())))
Expand All @@ -22,15 +21,23 @@ align_layer = function(settings) {
if (setequal(names(xlabs_layer), names(xlabs_orig))) {
orig_order = xlabs_orig[names(xlabs_layer)[settings$datapoints[["x"]]]]
x_layer = settings$datapoints[["x"]]
settings$datapoints[["x"]] = orig_order
if (is.null(settings$dodge)) {
x_new = x_layer[orig_order]
} else {
names(x_layer) = names(xlabs_layer)[round(x_layer)]
x_new = x_layer + (xlabs_orig[names(round(x_layer))] - round(x_layer))
}
settings$datapoints[["x"]] = x_new
# Adjust ancillary variables
for (v in c("rowid", "xmin", "xmax")) {
if (identical(settings$datapoints[[v]], x_layer)) {
settings$datapoints[[v]] = orig_order
for (v in c("xmin", "xmax")) {
if (identical(settings$datapoints[[v]], unname(x_layer))) {
settings$datapoints[[v]] = x_new
}
}
settings$datapoints = settings$datapoints[order(settings$datapoints[["x"]]), ]
settings$datapoints[["rowid"]] = seq_len(nrow(settings$datapoints))
}
}
}
}

48 changes: 42 additions & 6 deletions R/dodge.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
#'
#' @param datapoints Data frame containing plot data with at least `x` and `by`
#' columns.
#' @param dodge Numeric value specifying the dodge amount. If 0, no dodging is
#' performed.
#' @param fixed.pos Logical. If `TRUE`, dodge positions are fixed based on the
#' number of groups in `by`. If `FALSE`, dodge positions are calculated
#' separately for each unique x value.
#' @param dodge Numeric value in the range `[0,1)`, or logical. If numeric,
#' values are scaled relative to x-axis break spacing (e.g., `dodge = 0.1`
#' places outermost groups one-tenth of the way to adjacent breaks;
#' `dodge = 0.5` places them midway between breaks; etc.). Values < 0.5 are
#' recommended. If `TRUE`, dodge width is calculated automatically based on
#' the number of groups (0.1 per group for 2-4 groups, 0.45 for 5+ groups). If
#' `FALSE` or 0, no dodging is performed. Default is 0.
#' @param fixed.pos Logical indicating whether dodged groups should retain a
#' fixed relative position based on their group value. Relevant for `x`
#' categories that only have a subset of the total number of groups. Defaults
#' to `FALSE`, in which case dodging is based on the number of unique groups
#' present in that `x` category alone. See Examples.
#' @param cols Character vector of column names to dodge. If `NULL` (default),
#' automatically detects and dodges `x`, `xmin`, and `xmax` if they exist.
#' @param settings Environment containing plot settings. If `NULL` (default),
#' retrieved from the calling environment.
#'
#' @return Modified `datapoints` data frame with dodged positions.
#'
Expand All @@ -29,11 +38,37 @@ dodge_positions = function(
datapoints,
dodge,
fixed.pos = TRUE,
cols = NULL
cols = NULL,
settings = NULL
) {
if (is.null(settings)) {
settings = get("settings", envir = parent.frame())
}

if (is.logical(dodge)) {
if (isTRUE(dodge)) {
n = nlevels(datapoints$by)
dodge = if (n == 1) 0 else if (n <= 5) (n - 1) * 0.1 else 0.45
} else {
dodge = 0
}
}

assert_numeric(dodge, len = 1, lower = 0, upper = 1)
if (dodge >= 1) {
stop("`dodge` must be in the range [0,1).", call. = FALSE)
}
assert_logical(fixed.pos)

if (dodge == 0) {
return(datapoints)
} else if (dodge > 0.5) {
warning(
"Argument `dodge = ", dodge, "` exceeds 0.5. ",
"Large dodge values may position outer groups closer to neighboring axis breaks."
)
}
settings$dodge = dodge

# Auto-detect columns to dodge if not specified
if (is.null(cols)) {
Expand Down Expand Up @@ -64,3 +99,4 @@ dodge_positions = function(

datapoints
}

1 change: 1 addition & 0 deletions R/tinyplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ tinyplot.default = function(

# misc
flip = flip,
dodge = NULL,
by = by,
dots = dots,
type_info = list() # pass type-specific info from type_data to type_draw
Expand Down
11 changes: 1 addition & 10 deletions R/type_errorbar.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@
#'
#' @description Type function(s) for producing error bar and pointrange plots.
#'
#' @param dodge Numeric value (>= 0) for dodging of overlapping `by` groups.
#' Dodging is scaled relative to the x-axis tick locations (i.e., unique
#' levels of `x`). For example, `dodge = 0.5` would place the outermost dodged
#' groups exactly midway the between axis ticks. Default value is 0 (no
#' dodging).
#' @param fixed.pos Logical indicating whether dodged groups should retain a
#' fixed relative position based on their group value. Relevant for `x`
#' categories that only have a subset of the total number of groups. Defaults
#' to `FALSE`, in which case dodging is based on the number of unique groups
#' present in that `x` category alone. See Examples.
#' @inheritParams dodge_positions
#' @inheritParams graphics::arrows
#' @examples
#' mod = lm(mpg ~ wt * factor(am), mtcars)
Expand Down
3 changes: 0 additions & 3 deletions R/type_pointrange.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#' @rdname type_errorbar
#' @export
type_pointrange = function(dodge = 0, fixed.pos = FALSE) {
assert_numeric(dodge, len = 1, lower = 0)
assert_logical(fixed.pos)

out = list(
draw = draw_pointrange(),
data = data_pointrange(dodge = dodge, fixed.pos = fixed.pos),
Expand Down
3 changes: 0 additions & 3 deletions R/type_ribbon.R
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@
#'
#' @export
type_ribbon = function(alpha = NULL, dodge = 0, fixed.pos = FALSE) {
assert_numeric(dodge, len = 1, lower = 0)
assert_logical(fixed.pos)

out = list(
draw = draw_ribbon(),
data = data_ribbon(ribbon.alpha = alpha, dodge = dodge, fixed.pos = fixed.pos),
Expand Down
131 changes: 131 additions & 0 deletions inst/tinytest/_tinysnapshot/dodge_errorbar_add_ribbon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading