From d6fae1871055142809c225632b24520e575ac65c Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Fri, 21 Nov 2025 18:00:45 -0800 Subject: [PATCH 1/2] standalone align_layer func --- R/align_layer.R | 36 ++++++++++++++++++++++++++++ R/tinyplot.R | 62 +++++++------------------------------------------ 2 files changed, 44 insertions(+), 54 deletions(-) create mode 100644 R/align_layer.R diff --git a/R/align_layer.R b/R/align_layer.R new file mode 100644 index 00000000..4468312d --- /dev/null +++ b/R/align_layer.R @@ -0,0 +1,36 @@ +# Ensure added layers respect the x-axis order of the original plot layer +# (e.g., when adding lines or ribbons on top of errorbars) +align_layer = function(settings) { + + # Retrieve xlabs from current and original layers + xlabs_layer = settings[["xlabs"]] + xlabs_orig = get("xlabs", envir = get(".tinyplot_env", envir = parent.env(environment()))) + + # Only adjust if original layer has named xlabs + if (!is.null(names(xlabs_orig))) { + if (is.factor(settings$datapoints[["x"]])) { + # Case 1: relevel a factor (e.g., ribbon added to errorbars) + settings$datapoints[["x"]] = tryCatch( + factor(settings$datapoints[["x"]], levels = names(xlabs_orig)), + error = function(e) { + settings$datapoints[["x"]] + } + ) + settings$datapoints = settings$datapoints[order(settings$datapoints[["x"]]), ] + } else if (!is.null(names(xlabs_layer))) { + # Case 2: match implicit integer -> label mapping (e.g., lines added to errorbars) + if (setequal(names(xlabs_layer), names(xlabs_orig))) { + orig_order = xlabs_orig[names(xlabs_layer)[settings$datapoints[["x"]]]] + x_layer = settings$datapoints[["x"]] + settings$datapoints[["x"]] = orig_order + # Adjust ancillary variables + for (v in c("rowid", "xmin", "xmax")) { + if (identical(settings$datapoints[[v]], x_layer)) { + settings$datapoints[[v]] = orig_order + } + } + settings$datapoints = settings$datapoints[order(settings$datapoints[["x"]]), ] + } + } + } +} diff --git a/R/tinyplot.R b/R/tinyplot.R index b186ddf9..eb977f06 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -868,6 +868,13 @@ tinyplot.default = function( settings$type_data(settings, ...) } + # ensure axis aligment of any added layers + if (!add) { + assign("xlabs", settings[["xlabs"]], envir = get(".tinyplot_env", envir = parent.env(environment()))) + } else { + align_layer(settings) + } + # flip -> swap x and y after type_data, except for boxplots (which has its own bespoke flip logic) flip_datapoints(settings) @@ -875,6 +882,7 @@ 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) @@ -1181,60 +1189,6 @@ tinyplot.default = function( list2env(facet_window_args, environment()) - # - ## layering axis alignment ----- - # - - # ensure added layers respect the x-axis order of the original plot layer - # (e.g., when adding lines or ribbons on top of errorbars) - if (!add) { - # keep track of the x(y)labs from the original (1st) layer - assign("xlabs", xlabs, envir = get(".tinyplot_env", envir = parent.env(environment()))) - assign("ylabs", ylabs, envir = get(".tinyplot_env", envir = parent.env(environment()))) - } else { - # check whether we need to adjust any added layers - # aside: we need some extra accounting for flipped plots (x -> y) - if (!flip) { - labs_orig = "xlabs" - labs_layer = xlabs - dp_var = "x" - dp_ovars = c("rowid", "xmin", "xmax") - } else { - labs_orig = "ylabs" - labs_layer = ylabs - dp_var = "y" - dp_ovars = c("rowid", "ymin", "ymax") - } - # retrieve the relevant axis labs from the original layer (and we only care - # if it's not null) - labs_orig = get(labs_orig, envir = get(".tinyplot_env", envir = parent.env(environment()))) - if (!is.null(names(labs_orig))) { - if (is.factor(datapoints[[dp_var]])) { - # case 1: relevel a factor (e.g., ribbon added to errorbars) - datapoints[[dp_var]] = tryCatch( - factor(datapoints[[dp_var]], levels = names(labs_orig)), - error = function(e) { - datapoints[[dp_var]] - } - ) - datapoints = datapoints[order(datapoints[[dp_var]]), ] - } else if (!is.null(names(labs_layer))) { - # case 2: match implicit integer -> label mapping (e.g., lines added to errorbars) - if (setequal(names(labs_layer), names(labs_orig))) { - orig_order = labs_orig[names(labs_layer)[datapoints[[dp_var]]]] - dp_var_layer = datapoints[[dp_var]] - datapoints[[dp_var]] = orig_order - # it's a bit of a pain, but we also have to adjust some ancillary vars - for (dov in dp_ovars) { - if (identical(datapoints[[dov]], dp_var_layer)) datapoints[[dov]] = orig_order - } - datapoints = datapoints[order(datapoints[[dp_var]]), ] - } - } - } - } - - # ## split and draw datapoints ----- # From be1f2e2a207591f8a7b992dbe6e48e957e7ffa08 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Fri, 21 Nov 2025 18:01:30 -0800 Subject: [PATCH 2/2] news --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 04f267ba..d31d6a82 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,7 +46,7 @@ where the formatting is also better._ - Added layers, particularly from `tinyplot_add()`, should now respect the x-axis order of the original plot layer. This should ensure that we don't end up with misaligned layers. For example, when adding a ribbon on top of an - errorbar plot. (#517, #520 @grantmcdermott) + errorbar plot. (#517, #520, #523 @grantmcdermott) ### Documentation