From f65dbf45cb83148bb2ce9d1a026f0d10d8076818 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 14:40:01 -0800 Subject: [PATCH 01/21] use settings$legend_args --- R/tinyplot.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/R/tinyplot.R b/R/tinyplot.R index e7aa6bd1..0947e4e5 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -957,18 +957,18 @@ tinyplot.default = function( legend = NULL } if (!is.null(legend) && is.character(legend) && legend == "none") { - legend_args[["x"]] = "none" + settings$legend_args[["x"]] = "none" dual_legend = FALSE } if (null_by) { if (bubble && !dual_legend) { - legend_args[["title"]] = cex_dep + settings$legend_args[["title"]] = cex_dep lgnd_labs = names(bubble_cex) lgnd_cex = bubble_cex * cex_fct_adj } else if (is.null(legend)) { legend = "none" - legend_args[["x"]] = "none" + settings$legend_args[["x"]] = "none" } } @@ -984,8 +984,8 @@ tinyplot.default = function( has_sub = !is.null(sub) if (isTRUE(was_area_type) || isTRUE(type %in% c("area", "rect", "hist", "histogram"))) { - legend_args[["pt.lwd"]] = par("lwd") - legend_args[["lty"]] = 0 + settings$legend_args[["pt.lwd"]] = par("lwd") + settings$legend_args[["lty"]] = 0 } if (!dual_legend) { @@ -993,7 +993,7 @@ tinyplot.default = function( if (is.null(lgnd_cex)) lgnd_cex = cex * cex_fct_adj draw_legend( legend = legend, - legend_args = legend_args, + legend_args = settings$legend_args, by_dep = by_dep, lgnd_labs = lgnd_labs, type = type, @@ -1010,7 +1010,7 @@ tinyplot.default = function( ## dual legend case... # sanitize_legend: processes legend arguments and returns standardized legend_args list - legend_args = sanitize_legend(legend, legend_args) + legend_args = sanitize_legend(legend, settings$legend_args) # legend 1: by (grouping) key lgby = list( @@ -1061,7 +1061,7 @@ tinyplot.default = function( } has_legend = TRUE - } else if (legend_args[["x"]] == "none" && !add) { + } else if (settings$legend_args[["x"]] == "none" && !add) { omar = par("mar") ooma = par("oma") topmar_epsilon = 0.1 From bffeca2023a45c264432344ecb9126a682bad6c8 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 15:03:27 -0800 Subject: [PATCH 02/21] barplot guinea pig --- R/draw_legend_utils.R | 6 +----- R/type_barplot.R | 7 +++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 46749501..afb9429b 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,11 +63,7 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "pointrange", "errorbar", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - # turn off inner line for "barplot" type - if (identical(type, "barplot")) { - legend_args[["lty"]] = 0 - } - if (isTRUE(type %in% c("rect", "ribbon", "polygon", "polypath", "boxplot", "hist", "histogram", "spineplot", "ridge", "barplot", "violin")) || gradient) { + if (isTRUE(type %in% c("rect", "ribbon", "polygon", "polypath", "boxplot", "hist", "histogram", "spineplot", "ridge", "violin")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/type_barplot.R b/R/type_barplot.R index 2d3dd4d4..31ccfc49 100644 --- a/R/type_barplot.R +++ b/R/type_barplot.R @@ -199,6 +199,13 @@ data_barplot = function(width = 5/6, beside = FALSE, center = FALSE, FUN = NULL, xaxs = "r" xaxt = if (xaxt == "s") "l" else xaxt yaxs = "i" + + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "datapoints", "xlab", From e58f6dead9b50eaff8095a8255b45381240cc188 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 15:15:37 -0800 Subject: [PATCH 03/21] polys --- R/draw_legend_utils.R | 2 +- R/type_polygon.R | 13 +++++++++++- R/type_polypath.R | 46 +++++++++++++++++++++++++++---------------- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index afb9429b..7b4d2a44 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,7 +63,7 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "pointrange", "errorbar", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("rect", "ribbon", "polygon", "polypath", "boxplot", "hist", "histogram", "spineplot", "ridge", "violin")) || gradient) { + if (isTRUE(type %in% c("rect", "ribbon", "boxplot", "hist", "histogram", "spineplot", "ridge", "violin")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/type_polygon.R b/R/type_polygon.R index fb0f9bde..a9d3ef7b 100644 --- a/R/type_polygon.R +++ b/R/type_polygon.R @@ -16,7 +16,7 @@ type_polygon = function(density = NULL, angle = 45) { out = list( draw = draw_polygon(density = density, angle = angle), - data = NULL, + data = data_polygon(), name = "polygon" ) class(out) = "tinyplot_type" @@ -24,6 +24,17 @@ type_polygon = function(density = NULL, angle = 45) { } +data_polygon = function() { + fun = function(settings, ...) { + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + } + return(fun) +} + + draw_polygon = function(density = density, angle = 45) { fun = function(ix, iy, icol, ibg, ilty = par("lty"), ilwd = par("lwd"), ...) { polygon( diff --git a/R/type_polypath.R b/R/type_polypath.R index 29742787..2eff6c6a 100644 --- a/R/type_polypath.R +++ b/R/type_polypath.R @@ -21,27 +21,39 @@ #' ) #' @export type_polypath = function(rule = "winding") { - draw_polypath = function() { - fun = function(ix, iy, icol, ibg, ilty, ilwd, dots, ...) { - polypath( - x = ix, - y = iy, - border = icol, - col = ibg, - lty = ilty, - lwd = ilwd, - rule = rule - ) - } - return(fun) - } - out = list( - draw = draw_polypath(), - data = NULL, + draw = draw_polypath(rule = rule), + data = data_polypath(), name = "polypath" ) class(out) = "tinyplot_type" return(out) } + +data_polypath = function() { + fun = function(settings, ...) { + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + } + return(fun) +} + + +draw_polypath = function(rule = "winding") { + fun = function(ix, iy, icol, ibg, ilty, ilwd, dots, ...) { + polypath( + x = ix, + y = iy, + border = icol, + col = ibg, + lty = ilty, + lwd = ilwd, + rule = rule + ) + } + return(fun) +} + From 93b54bc009852dd04978897283cfe0be80a697b6 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 15:23:02 -0800 Subject: [PATCH 04/21] pointrange and error bar --- R/draw_legend_utils.R | 2 +- R/type_pointrange.R | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 7b4d2a44..647289a7 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -60,7 +60,7 @@ compute_legend_args = function( if (!isTRUE(type %in% c("p", "ribbon", "polygon", "polypath"))) { legend_args[["lwd"]] = legend_args[["lwd"]] %||% lwd } - if (is.null(type) || type %in% c("p", "pointrange", "errorbar", "text")) { + if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } if (isTRUE(type %in% c("rect", "ribbon", "boxplot", "hist", "histogram", "spineplot", "ridge", "violin")) || gradient) { diff --git a/R/type_pointrange.R b/R/type_pointrange.R index 90290354..e116447f 100644 --- a/R/type_pointrange.R +++ b/R/type_pointrange.R @@ -49,7 +49,7 @@ draw_pointrange = function() { data_pointrange = function(dodge, fixed.dodge) { fun = function(settings, ...) { - env2env(settings, environment(), c("datapoints", "xlabs")) + env2env(settings, environment(), c("datapoints", "xlabs", "cex")) if (is.character(datapoints$x)) { datapoints$x = as.factor(datapoints$x) @@ -71,6 +71,9 @@ data_pointrange = function(dodge, fixed.dodge) { } x = datapoints$x + + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% (cex %||% par("cex")) + env2env(environment(), settings, c( "x", "xlabs", From c3c8e0fe63615b6a3b82fed7c6339bb90afc12a8 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 15:29:21 -0800 Subject: [PATCH 05/21] hist --- R/draw_legend_utils.R | 4 ++-- R/type_barplot.R | 1 + R/type_histogram.R | 9 ++++++++- R/type_pointrange.R | 1 + R/type_polygon.R | 1 + R/type_polypath.R | 1 + 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 647289a7..2666dbde 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,13 +63,13 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("rect", "ribbon", "boxplot", "hist", "histogram", "spineplot", "ridge", "violin")) || gradient) { + if (isTRUE(type %in% c("rect", "ribbon", "boxplot", "spineplot", "ridge", "violin")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 legend_args[["seg.len"]] = legend_args[["seg.len"]] %||% 1.25 } - if (isTRUE(type %in% c("ribbon", "hist", "histogram", "spineplot"))) { + if (isTRUE(type %in% c("ribbon", "spineplot"))) { legend_args[["pt.lwd"]] = legend_args[["pt.lwd"]] %||% 0 } if (identical(type, "p")) { diff --git a/R/type_barplot.R b/R/type_barplot.R index 31ccfc49..9211ea90 100644 --- a/R/type_barplot.R +++ b/R/type_barplot.R @@ -200,6 +200,7 @@ data_barplot = function(width = 5/6, beside = FALSE, center = FALSE, FUN = NULL, xaxt = if (xaxt == "s") "l" else xaxt yaxs = "i" + # legend customizations settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 diff --git a/R/type_histogram.R b/R/type_histogram.R index 52adf080..ffe7114c 100644 --- a/R/type_histogram.R +++ b/R/type_histogram.R @@ -145,7 +145,6 @@ data_histogram = function(breaks = "Sturges", ylab = ifelse(datapoints$freq[1], "Frequency", "Density") } - # browser() x = c(datapoints$xmin, datapoints$xmax) y = c(datapoints$ymin, datapoints$ymax) ymin = datapoints$ymin @@ -154,6 +153,14 @@ data_histogram = function(breaks = "Sturges", xmax = datapoints$xmax by = if (length(unique(datapoints$by)) == 1) by else datapoints$by facet = if (length(unique(datapoints$facet)) == 1) facet else datapoints$facet + + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "x", "y", diff --git a/R/type_pointrange.R b/R/type_pointrange.R index e116447f..c98b61d5 100644 --- a/R/type_pointrange.R +++ b/R/type_pointrange.R @@ -72,6 +72,7 @@ data_pointrange = function(dodge, fixed.dodge) { x = datapoints$x + # legend customizations settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% (cex %||% par("cex")) env2env(environment(), settings, c( diff --git a/R/type_polygon.R b/R/type_polygon.R index a9d3ef7b..3d01814f 100644 --- a/R/type_polygon.R +++ b/R/type_polygon.R @@ -26,6 +26,7 @@ type_polygon = function(density = NULL, angle = 45) { data_polygon = function() { fun = function(settings, ...) { + # legend customizations settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/type_polypath.R b/R/type_polypath.R index 2eff6c6a..dbceacff 100644 --- a/R/type_polypath.R +++ b/R/type_polypath.R @@ -33,6 +33,7 @@ type_polypath = function(rule = "winding") { data_polypath = function() { fun = function(settings, ...) { + # legend customizations settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 From c3beffa993dc3a5777f420e32e1995224ba27242 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 16:22:58 -0800 Subject: [PATCH 06/21] histogram II --- R/tinyplot.R | 2 +- R/type_histogram.R | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/tinyplot.R b/R/tinyplot.R index 0947e4e5..30f4faf8 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -983,7 +983,7 @@ tinyplot.default = function( has_sub = !is.null(sub) - if (isTRUE(was_area_type) || isTRUE(type %in% c("area", "rect", "hist", "histogram"))) { + if (isTRUE(was_area_type) || isTRUE(type %in% c("area", "rect"))) { settings$legend_args[["pt.lwd"]] = par("lwd") settings$legend_args[["lty"]] = 0 } diff --git a/R/type_histogram.R b/R/type_histogram.R index ffe7114c..5d504d22 100644 --- a/R/type_histogram.R +++ b/R/type_histogram.R @@ -157,7 +157,8 @@ data_histogram = function(breaks = "Sturges", # legend customizations settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 - settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% par("lwd") + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 From 65202c22616a1e25ff83e406b16c5a05c6da0f21 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 16:25:58 -0800 Subject: [PATCH 07/21] area and rect --- R/draw_legend_utils.R | 2 +- R/tinyplot.R | 2 +- R/type_area.R | 8 ++++++++ R/type_rect.R | 16 +++++++++++++++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 2666dbde..869734d7 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,7 +63,7 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("rect", "ribbon", "boxplot", "spineplot", "ridge", "violin")) || gradient) { + if (isTRUE(type %in% c("ribbon", "boxplot", "spineplot", "ridge", "violin")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/tinyplot.R b/R/tinyplot.R index 30f4faf8..0b0c226b 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -983,7 +983,7 @@ tinyplot.default = function( has_sub = !is.null(sub) - if (isTRUE(was_area_type) || isTRUE(type %in% c("area", "rect"))) { + if (isTRUE(was_area_type)) { settings$legend_args[["pt.lwd"]] = par("lwd") settings$legend_args[["lty"]] = 0 } diff --git a/R/type_area.R b/R/type_area.R index c9ba59fb..13f96bfc 100644 --- a/R/type_area.R +++ b/R/type_area.R @@ -24,6 +24,14 @@ data_area = function(alpha = alpha) { # ribbon.alpha comes from parent scope, so assign it locally ribbon.alpha = ribbon.alpha + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% par("lwd") + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "datapoints", "ymax", diff --git a/R/type_rect.R b/R/type_rect.R index 173e021b..dd77cf35 100644 --- a/R/type_rect.R +++ b/R/type_rect.R @@ -27,7 +27,7 @@ type_rect = function() { out = list( draw = draw_rect(), - data = NULL, + data = data_rect(), name = "rect" ) class(out) = "tinyplot_type" @@ -35,6 +35,20 @@ type_rect = function() { } +data_rect = function() { + fun = function(settings, ...) { + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% par("lwd") + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + } + return(fun) +} + + draw_rect = function() { fun = function(ixmin, iymin, ixmax, iymax, ilty, ilwd, icol, ibg, ...) { rect( From 2d19af506d1f467c19b3919fe283bcaf9513df2e Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 21:21:22 -0800 Subject: [PATCH 08/21] density --- R/tinyplot.R | 6 ------ R/type_density.R | 14 +++++++++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/R/tinyplot.R b/R/tinyplot.R index 0b0c226b..43a2850a 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -767,7 +767,6 @@ tinyplot.default = function( null_ylim = is.null(ylim), # when palette functions need pre-processing this check raises error null_palette = tryCatch(is.null(palette), error = function(e) FALSE), - was_area_type = identical(type, "area"), # mostly for legend x_by = identical(x, by), # for "boxplot", "spineplot" and "ridges" @@ -983,11 +982,6 @@ tinyplot.default = function( has_sub = !is.null(sub) - if (isTRUE(was_area_type)) { - settings$legend_args[["pt.lwd"]] = par("lwd") - settings$legend_args[["lty"]] = 0 - } - if (!dual_legend) { ## simple case: single legend only if (is.null(lgnd_cex)) lgnd_cex = cex * cex_fct_adj diff --git a/R/type_density.R b/R/type_density.R index d4bd9b23..979b9d3a 100644 --- a/R/type_density.R +++ b/R/type_density.R @@ -149,16 +149,24 @@ data_density = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512, # flags for legend and fill dtype = if (!is.null(bg)) "ribbon" else "l" - dwas_area_type = !is.null(bg) type = dtype - was_area_type = dwas_area_type by = if (length(unique(datapoints$by)) == 1) by else datapoints$by facet = if (length(unique(datapoints$facet)) == 1) facet else datapoints$facet + + # legend customizations (only for filled density plots) + if (!is.null(bg)) { + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% par("lwd") + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + } + env2env(environment(), settings, c( "ylab", "type", - "was_area_type", "ribbon.alpha", "datapoints", "by", From c89916b306abbccc09896f0f3c65808812d87fe9 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 21:34:36 -0800 Subject: [PATCH 09/21] boxplot --- R/draw_legend_utils.R | 2 +- R/type_boxplot.R | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 869734d7..96cbc826 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,7 +63,7 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("ribbon", "boxplot", "spineplot", "ridge", "violin")) || gradient) { + if (isTRUE(type %in% c("ribbon", "spineplot", "ridge", "violin")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/type_boxplot.R b/R/type_boxplot.R index 898858a0..d46fe330 100644 --- a/R/type_boxplot.R +++ b/R/type_boxplot.R @@ -133,6 +133,13 @@ data_boxplot = function() { ymax = datapoints$ymax by = if (length(unique(datapoints$by)) > 1) datapoints$by else by facet = if (length(unique(datapoints$facet)) > 1) datapoints$facet else facet + + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "x", "y", From 95450b8c126b0ce37111c3d191e4bc0a4b796212 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 21:36:32 -0800 Subject: [PATCH 10/21] violin --- R/draw_legend_utils.R | 2 +- R/type_violin.R | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 96cbc826..0fb8a89c 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,7 +63,7 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("ribbon", "spineplot", "ridge", "violin")) || gradient) { + if (isTRUE(type %in% c("ribbon", "spineplot", "ridge")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/type_violin.R b/R/type_violin.R index d9e3db2a..f1d7969d 100644 --- a/R/type_violin.R +++ b/R/type_violin.R @@ -207,6 +207,13 @@ data_violin = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512, by = if (length(unique(datapoints$by)) == 1) by else datapoints$by facet = if (length(unique(datapoints$facet)) == 1) facet else datapoints$facet + + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "datapoints", "by", From d586d6dbd18e7b22b0a1f0a66fed9dd94d65e7eb Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 21:48:05 -0800 Subject: [PATCH 11/21] ridge --- R/draw_legend_utils.R | 2 +- R/tinyplot.R | 3 +-- R/type_ridge.R | 7 +++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 0fb8a89c..026b1124 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,7 +63,7 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("ribbon", "spineplot", "ridge")) || gradient) { + if (isTRUE(type %in% c("ribbon", "spineplot")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 diff --git a/R/tinyplot.R b/R/tinyplot.R index 43a2850a..5d3fd5b3 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -767,8 +767,7 @@ tinyplot.default = function( null_ylim = is.null(ylim), # when palette functions need pre-processing this check raises error null_palette = tryCatch(is.null(palette), error = function(e) FALSE), - x_by = identical(x, by), # for "boxplot", "spineplot" and "ridges" - + x_by = identical(x, by), # for "boxplot", "spineplot" and "ridge" # unevaluated expressions with side effects draw = substitute(draw), diff --git a/R/type_ridge.R b/R/type_ridge.R index c8a171b1..24133f7f 100644 --- a/R/type_ridge.R +++ b/R/type_ridge.R @@ -409,6 +409,13 @@ data_ridge = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512, col = col, alpha = alpha ) + + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "datapoints", "yaxt", From 053247c8f31ed92d4da0799dc2d8e5b89c19409c Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 22:01:44 -0800 Subject: [PATCH 12/21] spineplot --- R/draw_legend_utils.R | 4 ++-- R/type_spineplot.R | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 026b1124..93a1e856 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,13 +63,13 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("ribbon", "spineplot")) || gradient) { + if (isTRUE(type %in% c("ribbon")) || gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 legend_args[["seg.len"]] = legend_args[["seg.len"]] %||% 1.25 } - if (isTRUE(type %in% c("ribbon", "spineplot"))) { + if (isTRUE(type %in% c("ribbon"))) { legend_args[["pt.lwd"]] = legend_args[["pt.lwd"]] %||% 0 } if (identical(type, "p")) { diff --git a/R/type_spineplot.R b/R/type_spineplot.R index 7edcb192..36ea2d78 100644 --- a/R/type_spineplot.R +++ b/R/type_spineplot.R @@ -279,6 +279,14 @@ data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels x_by = x_by, y_by = y_by ) + + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, c( "x", "y", "ymin", "ymax", "xmin", "xmax", "col", "bg", "datapoints", "by", "facet", "axes", "frame.plot", "xaxt", "yaxt", "xaxs", "yaxs", From 4a92181bb80b32429026ca1c9ce6ff5c23f0b8a9 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 22:08:31 -0800 Subject: [PATCH 13/21] minor consolidation --- R/draw_legend_utils.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 93a1e856..d615b951 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -78,13 +78,14 @@ compute_legend_args = function( if (identical(type, "n") && isFALSE(gradient)) { legend_args[["pch"]] = legend_args[["pch"]] %||% par("pch") } + # Special pt.bg handling for types that need color-based fills if (identical(type, "spineplot")) { legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% legend_args[["col"]] - } - if (identical(type, "ridge") && isFALSE(gradient)) { + } else if (identical(type, "ridge") && isFALSE(gradient)) { legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% sapply(legend_args[["col"]], function(ccol) seq_palette(ccol, n = 2)[2]) + } else { + legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% bg } - legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% bg legend_args[["legend"]] = legend_args[["legend"]] %||% lgnd_labs if (length(lgnd_labs) != length(eval(legend_args[["legend"]]))) { warning( From 605f345253c3b09256c74308b76a8342664b1dc6 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 29 Nov 2025 22:30:23 -0800 Subject: [PATCH 14/21] ribbon --- R/draw_legend_utils.R | 5 +---- R/type_glm.R | 9 +++++++++ R/type_lm.R | 9 +++++++++ R/type_loess.R | 9 +++++++++ R/type_ribbon.R | 7 +++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index d615b951..077be50c 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -63,15 +63,12 @@ compute_legend_args = function( if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } - if (isTRUE(type %in% c("ribbon")) || gradient) { + if (gradient) { legend_args[["pch"]] = 22 legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% 3.5 legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 legend_args[["seg.len"]] = legend_args[["seg.len"]] %||% 1.25 } - if (isTRUE(type %in% c("ribbon"))) { - legend_args[["pt.lwd"]] = legend_args[["pt.lwd"]] %||% 0 - } if (identical(type, "p")) { legend_args[["pt.lwd"]] = legend_args[["pt.lwd"]] %||% lwd } diff --git a/R/type_glm.R b/R/type_glm.R index 579e3681..63f015b5 100644 --- a/R/type_glm.R +++ b/R/type_glm.R @@ -61,6 +61,15 @@ data_glm = function(family, se, level, type, ...) { }) datapoints = do.call(rbind, dat) datapoints = datapoints[order(datapoints$facet, datapoints$by, datapoints$x), ] + + # legend customizations - same as ribbon but add line through square + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% par("lty") + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, "datapoints") } return(fun) diff --git a/R/type_lm.R b/R/type_lm.R index 9bf4a627..7a09cf75 100644 --- a/R/type_lm.R +++ b/R/type_lm.R @@ -57,6 +57,15 @@ data_lm = function(se, level, ...) { }) datapoints = do.call(rbind, dat) datapoints = datapoints[order(datapoints$facet, datapoints$by, datapoints$x), ] + + # legend customizations - same as ribbon but add line through square + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% par("lty") + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, "datapoints") } return(fun) diff --git a/R/type_loess.R b/R/type_loess.R index eea85e6b..c0c5aeee 100644 --- a/R/type_loess.R +++ b/R/type_loess.R @@ -51,6 +51,15 @@ data_loess = function(span, degree, family, control, se, level, ...) { }) datapoints = do.call(rbind, datapoints) datapoints = datapoints[order(datapoints$facet, datapoints$by, datapoints$x), ] + + # legend customizations - same as ribbon but add line through square + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% par("lty") + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + env2env(environment(), settings, "datapoints") } return(fun) diff --git a/R/type_ribbon.R b/R/type_ribbon.R index 2ea08028..e303a93a 100644 --- a/R/type_ribbon.R +++ b/R/type_ribbon.R @@ -154,6 +154,13 @@ data_ribbon = function(ribbon.alpha = NULL, dodge = 0, fixed.dodge = FALSE) { # ribbon.alpha comes from parent scope, so assign it locally ribbon.alpha = ribbon.alpha + # legend customizations + settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 + settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 + settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 + settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 + vars_to_copy = c("x", "y", "ymin", "ymax", "xlabs", "datapoints", "ribbon.alpha") if (!is.null(by)) vars_to_copy = c(vars_to_copy, "by") if (!is.null(facet)) vars_to_copy = c(vars_to_copy, "facet") From 4bf35b0ba23c392c83c5df533227a1640c065ee2 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sun, 30 Nov 2025 19:34:31 -0800 Subject: [PATCH 15/21] settings already copied to main env --- R/tinyplot.R | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/R/tinyplot.R b/R/tinyplot.R index 5d3fd5b3..380da6e4 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -908,12 +908,14 @@ tinyplot.default = function( # ## aesthetics by group ----- # + by_aesthetics(settings) # ## make settings available in the environment directly ----- # + env2env(settings, environment()) @@ -955,18 +957,18 @@ tinyplot.default = function( legend = NULL } if (!is.null(legend) && is.character(legend) && legend == "none") { - settings$legend_args[["x"]] = "none" + legend_args[["x"]] = "none" dual_legend = FALSE } if (null_by) { if (bubble && !dual_legend) { - settings$legend_args[["title"]] = cex_dep + legend_args[["title"]] = cex_dep lgnd_labs = names(bubble_cex) lgnd_cex = bubble_cex * cex_fct_adj } else if (is.null(legend)) { legend = "none" - settings$legend_args[["x"]] = "none" + legend_args[["x"]] = "none" } } @@ -986,7 +988,7 @@ tinyplot.default = function( if (is.null(lgnd_cex)) lgnd_cex = cex * cex_fct_adj draw_legend( legend = legend, - legend_args = settings$legend_args, + legend_args = legend_args, by_dep = by_dep, lgnd_labs = lgnd_labs, type = type, @@ -1003,7 +1005,7 @@ tinyplot.default = function( ## dual legend case... # sanitize_legend: processes legend arguments and returns standardized legend_args list - legend_args = sanitize_legend(legend, settings$legend_args) + legend_args = sanitize_legend(legend, legend_args) # legend 1: by (grouping) key lgby = list( @@ -1054,7 +1056,7 @@ tinyplot.default = function( } has_legend = TRUE - } else if (settings$legend_args[["x"]] == "none" && !add) { + } else if (legend_args[["x"]] == "none" && !add) { omar = par("mar") ooma = par("oma") topmar_epsilon = 0.1 From b9bbaebb7d97db887a5cac8ee49ea620794e80d0 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sun, 30 Nov 2025 20:36:16 -0800 Subject: [PATCH 16/21] consolidate bubble logic --- R/bubble.R | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ R/tinyplot.R | 11 +++++++---- R/type_bubble.R | 15 --------------- R/type_points.R | 35 ++++++--------------------------- R/type_text.R | 36 ++++++---------------------------- 5 files changed, 70 insertions(+), 78 deletions(-) create mode 100644 R/bubble.R delete mode 100644 R/type_bubble.R diff --git a/R/bubble.R b/R/bubble.R new file mode 100644 index 00000000..af65743d --- /dev/null +++ b/R/bubble.R @@ -0,0 +1,51 @@ +bubble = function(settings) { + # Only process for points and text types + if (!(settings$type %in% c("p", "text"))) return(invisible()) + + cex = settings$cex + + # Only process if cex is a vector matching data length + if (is.null(cex) || length(cex) != nrow(settings$datapoints)) return(invisible()) + + clim = settings$clim %||% c(0.5, 2.5) + + bubble = TRUE + + ## Identify the pretty break points for our bubble labels + bubble_labs = pretty(cex, n = 5) + len_labs = length(bubble_labs) + cex = rescale_num(sqrt(c(bubble_labs, cex)) / pi, to = clim) + bubble_cex = cex[1:len_labs] + cex = cex[(len_labs+1):length(cex)] + + # catch for cases where pretty breaks leads to smallest category of 0 + if (bubble_labs[1] == 0) { + bubble_labs = bubble_labs[-1] + bubble_cex = bubble_cex[-1] + } + names(bubble_cex) = format(bubble_labs) + + if (max(clim) > 2.5) { + settings$legend_args[["x.intersp"]] = max(clim) / 2.5 + settings$legend_args[["y.intersp"]] = sapply(bubble_cex / 2.5, max, 1) + } + + # Must update settings with bubble/bubble_cex/cex before calling sanitize_bubble + env2env(environment(), settings, c("bubble", "bubble_cex", "cex")) + + sanitize_bubble(settings) +} + +sanitize_bubble = function(settings) { + env2env(settings, environment(), c("datapoints", "pch", "alpha", "bg", "cex", "bubble")) + + if (!bubble) return(invisible()) + + datapoints[["cex"]] = cex + bubble_pch = if (!is.null(pch) && length(pch)==1) pch else par("pch") + bubble_alpha = if (!is.null(alpha)) alpha else 1 + bubble_bg_alpha = if (!is.null(bg) && length(bg)==1 && is.numeric(bg) && bg > 0 && bg <=1) bg else 1 + + env2env(environment(), settings, c("datapoints", "bubble_pch", "bubble_alpha", "bubble_bg_alpha")) +} + diff --git a/R/tinyplot.R b/R/tinyplot.R index 380da6e4..1073a4c9 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -733,6 +733,9 @@ tinyplot.default = function( # type-specific settings bubble = FALSE, + bubble_pch = NULL, + bubble_alpha = NULL, + bubble_bg_alpha = NULL, ygroup = NULL, # for type_ridge() # data points and labels @@ -882,10 +885,10 @@ tinyplot.default = function( ## bubble plot ----- # - # catch some simple aesthetics for bubble plots before the standard "by" - # grouping sanitizers (actually: will only be used for dual_legend plots but - # easiest to assign/determine now) - sanitize_bubble(settings) + # Transform cex values for bubble charts. Handles size transformation, legend + # gotchas, and aesthetic sanitization. + # Currently limited to "p" and "text" types, but could expand to others. + bubble(settings) # diff --git a/R/type_bubble.R b/R/type_bubble.R deleted file mode 100644 index 53e3977e..00000000 --- a/R/type_bubble.R +++ /dev/null @@ -1,15 +0,0 @@ -sanitize_bubble = function(settings) { - env2env(settings, environment(), c("datapoints", "pch", "alpha", "bg", "cex", "bubble")) - if (bubble) { - datapoints[["cex"]] = cex - bubble_pch = if (!is.null(pch) && length(pch)==1) pch else par("pch") - bubble_alpha = if (!is.null(alpha)) alpha else 1 - bubble_bg_alpha = if (!is.null(bg) && length(bg)==1 && is.numeric(bg) && bg > 0 && bg <=1) bg else 1 - } - - bubble_pch = if (bubble) bubble_pch else NULL - bubble_alpha = if (bubble) bubble_alpha else NULL - bubble_bg_alpha = if (bubble) bubble_bg_alpha else NULL - - env2env(environment(), settings, c("datapoints", "bubble_pch", "bubble_alpha", "bubble_bg_alpha")) -} diff --git a/R/type_points.R b/R/type_points.R index 781d8741..e1c13e55 100644 --- a/R/type_points.R +++ b/R/type_points.R @@ -43,8 +43,11 @@ type_points = function(clim = c(0.5, 2.5), dodge = 0, fixed.dodge = FALSE) { } 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")) + fun = function(settings, ...) { + env2env(settings, environment(), "datapoints") + + # Store clim for bubble() function + settings$clim = clim # catch for factors (we should still be able to "force" plot these with points) if (is.factor(datapoints$x)) { @@ -69,36 +72,10 @@ data_points = function(clim = c(0.5, 2.5), dodge = 0, fixed.dodge = FALSE) { datapoints = dodge_positions(datapoints, dodge, fixed.dodge) } - bubble = FALSE - bubble_cex = 1 - if (!is.null(cex) && length(cex) == nrow(datapoints)) { - bubble = TRUE - ## Identify the pretty break points for our bubble labels - bubble_labs = pretty(cex, n = 5) - len_labs = length(bubble_labs) - cex = rescale_num(sqrt(c(bubble_labs, cex)) / pi, to = clim) - bubble_cex = cex[1:len_labs] - cex = cex[(len_labs+1):length(cex)] - # catch for cases where pretty breaks leads to smallest category of 0 - if (bubble_labs[1] == 0) { - bubble_labs = bubble_labs[-1] - bubble_cex = bubble_cex[-1] - } - names(bubble_cex) = format(bubble_labs) - if (max(clim) > 2.5) { - legend_args[["x.intersp"]] = max(clim) / 2.5 - legend_args[["y.intersp"]] = sapply(bubble_cex / 2.5, max, 1) - } - } - env2env(environment(), settings, c( "datapoints", "xlabs", - "ylabs", - "cex", - "bubble", - "bubble_cex", - "legend_args" + "ylabs" )) } } diff --git a/R/type_text.R b/R/type_text.R index 425bf81e..f0774be9 100644 --- a/R/type_text.R +++ b/R/type_text.R @@ -85,8 +85,12 @@ type_text = function( } data_text = function(labels = NULL, clim = c(0.5, 2.5)) { - fun = function(settings, cex = NULL, ...) { + fun = function(settings, ...) { env2env(settings, environment(), "datapoints") + + # Store clim for bubble() function + settings$clim = clim + if (is.null(labels)) { labels = datapoints$y } @@ -102,35 +106,7 @@ data_text = function(labels = NULL, clim = c(0.5, 2.5)) { datapoints$y = as.numeric(datapoints$y) } - bubble = FALSE - bubble_cex = 1 - if (!is.null(cex) && length(cex) == nrow(datapoints)) { - bubble = TRUE - ## Identify the pretty break points for our bubble labels - bubble_labs = pretty(cex, n = 5) - len_labs = length(bubble_labs) - # cex = rescale_num(c(bubble_labs, cex), to = clim) - cex = rescale_num(sqrt(c(bubble_labs, cex)) / pi, to = clim) - bubble_cex = cex[1:len_labs] - cex = cex[(len_labs + 1):length(cex)] - # catch for cases where pretty breaks leads to smallest category of 0 - if (bubble_labs[1] == 0) { - bubble_labs = bubble_labs[-1] - bubble_cex = bubble_cex[-1] - } - names(bubble_cex) = format(bubble_labs) - if (max(clim) > 2.5) { - legend_args[["x.intersp"]] = max(clim) / 2.5 - legend_args[["y.intersp"]] = sapply(bubble_cex / 2.5, max, 1) - } - } - - env2env(environment(), settings, c( - "datapoints", - "cex", - "bubble", - "bubble_cex" - )) + env2env(environment(), settings, "datapoints") } return(fun) } From 861fa6ffd96d2205de08fef0b1b7a9c19f87dc14 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sun, 30 Nov 2025 21:36:20 -0800 Subject: [PATCH 17/21] "p" (with bubble exception still to-do) --- R/bubble.R | 4 ++++ R/draw_legend_utils.R | 9 +++------ R/type_points.R | 3 +++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/R/bubble.R b/R/bubble.R index af65743d..424ac5c8 100644 --- a/R/bubble.R +++ b/R/bubble.R @@ -29,6 +29,10 @@ bubble = function(settings) { settings$legend_args[["x.intersp"]] = max(clim) / 2.5 settings$legend_args[["y.intersp"]] = sapply(bubble_cex / 2.5, max, 1) } + + ## fixme: can't assign pt.cex here b/c of dual legend gotcha (don't want to + ## override the "normal" pt.cex too) + # legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (settings[["cex"]] %||% par("cex")) # Must update settings with bubble/bubble_cex/cex before calling sanitize_bubble env2env(environment(), settings, c("bubble", "bubble_cex", "cex")) diff --git a/R/draw_legend_utils.R b/R/draw_legend_utils.R index 077be50c..fac21ae2 100644 --- a/R/draw_legend_utils.R +++ b/R/draw_legend_utils.R @@ -57,9 +57,9 @@ compute_legend_args = function( legend_args[["bty"]] = legend_args[["bty"]] %||% "n" legend_args[["horiz"]] = legend_args[["horiz"]] %||% FALSE legend_args[["xpd"]] = legend_args[["xpd"]] %||% NA - if (!isTRUE(type %in% c("p", "ribbon", "polygon", "polypath"))) { - legend_args[["lwd"]] = legend_args[["lwd"]] %||% lwd - } + legend_args[["lwd"]] = legend_args[["lwd"]] %||% lwd + # special handling of pt.cex for bubble plots + # (fixme: can't handle ahead of time in bubble.R b/c of dual legend gotcha) if (is.null(type) || type %in% c("p", "text")) { legend_args[["pt.cex"]] = legend_args[["pt.cex"]] %||% (cex %||% par("cex")) } @@ -69,9 +69,6 @@ compute_legend_args = function( legend_args[["y.intersp"]] = legend_args[["y.intersp"]] %||% 1.25 legend_args[["seg.len"]] = legend_args[["seg.len"]] %||% 1.25 } - if (identical(type, "p")) { - legend_args[["pt.lwd"]] = legend_args[["pt.lwd"]] %||% lwd - } if (identical(type, "n") && isFALSE(gradient)) { legend_args[["pch"]] = legend_args[["pch"]] %||% par("pch") } diff --git a/R/type_points.R b/R/type_points.R index e1c13e55..16e66663 100644 --- a/R/type_points.R +++ b/R/type_points.R @@ -72,6 +72,9 @@ data_points = function(clim = c(0.5, 2.5), dodge = 0, fixed.dodge = FALSE) { datapoints = dodge_positions(datapoints, dodge, fixed.dodge) } + # legend customizations + settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% settings$lwd + env2env(environment(), settings, c( "datapoints", "xlabs", From e0dec3364c6a9adce007fcedcfa07cab1762ded3 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Mon, 1 Dec 2025 14:04:16 -0800 Subject: [PATCH 18/21] issue #532 --- vignettes/types.qmd | 182 +++++++++++++++++++++++++++++++++----------- 1 file changed, 139 insertions(+), 43 deletions(-) diff --git a/vignettes/types.qmd b/vignettes/types.qmd index a248e9c1..290538df 100644 --- a/vignettes/types.qmd +++ b/vignettes/types.qmd @@ -166,59 +166,35 @@ tinyplot(Temp ~ Wind | Month, data = aq, facet = "by", type = "lm") It is easy to add custom types to **tinyplot**. Users who need highly customized plots, or developers who want to add support to their package or functions, only -need to define three simple functions: `data_()`, `draw_()`, and -`type_()`. - -In this section, we explain the role of each of these functions and present a -minimalist example of a custom type. Interested readers may refer to -the **tinyplot** [source code](https://github.com/grantmcdermott/tinyplot) -to see many more examples, since each **tinyplot** type is itself implemented as a -custom type. - -The three functions that we need to define for a new type are: - -1. `data_()`: Data function factory. - - Must take two arguments: - - `settings` is a special internal environment containing useful parameters that can be read or modified by `tinyplot` functions, including `datapoints`, `by`, `facet`, `xlab`, `ylab`, `xlim`, `ylim`, `palette`, and many more (see below) - - `...` - - Defines the data that will be passed on to the plotting functions (including any necessary transformations). Note that this function should modify the `settings` environment in-place; it does not need to return anything -2. `draw_()`: Drawing function factory. - - Accepts information about data point values and aesthetics. - - Inputs must include `...` - - The `i` prefix in argument names indicates that we are operating on a subgroup of the data, identified by `facet` or using the `|` operator in a formula. - - Available arguments are: `ibg`, `icol`, `ilty`, `ilwd`, `ipch`, `ix`, `ixmax`, `ixmin`, `iy`, `iymax`, `iymin`, `cex`, `dots`, `type`, `x_by`, `i`, `facet_by`, `by_data`, `facet_data`, `flip` - - Returns a function which can call base R to draw the plot. -3. `type_()`: A wrapper function that returns a named list with three elements: - - `draw` - - `data` - - `name` +need to define their own `type_()` function. Let's start with a simple +example to see how this works in practice. ### A minimal example -Here is a minimal example of a custom type that logs both `x` and `y` and -plots points. Note how we use `env2env()` to extract variables from the `settings` -environment, modify them, and copy the modified values back: +Here is a minimal example of a custom `type_log()` that logs both `x` and `y` +before plotting points. ```{r} -#| layout-ncol: 2 type_log = function(base = exp(1)) { + # data tranformation function data_log = function() { fun = function(settings, ...) { - # Extract datapoints from settings environment + # extract raw datapoints from settings environment datapoints = settings$datapoints - # Transform the data + # transform (log) the data datapoints$x = log(datapoints$x, base = base) datapoints$y = log(datapoints$y, base = base) datapoints = datapoints[order(datapoints$x), ] - # Assign (inject) modified datapoints back to settings + # re-assign modified datapoints back to settings settings$datapoints = datapoints } return(fun) } + # drawing function draw_log = function() { fun = function(ix, iy, icol, ...) { points( @@ -230,45 +206,165 @@ type_log = function(base = exp(1)) { return(fun) } + # return object (list) out = list( + data = data_log(), draw = draw_log(), + name = "p" # fallback behaviour same as "p" type (e.g., legend defaults) + ) + class(out) = "tinyplot_type" + return(out) + +} +``` + +To use, simply pass to `type = type_log()` as per as normal. + +```{r} +#| layout-ncol: 2 +tinyplot(mpg ~ wt | factor(am), data = mtcars, + type = type_log(), main = "Ln (natural logarithm)") + +tinyplot(mpg ~ wt | factor(am), data = mtcars, + type = type_log(base = 10), main = "Log (base 10)") +``` + +### How it works + +As illustrated by our minimal example, custom **tinyplot** types require three +key functions: + +1. **`type_()`**: The main wrapper function that users will call. It + should accept any type-specific arguments that users will need to adjust the + final plot (e.g., here: `base`). More importantly, it _must_ return a list + of class `"tinyplot_type"` and contain three elements: + - `data`: A data transformation function (see below) + - `draw`: A drawing function (see below) + - `name`: The name of the underlying plot type (this can be anything, but + think of it as a convenient way to inherit default/fallback behaviour from + an existing plot type) + +2. **`data_()`**: A function factory that returns a data transformation + function: + - **Required arguments**: `settings` and `...` + - `settings` is an environment containing plot data and parameters, + including `datapoints` (a data frame with `x`, `y`, `by`, etc.), + `legend_args` (a list of legend customizations), and many other internal + parameters (see [Appendix: Available settings](#appendix-available-settings) + below for a complete list). + - Should modify `settings` in-place (e.g., `settings$datapoints = ...`) + - Does not need to return anything + +3. **`draw_()`**: A function factory that returns a drawing function: + - **Required arguments**: `...` + - **Available arguments**: `ix`, `iy`, `ixmin`, `ixmax`, `iymin`, `iymax`, + `icol`, `ibg`, `ipch`, `ilty`, `ilwd`, `icex`, and others + - The `i` prefix indicates data for a single group (when using `|` grouping + or facets) + - Should call base R graphics functions (e.g., `points()`, `lines()`, + `polygon()`) to render the plot + +The key insight is that `data_*()` transforms the data once, while `draw_*()` +is called repeatedly for each group or facet. + +### Pro-tip: Re-use existing plot types and scaffolding + +Rather than code up all of the internals for a custom type from scratch, users +are strongly encouraged to re-use the scaffolding for existing types. One of the +easiest ways to do this---which we use repeatedly in the main **tinyplot** +codebase---is by "delegating" to an existing type at the end of the +`data_` function. Again, this is perhaps easier seen than explained, so +consider an alternate version of our previous `type_log()` function. + +```{r} +type_log_redux = function(base = exp(1)) { + + # data tranformation function + data_log = function() { + fun = function(settings, ...) { + datapoints = settings$datapoints + + datapoints$x = log(datapoints$x, base = base) + datapoints$y = log(datapoints$y, base = base) + datapoints = datapoints[order(datapoints$x), ] + + settings$datapoints = datapoints + + # re-assign/delegate to "p" type + settings$type = "p" + } + return(fun) + } + + # return object (list) + out = list( data = data_log(), + draw = NULL, # NULL => fall back to default type (now "p") drawing function name = "log" ) class(out) = "tinyplot_type" return(out) + } +``` + +The key differences from our original `type_log` function are: + +- `settings$type = "p"` (re-assign the type at the end of `data_log()`) +- `draw = NULL` (drop the dedicated `draw_log()` function entirely; will fall back to `draw_points()` internally now) +This "redux" version is obviously shorter than our original version, since we +can skip the custom `draw_log()` section. But re-using existing **tinyplot** +scaffolding brings added benefits beyond code concision. In particular, it +guarantees that all of the functionality from an existing `type` will be made +available to your custom type. For example, in our first `type_log()` function +we didn't account for the fact that users might want to adjust groups by other +aesthetics (e.g, `pch`), whereas we get this for free in `type_log_redux()`. + +```{r} +#| layout-ncol: 2 tinyplot(mpg ~ wt | factor(am), data = mtcars, - type = type_log(), main = "Ln") + pch = "by", + type = type_log(), main = "type_log") tinyplot(mpg ~ wt | factor(am), data = mtcars, - type = type_log(base = 10), main = "Log 10") + pch = "by", + type = type_log_redux(), main = "type_log_redux") ``` +Moral of the story: be lazy and re-use existing **tinyplot** scaffolding +wherever possible; especially drawing functions. + ### More examples The **tinyplot** [source code](https://github.com/grantmcdermott/tinyplot/tree/main/R) contains many examples of type constructor functions that should provide a -helpful starting point for custom plot types. Failing that, the **tinyplot** -team are always happy to help guide users on how to create their own types and -re-purpose existing **tinyplot** code. Just let us know by +helpful starting point for custom plot types. Each built-in **tinyplot** type +is itself implemented as a custom type, so you can see real-world examples of +varying complexity. For example, take a look at the +[`type_points.R`](https://github.com/grantmcdermott/tinyplot/blob/main/R/type_points.R) or +[`type_area.R`](https://github.com/grantmcdermott/tinyplot/blob/main/R/type_area.R) +code. (The latter provides an example where we delegate to the `type_ribbon` +drawing code.) + +Beyond that, the **tinyplot** team are always happy to help guide users on how +to create their own types. Just let us know by [raising an issue](https://github.com/grantmcdermott/tinyplot/issues) on our GitHub repo. +## Appendix: Available settings {#appendix-available-settings} -### Available settings - -To see what parameters are available in the `settings` environment, we can create -a simple `type_error()` that stops with the names of all available settings: +To see what objects and parameters are available in the `settings` environment, +we can create a simple `type_error()` function that stops with the names of all +available settings: ```{r} #| error: true type_error = function() { data_error = function() { fun = function(settings, ...) { - stop(paste(names(settings), collapse = ", ")) + stop(paste(sort(names(settings)), collapse = ", ")) } return(fun) } From e26cd24a1467b3d851f2fdd399cdf7a72842ce80 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Mon, 1 Dec 2025 14:15:12 -0800 Subject: [PATCH 19/21] tweak --- vignettes/types.qmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/types.qmd b/vignettes/types.qmd index 290538df..f5962bcf 100644 --- a/vignettes/types.qmd +++ b/vignettes/types.qmd @@ -267,7 +267,7 @@ key functions: The key insight is that `data_*()` transforms the data once, while `draw_*()` is called repeatedly for each group or facet. -### Pro-tip: Re-use existing plot types and scaffolding +### Pro tip: Re-use existing plot types Rather than code up all of the internals for a custom type from scratch, users are strongly encouraged to re-use the scaffolding for existing types. One of the From 4b7063424688896afe314ee4fc9245694252bd22 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Mon, 1 Dec 2025 14:32:45 -0800 Subject: [PATCH 20/21] bump to dev version --- DESCRIPTION | 2 +- NEWS.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 9628fd94..009dd9cb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: tinyplot Type: Package Title: Lightweight Extension of the Base R Graphics System -Version: 0.6.0 +Version: 0.6.99 Date: 2025-11-26 Authors@R: c( diff --git a/NEWS.md b/NEWS.md index 65807b38..0a586921 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,21 @@ _If you are viewing this file on CRAN, please check the [latest NEWS](https://grantmcdermott.com/tinyplot/NEWS.html) on our website where the formatting is also better._ +## Development version + +### Internals + +- We now encourage type-specific legend customizations within the individual + `type_` constructors. (#531 @grantmcdermott) + +### Documentation + +- Improved guidance for + [custom types](https://grantmcdermott.com/tinyplot/vignettes/types.html#custom-types) + in the `Types` vignette. (#531 @grantmcdermott) + +### Breaking changes + ## v0.6.0 ### Breaking changes From 52edab1b650c905d00cb38bd5429e58372de4cf0 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Mon, 1 Dec 2025 14:38:59 -0800 Subject: [PATCH 21/21] custom type_log test --- .../_tinysnapshot/custom_type_log.svg | 116 ++++++++++++++++++ inst/tinytest/test-type_custom.R | 34 +++++ 2 files changed, 150 insertions(+) create mode 100644 inst/tinytest/_tinysnapshot/custom_type_log.svg create mode 100644 inst/tinytest/test-type_custom.R diff --git a/inst/tinytest/_tinysnapshot/custom_type_log.svg b/inst/tinytest/_tinysnapshot/custom_type_log.svg new file mode 100644 index 00000000..af40ee85 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/custom_type_log.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + +factor(am) +0 +1 + + + + + + + +Custom: type_log() +wt +mpg + + + + + + + + + + +0.4 +0.6 +0.8 +1.0 +1.2 +1.4 +1.6 + + + + + + + +2.4 +2.6 +2.8 +3.0 +3.2 +3.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-type_custom.R b/inst/tinytest/test-type_custom.R new file mode 100644 index 00000000..92f00868 --- /dev/null +++ b/inst/tinytest/test-type_custom.R @@ -0,0 +1,34 @@ +source("helpers.R") +using("tinysnapshot") + +# type_log +f = function () { + + # based on the "redux" example in the types vignette + type_log = function(base = exp(1)) { + data_log = function() { + fun = function(settings, ...) { + datapoints = settings$datapoints + datapoints$x = log(datapoints$x, base = base) + datapoints$y = log(datapoints$y, base = base) + datapoints = datapoints[order(datapoints$x), ] + settings$datapoints = datapoints + settings$type = "p" + } + return(fun) + } + out = list( + data = data_log(), + draw = NULL, + name = "log" + ) + class(out) = "tinyplot_type" + return(out) + } + + tinyplot(mpg ~ wt | factor(am), data = mtcars, + pch = "by", + type = type_log(), + main = "Custom: type_log()") +} +expect_snapshot_plot(f, label = "custom_type_log") \ No newline at end of file