From 4383b512dc7d0ffff59a9898490bd750c6adad5b Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 30 Sep 2024 10:18:19 +0200 Subject: [PATCH 1/5] ensure effective limits are sorted when negatives present --- R/guide-axis-logticks.R | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/R/guide-axis-logticks.R b/R/guide-axis-logticks.R index 37273cba06..1c3dcc18dc 100644 --- a/R/guide-axis-logticks.R +++ b/R/guide-axis-logticks.R @@ -187,28 +187,27 @@ GuideAxisLogticks <- ggproto( # Reconstruct original range limits <- transformation$inverse(scale$get_limits()) has_negatives <- any(limits <= 0) - - if (!has_negatives) { - start <- floor(log10(min(limits))) - 1L - end <- ceiling(log10(max(limits))) + 1L - } else { - params$negative_small <- params$negative_small %||% 0.1 - start <- floor(log10(abs(params$negative_small))) - end <- ceiling(log10(max(abs(limits)))) + 1L + if (has_negatives) { + large <- max(abs(limits)) + small <- params$negative_small %||% 0.1 + limits <- sort(c(small * 10, large)) } + start <- floor(log10(min(limits))) - 1L + end <- ceiling(log10(max(limits))) + 1L + # Calculate tick marks - tens <- 10^seq(start, end, by = 1) + tens <- 10^seq(start, end, by = 1L) fives <- tens * 5 ones <- as.vector(outer(setdiff(2:9, 5), tens)) if (has_negatives) { # Filter and mirror ticks around 0 - tens <- tens[tens >= params$negative_small] + tens <- tens[tens >= small] tens <- c(tens, -tens, 0) - fives <- fives[fives >= params$negative_small] + fives <- fives[fives >= small] fives <- c(fives, -fives) - ones <- ones[ones >= params$negative_small] + ones <- ones[ones >= small] ones <- c(ones, -ones) } From 88b167563a983f3485c1bcb56f9409692fe404f2 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 30 Sep 2024 10:18:48 +0200 Subject: [PATCH 2/5] smarter defaults for `negative.small` --- R/guide-axis-logticks.R | 6 ++++-- man/guide_axis_logticks.Rd | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/R/guide-axis-logticks.R b/R/guide-axis-logticks.R index 1c3dcc18dc..bf78c3d815 100644 --- a/R/guide-axis-logticks.R +++ b/R/guide-axis-logticks.R @@ -17,6 +17,8 @@ NULL #' keep the default `NULL` argument. #' @param negative.small When the scale limits include 0 or negative numbers, #' what should be the smallest absolute value that is marked with a tick? +#' If `NULL` (default), will be the smallest of 0.1 or 0.1 times the absolute +#' scale maximum. #' @param short.theme A theme [element][element_line()] for customising the #' display of the shortest ticks. Must be a line or blank element, and #' it inherits from the `axis.minor.ticks` setting for the relevant position. @@ -69,7 +71,7 @@ guide_axis_logticks <- function( mid = 1.5, short = 0.75, prescale.base = NULL, - negative.small = 0.1, + negative.small = NULL, short.theme = element_line(), expanded = TRUE, cap = "none", @@ -189,7 +191,7 @@ GuideAxisLogticks <- ggproto( has_negatives <- any(limits <= 0) if (has_negatives) { large <- max(abs(limits)) - small <- params$negative_small %||% 0.1 + small <- params$negative_small %||% min(c(1, large) * 0.1) limits <- sort(c(small * 10, large)) } diff --git a/man/guide_axis_logticks.Rd b/man/guide_axis_logticks.Rd index b6f7d55737..dbb78e1395 100644 --- a/man/guide_axis_logticks.Rd +++ b/man/guide_axis_logticks.Rd @@ -9,7 +9,7 @@ guide_axis_logticks( mid = 1.5, short = 0.75, prescale.base = NULL, - negative.small = 0.1, + negative.small = NULL, short.theme = element_line(), expanded = TRUE, cap = "none", @@ -33,7 +33,9 @@ When using a log-transform in the position scale or in \code{coord_trans()}, keep the default \code{NULL} argument.} \item{negative.small}{When the scale limits include 0 or negative numbers, -what should be the smallest absolute value that is marked with a tick?} +what should be the smallest absolute value that is marked with a tick? +If \code{NULL} (default), will be the smallest of 0.1 or 0.1 times the absolute +scale maximum.} \item{short.theme}{A theme \link[=element_line]{element} for customising the display of the shortest ticks. Must be a line or blank element, and From a7d0a81f75f8c29c27705524c79bdd8ce8726968 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 30 Sep 2024 10:20:17 +0200 Subject: [PATCH 3/5] add news bullet --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 8d7f8760d0..0ee5d93ccf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # ggplot2 (development version) +* Better handling of the `guide_axis_logticks(negative.small)` parameter when + scale limits have small maximum (@teunbrand, #6121). * New `get_labs()` function for retrieving completed plot labels (@teunbrand, #6008). * Built-in `theme_*()` functions now have `ink` and `paper` arguments to control From e0251569bbc8099c5a890a41a40c6d20bc094fc3 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 30 Sep 2024 10:32:48 +0200 Subject: [PATCH 4/5] add test --- tests/testthat/test-guides.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 23df1f75e2..0396de460d 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -470,6 +470,11 @@ test_that("guide_axis_logticks calculates appropriate ticks", { expect_equal(unlog, c(-rev(outcome), 0, outcome)) expect_equal(key$.type, rep(c(1,2,3), c(7, 4, 28))) + # Test very small pseudo_log (#6121) + scale <- test_scale(transform_pseudo_log(sigma = 1e-5), c(0, 1e-10)) + key <- train_guide(guide_axis_logticks(), scale)$logkey + expect_gte(nrow(key), 1) + # Test expanded argument scale <- test_scale(transform_log10(), c(20, 900)) scale$continuous_range <- c(1, 3) From 073dc1116436005fac654b045affac400bb956c0 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 6 May 2025 12:16:29 +0200 Subject: [PATCH 5/5] use `scales::minor_breaks_log()` --- R/guide-axis-logticks.R | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/R/guide-axis-logticks.R b/R/guide-axis-logticks.R index bf78c3d815..c8542de47a 100644 --- a/R/guide-axis-logticks.R +++ b/R/guide-axis-logticks.R @@ -188,39 +188,12 @@ GuideAxisLogticks <- ggproto( # Reconstruct original range limits <- transformation$inverse(scale$get_limits()) - has_negatives <- any(limits <= 0) - if (has_negatives) { - large <- max(abs(limits)) - small <- params$negative_small %||% min(c(1, large) * 0.1) - limits <- sort(c(small * 10, large)) - } - - start <- floor(log10(min(limits))) - 1L - end <- ceiling(log10(max(limits))) + 1L - - # Calculate tick marks - tens <- 10^seq(start, end, by = 1L) - fives <- tens * 5 - ones <- as.vector(outer(setdiff(2:9, 5), tens)) - if (has_negatives) { - # Filter and mirror ticks around 0 - tens <- tens[tens >= small] - tens <- c(tens, -tens, 0) - fives <- fives[fives >= small] - fives <- c(fives, -fives) - ones <- ones[ones >= small] - ones <- c(ones, -ones) - } - - # Set ticks back into transformed space - ticks <- transformation$transform(c(tens, fives, ones)) - nticks <- c(length(tens), length(fives), length(ones)) + ticks <- minor_breaks_log(smallest = params$negative_small)(limits) + tick_type <- match(attr(ticks, "detail"), c(10, 5, 1)) + ticks <- transformation$transform(ticks) - logkey <- data_frame0( - !!aesthetic := ticks, - .type = rep(1:3, times = nticks) - ) + logkey <- data_frame0(!!aesthetic := ticks, .type = tick_type) # Discard out-of-bounds ticks range <- if (params$expanded) scale$continuous_range else scale$get_limits()