From 68e7a984f190c17e5ed28e925c4c910521ac266e Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Tue, 25 Nov 2025 12:43:33 -0800 Subject: [PATCH 1/6] fixed.pos -> fixed.dodge --- R/dodge.R | 39 +++++++++++++++++++++++---------------- R/type_errorbar.R | 8 ++++---- R/type_pointrange.R | 8 ++++---- R/type_ribbon.R | 8 ++++---- man/dodge_positions.Rd | 36 ++++++++++++++++++++++-------------- man/type_errorbar.Rd | 38 +++++++++++++++++++++++--------------- man/type_ribbon.Rd | 32 ++++++++++++++++++++------------ 7 files changed, 100 insertions(+), 69 deletions(-) diff --git a/R/dodge.R b/R/dodge.R index 51ab2735..3a4242b8 100644 --- a/R/dodge.R +++ b/R/dodge.R @@ -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), @@ -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. #' @@ -37,7 +44,7 @@ dodge_positions = function( datapoints, dodge, - fixed.pos = TRUE, + fixed.dodge = TRUE, cols = NULL, settings = NULL ) { @@ -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) @@ -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) diff --git a/R/type_errorbar.R b/R/type_errorbar.R index d325acb9..71c5bef0 100644 --- a/R/type_errorbar.R +++ b/R/type_errorbar.R @@ -44,20 +44,20 @@ #' #' # 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`. +#' # position consistently across all terms, use `fixed.dodge = TRUE`. #' #' tinyplot(estimate ~ term | model, #' ymin = conf.low, ymax = conf.high, #' data = results, -#' type = type_pointrange(dodge = 0.2, fixed.pos = TRUE)) +#' type = type_pointrange(dodge = 0.2, fixed.dodge = TRUE)) #' #' tpar(op) #' #' @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" diff --git a/R/type_pointrange.R b/R/type_pointrange.R index 9cefe803..90290354 100644 --- a/R/type_pointrange.R +++ b/R/type_pointrange.R @@ -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" @@ -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")) @@ -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 diff --git a/R/type_ribbon.R b/R/type_ribbon.R index 8851e2af..2ea08028 100644 --- a/R/type_ribbon.R +++ b/R/type_ribbon.R @@ -78,10 +78,10 @@ #' ) #' #' @export -type_ribbon = function(alpha = NULL, dodge = 0, fixed.pos = FALSE) { +type_ribbon = function(alpha = NULL, dodge = 0, fixed.dodge = FALSE) { out = list( draw = draw_ribbon(), - data = data_ribbon(ribbon.alpha = alpha, dodge = dodge, fixed.pos = fixed.pos), + data = data_ribbon(ribbon.alpha = alpha, dodge = dodge, fixed.dodge = fixed.dodge), name = "ribbon" ) class(out) = "tinyplot_type" @@ -104,7 +104,7 @@ draw_ribbon = function() { } -data_ribbon = function(ribbon.alpha = NULL, dodge = 0, fixed.pos = FALSE) { +data_ribbon = function(ribbon.alpha = NULL, dodge = 0, fixed.dodge = FALSE) { ribbon.alpha = sanitize_ribbon_alpha(ribbon.alpha) fun = function(settings, ...) { env2env(settings, environment(), c("datapoints", "xlabs", "null_by", "null_facet")) @@ -124,7 +124,7 @@ data_ribbon = function(ribbon.alpha = NULL, dodge = 0, fixed.pos = FALSE) { # dodge (auto-detects x, xmin, xmax columns) if (dodge != 0) { - datapoints = dodge_positions(datapoints, dodge, fixed.pos) + datapoints = dodge_positions(datapoints, dodge, fixed.dodge) } if (null_by && null_facet) { diff --git a/man/dodge_positions.Rd b/man/dodge_positions.Rd index ff3df05f..1d17e340 100644 --- a/man/dodge_positions.Rd +++ b/man/dodge_positions.Rd @@ -7,7 +7,7 @@ dodge_positions( datapoints, dodge, - fixed.pos = TRUE, + fixed.dodge = TRUE, cols = NULL, settings = NULL ) @@ -16,19 +16,27 @@ dodge_positions( \item{datapoints}{Data frame containing plot data with at least \code{x} and \code{by} columns.} -\item{dodge}{Numeric value in the range \verb{[0,1)}, or logical. If numeric, -values are scaled relative to x-axis break spacing (e.g., \code{dodge = 0.1} -places outermost groups one-tenth of the way to adjacent breaks; -\code{dodge = 0.5} places them midway between breaks; etc.). Values < 0.5 are -recommended. If \code{TRUE}, dodge width is calculated automatically based on +\item{dodge}{Adjustment parameter for dodging overlapping points or ranges in +grouped plots along the x-axis (or y-axis for flipped plots). Either: +\itemize{ +\item numeric value in the range \verb{[0,1)}. Note that values are scaled +relative to the spacing of x-axis breaks, e.g. \code{dodge = 0.1} places the +outermost groups one-tenth of the way to adjacent breaks, \code{dodge = 0.5} +places them midway between breaks, etc. Values < 0.5 are recommended. +\item logical. If \code{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 -\code{FALSE} or 0, no dodging is performed. Default is 0.} +\code{FALSE} or 0, no dodging is performed. +} + +Default value is 0 (no dodging). While we do not check, it is \emph{strongly} +recommended that dodging only be used in cases where the x-axis comprises a +limited number of discrete breaks.} -\item{fixed.pos}{Logical indicating whether dodged groups should retain a -fixed relative position based on their group value. Relevant for \code{x} -categories that only have a subset of the total number of groups. Defaults -to \code{FALSE}, in which case dodging is based on the number of unique groups -present in that \code{x} category alone. See Examples.} +\item{fixed.dodge}{Logical. If \code{FALSE} (default), dodge positions are +calculated independently for each \code{x} value, based only on the groups +present at that position. If \code{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 \code{x} value).} \item{cols}{Character vector of column names to dodge. If \code{NULL} (default), automatically detects and dodges \code{x}, \code{xmin}, and \code{xmax} if they exist.} @@ -44,8 +52,8 @@ Adjusts x-coordinates (and optionally xmin/xmax) to dodge overlapping points or ranges in grouped plots. } \details{ -When \code{fixed.pos = TRUE}, all groups are dodged by the same amount across all -x values, which is useful when x is categorical. When \code{fixed.pos = FALSE}, +When \code{fixed.dodge = TRUE}, all groups are dodged by the same amount across all +x values, which is useful when x is categorical. When \code{fixed.dodge = FALSE}, dodging is calculated independently for each x value, which is useful when the number of groups varies across x values. diff --git a/man/type_errorbar.Rd b/man/type_errorbar.Rd index fe3d8932..b920d79a 100644 --- a/man/type_errorbar.Rd +++ b/man/type_errorbar.Rd @@ -5,26 +5,34 @@ \alias{type_pointrange} \title{Error bar and pointrange plot types} \usage{ -type_errorbar(length = 0.05, dodge = 0, fixed.pos = FALSE) +type_errorbar(length = 0.05, dodge = 0, fixed.dodge = FALSE) -type_pointrange(dodge = 0, fixed.pos = FALSE) +type_pointrange(dodge = 0, fixed.dodge = FALSE) } \arguments{ \item{length}{length of the edges of the arrow head (in inches).} -\item{dodge}{Numeric value in the range \verb{[0,1)}, or logical. If numeric, -values are scaled relative to x-axis break spacing (e.g., \code{dodge = 0.1} -places outermost groups one-tenth of the way to adjacent breaks; -\code{dodge = 0.5} places them midway between breaks; etc.). Values < 0.5 are -recommended. If \code{TRUE}, dodge width is calculated automatically based on +\item{dodge}{Adjustment parameter for dodging overlapping points or ranges in +grouped plots along the x-axis (or y-axis for flipped plots). Either: +\itemize{ +\item numeric value in the range \verb{[0,1)}. Note that values are scaled +relative to the spacing of x-axis breaks, e.g. \code{dodge = 0.1} places the +outermost groups one-tenth of the way to adjacent breaks, \code{dodge = 0.5} +places them midway between breaks, etc. Values < 0.5 are recommended. +\item logical. If \code{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 -\code{FALSE} or 0, no dodging is performed. Default is 0.} +\code{FALSE} or 0, no dodging is performed. +} + +Default value is 0 (no dodging). While we do not check, it is \emph{strongly} +recommended that dodging only be used in cases where the x-axis comprises a +limited number of discrete breaks.} -\item{fixed.pos}{Logical indicating whether dodged groups should retain a -fixed relative position based on their group value. Relevant for \code{x} -categories that only have a subset of the total number of groups. Defaults -to \code{FALSE}, in which case dodging is based on the number of unique groups -present in that \code{x} category alone. See Examples.} +\item{fixed.dodge}{Logical. If \code{FALSE} (default), dodge positions are +calculated independently for each \code{x} value, based only on the groups +present at that position. If \code{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 \code{x} value).} } \description{ Type function(s) for producing error bar and pointrange plots. @@ -69,12 +77,12 @@ tinyplot(estimate ~ term | model, # 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`. +# position consistently across all terms, use `fixed.dodge = TRUE`. tinyplot(estimate ~ term | model, ymin = conf.low, ymax = conf.high, data = results, - type = type_pointrange(dodge = 0.2, fixed.pos = TRUE)) + type = type_pointrange(dodge = 0.2, fixed.dodge = TRUE)) tpar(op) diff --git a/man/type_ribbon.Rd b/man/type_ribbon.Rd index b2918dbb..086c191a 100644 --- a/man/type_ribbon.Rd +++ b/man/type_ribbon.Rd @@ -7,7 +7,7 @@ \usage{ type_area(alpha = NULL) -type_ribbon(alpha = NULL, dodge = 0, fixed.pos = FALSE) +type_ribbon(alpha = NULL, dodge = 0, fixed.dodge = FALSE) } \arguments{ \item{alpha}{numeric value between 0 and 1 specifying the opacity of ribbon shading @@ -15,19 +15,27 @@ If no \code{alpha} value is provided, then will default to \code{tpar("ribbon.al (i.e., probably \code{0.2} unless this has been overridden by the user in their global settings.)} -\item{dodge}{Numeric value in the range \verb{[0,1)}, or logical. If numeric, -values are scaled relative to x-axis break spacing (e.g., \code{dodge = 0.1} -places outermost groups one-tenth of the way to adjacent breaks; -\code{dodge = 0.5} places them midway between breaks; etc.). Values < 0.5 are -recommended. If \code{TRUE}, dodge width is calculated automatically based on +\item{dodge}{Adjustment parameter for dodging overlapping points or ranges in +grouped plots along the x-axis (or y-axis for flipped plots). Either: +\itemize{ +\item numeric value in the range \verb{[0,1)}. Note that values are scaled +relative to the spacing of x-axis breaks, e.g. \code{dodge = 0.1} places the +outermost groups one-tenth of the way to adjacent breaks, \code{dodge = 0.5} +places them midway between breaks, etc. Values < 0.5 are recommended. +\item logical. If \code{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 -\code{FALSE} or 0, no dodging is performed. Default is 0.} +\code{FALSE} or 0, no dodging is performed. +} + +Default value is 0 (no dodging). While we do not check, it is \emph{strongly} +recommended that dodging only be used in cases where the x-axis comprises a +limited number of discrete breaks.} -\item{fixed.pos}{Logical indicating whether dodged groups should retain a -fixed relative position based on their group value. Relevant for \code{x} -categories that only have a subset of the total number of groups. Defaults -to \code{FALSE}, in which case dodging is based on the number of unique groups -present in that \code{x} category alone. See Examples.} +\item{fixed.dodge}{Logical. If \code{FALSE} (default), dodge positions are +calculated independently for each \code{x} value, based only on the groups +present at that position. If \code{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 \code{x} value).} } \description{ Type constructor functions for producing polygon ribbons, which From 4c005de1014978d84f8e13a9a8c0dab4fbca7d1b Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Tue, 25 Nov 2025 12:45:59 -0800 Subject: [PATCH 2/6] support dodge arg in points and lines types --- R/sanitize_type.R | 3 ++- R/type_lines.R | 37 +++++++++++++++++++++++++++++++++++-- R/type_points.R | 12 +++++++++--- man/type_lines.Rd | 24 +++++++++++++++++++++++- man/type_points.Rd | 24 +++++++++++++++++++++++- 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/R/sanitize_type.R b/R/sanitize_type.R index c9c7377b..6a13516c 100644 --- a/R/sanitize_type.R +++ b/R/sanitize_type.R @@ -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, @@ -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() diff --git a/R/type_lines.R b/R/type_lines.R index eeffcc1c..d88fc714 100644 --- a/R/type_lines.R +++ b/R/type_lines.R @@ -3,6 +3,7 @@ #' @description Type function for plotting lines. #' #' @inheritParams graphics::plot.default +#' @inheritParams dodge_positions #' #' @examples #' # "l" type convenience character string @@ -12,10 +13,10 @@ #' 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" @@ -23,6 +24,38 @@ type_lines = function(type = "l") { } +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( diff --git a/R/type_points.R b/R/type_points.R index 53e868e2..781d8741 100644 --- a/R/type_points.R +++ b/R/type_points.R @@ -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 @@ -31,9 +32,9 @@ #' 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" ) @@ -41,7 +42,7 @@ type_points = function(clim = c(0.5, 2.5)) { 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")) @@ -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)) { diff --git a/man/type_lines.Rd b/man/type_lines.Rd index 7fd54ea0..e80592e6 100644 --- a/man/type_lines.Rd +++ b/man/type_lines.Rd @@ -4,7 +4,7 @@ \alias{type_lines} \title{Lines plot type} \usage{ -type_lines(type = "l") +type_lines(type = "l", dodge = 0, fixed.dodge = FALSE) } \arguments{ \item{type}{1-character string giving the type of plot desired. The @@ -17,6 +17,28 @@ type_lines(type = "l") \code{"s"} and \code{"S"} for stair steps and \code{"h"} for histogram-like vertical lines. Finally, \code{"n"} does not produce any points or lines.} + +\item{dodge}{Adjustment parameter for dodging overlapping points or ranges in +grouped plots along the x-axis (or y-axis for flipped plots). Either: +\itemize{ +\item numeric value in the range \verb{[0,1)}. Note that values are scaled +relative to the spacing of x-axis breaks, e.g. \code{dodge = 0.1} places the +outermost groups one-tenth of the way to adjacent breaks, \code{dodge = 0.5} +places them midway between breaks, etc. Values < 0.5 are recommended. +\item logical. If \code{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 +\code{FALSE} or 0, no dodging is performed. +} + +Default value is 0 (no dodging). While we do not check, it is \emph{strongly} +recommended that dodging only be used in cases where the x-axis comprises a +limited number of discrete breaks.} + +\item{fixed.dodge}{Logical. If \code{FALSE} (default), dodge positions are +calculated independently for each \code{x} value, based only on the groups +present at that position. If \code{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 \code{x} value).} } \description{ Type function for plotting lines. diff --git a/man/type_points.Rd b/man/type_points.Rd index 5a93ecc0..4bd520ed 100644 --- a/man/type_points.Rd +++ b/man/type_points.Rd @@ -4,11 +4,33 @@ \alias{type_points} \title{Points plot type} \usage{ -type_points(clim = c(0.5, 2.5)) +type_points(clim = c(0.5, 2.5), dodge = 0, fixed.dodge = FALSE) } \arguments{ \item{clim}{Numeric giving the lower and upper limits of the character expansion (\code{cex}) normalization for bubble charts.} + +\item{dodge}{Adjustment parameter for dodging overlapping points or ranges in +grouped plots along the x-axis (or y-axis for flipped plots). Either: +\itemize{ +\item numeric value in the range \verb{[0,1)}. Note that values are scaled +relative to the spacing of x-axis breaks, e.g. \code{dodge = 0.1} places the +outermost groups one-tenth of the way to adjacent breaks, \code{dodge = 0.5} +places them midway between breaks, etc. Values < 0.5 are recommended. +\item logical. If \code{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 +\code{FALSE} or 0, no dodging is performed. +} + +Default value is 0 (no dodging). While we do not check, it is \emph{strongly} +recommended that dodging only be used in cases where the x-axis comprises a +limited number of discrete breaks.} + +\item{fixed.dodge}{Logical. If \code{FALSE} (default), dodge positions are +calculated independently for each \code{x} value, based only on the groups +present at that position. If \code{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 \code{x} value).} } \description{ Type function for plotting points, i.e. a scatter plot. From 6a7114f6f3cc22c36432f1559445f97bf33df207 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Tue, 25 Nov 2025 13:40:23 -0800 Subject: [PATCH 3/6] example updates --- R/type_errorbar.R | 71 +++++++++++++++++++++++++++++++------------- man/type_errorbar.Rd | 71 +++++++++++++++++++++++++++++++------------- 2 files changed, 100 insertions(+), 42 deletions(-) diff --git a/R/type_errorbar.R b/R/type_errorbar.R index 71c5bef0..32c8531f 100644 --- a/R/type_errorbar.R +++ b/R/type_errorbar.R @@ -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") @@ -19,39 +22,65 @@ #' 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.dodge = 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.dodge = 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.dodge = FALSE) { diff --git a/man/type_errorbar.Rd b/man/type_errorbar.Rd index b920d79a..c6384d7e 100644 --- a/man/type_errorbar.Rd +++ b/man/type_errorbar.Rd @@ -38,12 +38,15 @@ groups are missing for a particular \code{x} value).} Type function(s) for producing error bar and pointrange plots. } \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") @@ -52,38 +55,64 @@ tinyplot(est ~ term, ymin = lwr, ymax = upr, data = coefs, type = "pointrange") 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 = 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`. 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, 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.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.dodge = TRUE)) + data = models, + type = "pointrange", + dodge = 0.1, fixed.dodge = TRUE) +tinyplot_add(type = "l", lty = 2) -tpar(op) +tinytheme() # reset theme } From 3885745446b37c47be96cd3ae39d2db3a54bf039 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Tue, 25 Nov 2025 13:47:38 -0800 Subject: [PATCH 4/6] news --- NEWS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index fb22ec71..8e673e8d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -26,11 +26,12 @@ where the formatting is also better._ - `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) + - `dodge` argument now 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) ### Bug fixes From dfca7c641a921a15bafd6f4aff2b4c39334b370f Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Tue, 25 Nov 2025 13:52:28 -0800 Subject: [PATCH 5/6] test --- .../dodge_errorbar_add_lines.svg | 128 ++++++++++++++++++ inst/tinytest/test-dodge.R | 11 ++ 2 files changed, 139 insertions(+) create mode 100644 inst/tinytest/_tinysnapshot/dodge_errorbar_add_lines.svg diff --git a/inst/tinytest/_tinysnapshot/dodge_errorbar_add_lines.svg b/inst/tinytest/_tinysnapshot/dodge_errorbar_add_lines.svg new file mode 100644 index 00000000..a096a87a --- /dev/null +++ b/inst/tinytest/_tinysnapshot/dodge_errorbar_add_lines.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + +model +Model A +Model B +Model C + + + + + + + +term +estimate + + + + + + + +(Intercept) +wt +cyl +hp + + + + + + +0 +10 +20 +30 +40 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-dodge.R b/inst/tinytest/test-dodge.R index 433f97a3..568937c8 100644 --- a/inst/tinytest/test-dodge.R +++ b/inst/tinytest/test-dodge.R @@ -83,6 +83,17 @@ fun = function() { expect_snapshot_plot(fun, label = "dodge_errorbar_add_ribbon") +fun = function() { + tinyplot(estimate ~ term | model, + ymin = conf.low, ymax = conf.high, + data = results, + type = "errorbar", + dodge = 0.1, + theme = "basic") + tinyplot_add(type = "l", lty = 2) +} +expect_snapshot_plot(fun, label = "dodge_errorbar_add_lines") + # ## Warning and errors From c717b49fb73b4938e6b666c59357a146a073e5b6 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Tue, 25 Nov 2025 14:07:26 -0800 Subject: [PATCH 6/6] news --- NEWS.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8e673e8d..c68e5357 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 @@ -20,18 +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: - - `dodge` argument now supported by `type_lines()`, `type_points()`, and +- 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) - 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