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
17 changes: 12 additions & 5 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ where the formatting is also better._

## Dev version

### Breaking change
### Breaking changes

- "Breaking" change for internal development and custom types only:
The plot settings and parameters from individual `tinyplot` calls are now
Expand All @@ -20,17 +20,24 @@ where the formatting is also better._
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)

### New features

- `type_text()` gains a `family` argument for controlling the font family,
separate to the main plot text elements. (#494 @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)
- Expanded `dodge` argument capabilities and consistency for dealing with
overlapping groups:
- `dodge` argument now also supported by `type_lines()`, `type_points()`, and
`type_ribbon()`. (#522, #528 @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)
- Alongside numeric values, we now support a logical `dodge = TRUE` argument,
which gives automatic width spacing based on the number of groups. (#525 @grantmcdermott)
- Renamed ancillary argument `fixed.pos` -> `fixed.dodge`, per the breaking
change above. (#528 @grantmcdermott)

### Bug fixes

Expand Down
39 changes: 23 additions & 16 deletions R/dodge.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@
#'
#' @param datapoints Data frame containing plot data with at least `x` and `by`
#' columns.
#' @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
#' @param dodge Adjustment parameter for dodging overlapping points or ranges in
#' grouped plots along the x-axis (or y-axis for flipped plots). Either:
#'
#' - numeric value in the range `[0,1)`. Note that values are scaled
#' relative to the spacing of x-axis breaks, e.g. `dodge = 0.1` places the
#' outermost groups one-tenth of the way to adjacent breaks, `dodge = 0.5`
#' places them midway between breaks, etc. Values < 0.5 are recommended.
#' - logical. If `TRUE`, the 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.
#' `FALSE` or 0, no dodging is performed.
#'
#' Default value is 0 (no dodging). While we do not check, it is _strongly_
#' recommended that dodging only be used in cases where the x-axis comprises a
#' limited number of discrete breaks.
#' @param fixed.dodge Logical. If `FALSE` (default), dodge positions are
#' calculated independently for each `x` value, based only on the groups
#' present at that position. If `TRUE`, dodge positions are based on all
#' groups, ensuring "fixed" spacing across x-axis breaks (i.e., even if some
#' groups are missing for a particular `x` value).
#' @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),
Expand All @@ -25,8 +32,8 @@
#' @return Modified `datapoints` data frame with dodged positions.
#'
#' @details
#' When `fixed.pos = TRUE`, all groups are dodged by the same amount across all
#' x values, which is useful when x is categorical. When `fixed.pos = FALSE`,
#' When `fixed.dodge = TRUE`, all groups are dodged by the same amount across all
#' x values, which is useful when x is categorical. When `fixed.dodge = FALSE`,
#' dodging is calculated independently for each x value, which is useful when
#' the number of groups varies across x values.
#'
Expand All @@ -37,7 +44,7 @@
dodge_positions = function(
datapoints,
dodge,
fixed.pos = TRUE,
fixed.dodge = TRUE,
cols = NULL,
settings = NULL
) {
Expand All @@ -58,7 +65,7 @@ dodge_positions = function(
if (dodge >= 1) {
stop("`dodge` must be in the range [0,1).", call. = FALSE)
}
assert_logical(fixed.pos)
assert_logical(fixed.dodge)

if (dodge == 0) {
return(datapoints)
Expand All @@ -76,7 +83,7 @@ dodge_positions = function(
cols = cols[cols %in% names(datapoints)]
}

if (fixed.pos) {
if (fixed.dodge) {
n = nlevels(datapoints$by)
d = cumsum(rep(dodge, n))
d = d - mean(d)
Expand Down
3 changes: 2 additions & 1 deletion R/sanitize_type.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ sanitize_type = function(settings) {
"hline" = type_hline,
"j" = type_jitter,
"jitter" = type_jitter,
"l" = type_lines,
"lines" = type_lines,
"lm" = type_lm,
"loess" = type_loess,
Expand All @@ -95,7 +96,7 @@ sanitize_type = function(settings) {
type # default case
)
}

# browser()
if (is.function(type)) {
args = intersect(names(formals(type)), names(dots))
args = if (length(args) >= 1L) dots[args] else list()
Expand Down
75 changes: 52 additions & 23 deletions R/type_errorbar.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
#' @inheritParams dodge_positions
#' @inheritParams graphics::arrows
#' @examples
#' tinytheme("basic")
#'
#' #
#' ## Basic coefficient plot(s)
#'
#' mod = lm(mpg ~ wt * factor(am), mtcars)
#' coefs = data.frame(names(coef(mod)), coef(mod), confint(mod))
#' colnames(coefs) = c("term", "est", "lwr", "upr")
#'
#' op = tpar(pch = 19)
#'
#' # "errorbar" and "pointrange" type convenience strings
#' tinyplot(est ~ term, ymin = lwr, ymax = upr, data = coefs, type = "errorbar")
#' tinyplot(est ~ term, ymin = lwr, ymax = upr, data = coefs, type = "pointrange")
Expand All @@ -19,45 +22,71 @@
#' tinyplot(est ~ term, ymin = lwr, ymax = upr, data = coefs,
#' type = type_errorbar(length = 0.2))
#'
#' # display three models side-by-side with dodging
#' #
#' ## Flipped plots
#'
#' # For flipped errobar / pointrange plots, it is recommended to use a dynamic
#' # theme that applies horizontal axis tick labels
#' tinyplot(est ~ term, ymin = lwr, ymax = upr, data = coefs, type = "errorbar",
#' flip = TRUE, theme = "classic")
#' tinyplot_add(type = 'vline', lty = 2)
#'
#'
#' #
#' ## Dodging groups
#'
#' models = list(
#' "Model A" = lm(mpg ~ wt + cyl, data = mtcars),
#' "Model B" = lm(mpg ~ wt + hp + cyl, data = mtcars),
#' "Model C" = lm(mpg ~ wt, data = mtcars)
#' "Model A" = lm(mpg ~ wt, data = mtcars),
#' "Model B" = lm(mpg ~ wt + cyl, data = mtcars),
#' "Model C" = lm(mpg ~ wt + cyl + hp, data = mtcars)
#' )
#'
#' results = lapply(names(models), function(m) {
#' models = do.call(
#' rbind,
#' lapply(names(models), function(m) {
#' data.frame(
#' model = m,
#' term = names(coef(models[[m]])),
#' estimate = coef(models[[m]]),
#' setNames(data.frame(confint(models[[m]])), c("conf.low", "conf.high"))
#' model = m,
#' term = names(coef(models[[m]])),
#' estimate = coef(models[[m]]),
#' setNames(data.frame(confint(models[[m]])), c("conf.low", "conf.high"))
#' )
#' })
#' results = do.call(rbind, results)
#' })
#' )
#'
#' tinyplot(estimate ~ term | model,
#' ymin = conf.low, ymax = conf.high,
#' data = results,
#' type = type_pointrange(dodge = 0.2))
#' data = models,
#' type = type_pointrange(dodge = 0.1))
#'
#' # Aside 1: relative vs fixed dodge
#' # The default dodge position is based on the unique groups (here: models)
#' # available to each x value (here: coefficient term). To "fix" the dodge
#' # position across all x values, use `fixed.dodge = TRUE`.
#'
#' # Note that the default dodged position is based solely on the number of
#' # groups (here: models) available to each coefficient term. To fix the
#' # position consistently across all terms, use `fixed.pos = TRUE`.
#' tinyplot(estimate ~ term | model,
#' ymin = conf.low, ymax = conf.high,
#' data = models,
#' type = type_pointrange(dodge = 0.1, fixed.dodge = TRUE))
#'
#' # Aside 2: layering
#' # For layering on top of dodged plots, rather pass the dodging arguments
#' # through the top-level call if you'd like the dodging behaviour to be
#' # inherited automatically by the add layers.
#'
#' tinyplot(estimate ~ term | model,
#' ymin = conf.low, ymax = conf.high,
#' data = results,
#' type = type_pointrange(dodge = 0.2, fixed.pos = TRUE))
#' data = models,
#' type = "pointrange",
#' dodge = 0.1, fixed.dodge = TRUE)
#' tinyplot_add(type = "l", lty = 2)
#'
#' tpar(op)
#' tinytheme() # reset theme
#'
#' @export
type_errorbar = function(length = 0.05, dodge = 0, fixed.pos = FALSE) {
type_errorbar = function(length = 0.05, dodge = 0, fixed.dodge = FALSE) {
out = list(
draw = draw_errorbar(length = length),
data = data_pointrange(dodge = dodge, fixed.pos = fixed.pos),
data = data_pointrange(dodge = dodge, fixed.dodge = fixed.dodge),
name = "p"
)
class(out) = "tinyplot_type"
Expand Down
37 changes: 35 additions & 2 deletions R/type_lines.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#' @description Type function for plotting lines.
#'
#' @inheritParams graphics::plot.default
#' @inheritParams dodge_positions
#'
#' @examples
#' # "l" type convenience character string
Expand All @@ -12,17 +13,49 @@
#' tinyplot(circumference ~ age | Tree, data = Orange, type = type_lines(type = "s"))
#'
#' @export
type_lines = function(type = "l") {
type_lines = function(type = "l", dodge = 0, fixed.dodge = FALSE) {
out = list(
draw = draw_lines(type = type),
data = NULL,
data = data_lines(dodge = dodge, fixed.dodge = fixed.dodge),
name = type
)
class(out) = "tinyplot_type"
return(out)
}


data_lines = function(dodge = 0, fixed.dodge = FALSE) {
if (is.null(dodge) || dodge == 0) return(NULL)
fun = function(settings, ...) {
env2env(settings, environment(), c("datapoints", "xlabs"))

if (is.character(datapoints$x)) {
datapoints$x = as.factor(datapoints$x)
}
if (is.factor(datapoints$x)) {
xlvls = unique(datapoints$x)
datapoints$x = factor(datapoints$x, levels = xlvls)
xlabs = seq_along(xlvls)
names(xlabs) = xlvls
datapoints$x = as.integer(datapoints$x)
}

# dodge
if (dodge != 0) {
datapoints = dodge_positions(datapoints, dodge, fixed.dodge)
}

x = datapoints$x
env2env(environment(), settings, c(
"x",
"xlabs",
"datapoints"
))
}
fun
}


draw_lines = function(type = "l") {
fun = function(ix, iy, icol, ipch, ibg, ilty, ilwd, icex = 1, ...) {
lines(
Expand Down
8 changes: 4 additions & 4 deletions R/type_pointrange.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#' @rdname type_errorbar
#' @export
type_pointrange = function(dodge = 0, fixed.pos = FALSE) {
type_pointrange = function(dodge = 0, fixed.dodge = FALSE) {
out = list(
draw = draw_pointrange(),
data = data_pointrange(dodge = dodge, fixed.pos = fixed.pos),
data = data_pointrange(dodge = dodge, fixed.dodge = fixed.dodge),
name = "p"
)
class(out) = "tinyplot_type"
Expand Down Expand Up @@ -47,7 +47,7 @@ draw_pointrange = function() {
}


data_pointrange = function(dodge, fixed.pos) {
data_pointrange = function(dodge, fixed.dodge) {
fun = function(settings, ...) {
env2env(settings, environment(), c("datapoints", "xlabs"))

Expand All @@ -67,7 +67,7 @@ data_pointrange = function(dodge, fixed.pos) {

# dodge
if (dodge != 0) {
datapoints = dodge_positions(datapoints, dodge, fixed.pos)
datapoints = dodge_positions(datapoints, dodge, fixed.dodge)
}

x = datapoints$x
Expand Down
12 changes: 9 additions & 3 deletions R/type_points.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#' @description Type function for plotting points, i.e. a scatter plot.
#' @param clim Numeric giving the lower and upper limits of the character
#' expansion (`cex`) normalization for bubble charts.
#' @inheritParams dodge_positions
#'
#' @examples
#' # "p" type convenience character string
Expand Down Expand Up @@ -31,17 +32,17 @@
#' pch = 21, fill = 0.3)
#'
#' @export
type_points = function(clim = c(0.5, 2.5)) {
type_points = function(clim = c(0.5, 2.5), dodge = 0, fixed.dodge = FALSE) {
out = list(
data = data_points(clim = clim),
data = data_points(clim = clim, dodge = dodge, fixed.dodge = fixed.dodge),
draw = draw_points(),
name = "p"
)
class(out) = "tinyplot_type"
return(out)
}

data_points = function(clim = c(0.5, 2.5)) {
data_points = function(clim = c(0.5, 2.5), dodge = 0, fixed.dodge = FALSE) {
fun = function(settings, cex = NULL, ...) {
env2env(settings, environment(), c("datapoints", "cex", "legend_args"))

Expand All @@ -63,6 +64,11 @@ data_points = function(clim = c(0.5, 2.5)) {
ylabs = NULL
}

# dodge
if (dodge != 0) {
datapoints = dodge_positions(datapoints, dodge, fixed.dodge)
}

bubble = FALSE
bubble_cex = 1
if (!is.null(cex) && length(cex) == nrow(datapoints)) {
Expand Down
Loading