Skip to content

Commit 86cc4d2

Browse files
authored
Fix scale- and coord-related regressions (#3566)
* fix failure with censored breaks when labels were specified manually (fixes #3558) * ensure that ScaleContinuous$is_empty() returns TRUE when there is an NA in the limits (fixes #3560) * ensure that functional and NA limits are handled properly on discrete and continuous scales (fixes #3448)
1 parent 5e388e1 commit 86cc4d2

File tree

6 files changed

+72
-5
lines changed

6 files changed

+72
-5
lines changed

R/guides-axis.r

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ guide_train.axis <- function(guide, scale, aesthetic = NULL) {
9393
}
9494
}
9595

96-
guide$key <- ticks
96+
guide$key <- ticks[is.finite(ticks[[aesthetic]]), ]
9797
}
9898

9999
guide$name <- paste0(guide$name, "_", aesthetic)

R/scale-.r

+24-3
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,9 @@ Scale <- ggproto("Scale", NULL,
451451
if (is.null(self$limits)) {
452452
self$range$range
453453
} else if (is.function(self$limits)) {
454-
# if limits is a function, it expects to work in data space
455-
self$trans$transform(self$limits(self$trans$inverse(self$range$range)))
454+
self$limits(self$range$range)
456455
} else {
457-
ifelse(is.na(self$limits), self$range$range, self$limits)
456+
self$limits
458457
}
459458
},
460459

@@ -542,6 +541,12 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale,
542541
self$range$train(x)
543542
},
544543

544+
is_empty = function(self) {
545+
has_data <- !is.null(self$range$range)
546+
has_limits <- is.function(self$limits) || (!is.null(self$limits) && all(is.finite(self$limits)))
547+
!has_data && !has_limits
548+
},
549+
545550
transform = function(self, x) {
546551
new_x <- self$trans$transform(x)
547552
axis <- if ("x" %in% self$aesthetics) "x" else "y"
@@ -563,6 +568,22 @@ ScaleContinuous <- ggproto("ScaleContinuous", Scale,
563568
self$rescaler(x, from = range)
564569
},
565570

571+
get_limits = function(self) {
572+
if (self$is_empty()) {
573+
return(c(0, 1))
574+
}
575+
576+
if (is.null(self$limits)) {
577+
self$range$range
578+
} else if (is.function(self$limits)) {
579+
# if limits is a function, it expects to work in data space
580+
self$trans$transform(self$limits(self$trans$inverse(self$range$range)))
581+
} else {
582+
# NA limits for a continuous scale mean replace with the min/max of data
583+
ifelse(is.na(self$limits), self$range$range, self$limits)
584+
}
585+
},
586+
566587
dimension = function(self, expand = expansion(0, 0), limits = self$get_limits()) {
567588
expand_limits_scale(self, expand, limits)
568589
},

R/scale-view.r

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ view_scale_primary <- function(scale, limits = scale$get_limits(),
1717

1818
if(!scale$is_discrete()) {
1919
breaks <- scale$get_breaks(continuous_range)
20-
breaks <- breaks[is.finite(breaks)]
2120
minor_breaks <- scale$get_breaks_minor(b = breaks, limits = continuous_range)
2221
} else {
2322
breaks <- scale$get_breaks(limits)

tests/testthat/test-scale-discrete.R

+6
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,9 @@ test_that("discrete position scales can accept functional limits", {
8181
scale$train(c("a", "b", "c"))
8282
expect_identical(scale$get_limits(), c("c", "b", "a"))
8383
})
84+
85+
test_that("discrete non-position scales can accept functional limits", {
86+
scale <- scale_colour_discrete(limits = rev)
87+
scale$train(c("a", "b", "c"))
88+
expect_identical(scale$get_limits(), c("c", "b", "a"))
89+
})

tests/testthat/test-scales-breaks-labels.r

+16
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,22 @@ test_that("continuous limits accepts functions", {
247247
expect_equal(layer_scales(p)$y$get_limits(), c(range(mpg$hwy)[1] - 10, range(mpg$hwy)[2] + 100))
248248
})
249249

250+
test_that("equal length breaks and labels can be passed to ViewScales with limits", {
251+
252+
test_scale <- scale_x_continuous(
253+
breaks = c(0, 20, 40),
254+
labels = c("0", "20", "40"),
255+
limits = c(10, 30)
256+
)
257+
258+
expect_identical(test_scale$get_breaks(), c(NA, 20, NA))
259+
expect_identical(test_scale$get_labels(), c(c("0", "20", "40")))
260+
261+
test_view_scale <- view_scale_primary(test_scale)
262+
expect_identical(test_view_scale$get_breaks(), c(NA, 20, NA))
263+
expect_identical(test_scale$get_labels(), c(c("0", "20", "40")))
264+
})
265+
250266
# Visual tests ------------------------------------------------------------
251267

252268
test_that("minor breaks draw correctly", {

tests/testthat/test-scales.r

+25
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,28 @@ test_that("multiple aesthetics can be set with one function call", {
294294
expect_equal(layer_data(p)$colour, c("cyan", "red", "green"))
295295
expect_equal(layer_data(p)$fill, c("red", "green", "blue"))
296296
})
297+
298+
test_that("limits with NA are replaced with the min/max of the data for continuous scales", {
299+
make_scale <- function(limits = NULL, data = NULL) {
300+
scale <- continuous_scale("aesthetic", scale_name = "test", palette = identity, limits = limits)
301+
if (!is.null(data)) {
302+
scale$train(data)
303+
}
304+
scale
305+
}
306+
307+
# emptiness
308+
expect_true(make_scale()$is_empty())
309+
expect_false(make_scale(limits = c(0, 1))$is_empty())
310+
expect_true(make_scale(limits = c(0, NA))$is_empty())
311+
expect_true(make_scale(limits = c(NA, NA))$is_empty())
312+
expect_true(make_scale(limits = c(NA, 0))$is_empty())
313+
314+
# limits
315+
expect_equal(make_scale(data = 1:5)$get_limits(), c(1, 5))
316+
expect_equal(make_scale(limits = c(1, 5))$get_limits(), c(1, 5))
317+
expect_equal(make_scale(limits = c(NA, NA))$get_limits(), c(0, 1))
318+
expect_equal(make_scale(limits = c(NA, NA), data = 1:5)$get_limits(), c(1, 5))
319+
expect_equal(make_scale(limits = c(1, NA), data = 1:5)$get_limits(), c(1, 5))
320+
expect_equal(make_scale(limits = c(NA, 5), data = 1:5)$get_limits(), c(1, 5))
321+
})

0 commit comments

Comments
 (0)