Skip to content

Commit 97edd62

Browse files
authored
Continuous limits for discrete scales (#6273)
* simplify expansion * add `continuous.limits` as scale argument * allow `scale$continuous_limits` to overrule expansion limits * document * confine and improve * add news bullet * add tests * fix wrong description * Better parity with continuous limits
1 parent 3d1102c commit 97edd62

File tree

5 files changed

+67
-9
lines changed

5 files changed

+67
-9
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* `scale_{x/y}_discrete(continuous.limits)` is a new argument to control the
4+
display range of discrete scales (@teunbrand, #4174, #6259).
35
* `geom_ribbon()` now appropriately warns about, and removes, missing values
46
(@teunbrand, #6243).
57
* `guide_*()` can now accept two inside legend theme elements:

R/scale-discrete-.R

+14-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
#' argument (the number of levels in the scale) returns the numerical values
1717
#' that they should take.
1818
#' @param sec.axis [dup_axis()] is used to specify a secondary axis.
19+
#' @param continuous.limits One of:
20+
#' * `NULL` to use the default scale range
21+
#' * A numeric vector of length two providing a display range for the scale.
22+
#' Use `NA` to refer to the existing minimum or maximum.
23+
#' * A function that accepts the limits and returns a numeric vector of
24+
#' length two.
1925
#' @rdname scale_discrete
2026
#' @family position scales
2127
#' @seealso
@@ -69,7 +75,8 @@
6975
#' }
7076
scale_x_discrete <- function(name = waiver(), ..., palette = seq_len,
7177
expand = waiver(), guide = waiver(),
72-
position = "bottom", sec.axis = waiver()) {
78+
position = "bottom", sec.axis = waiver(),
79+
continuous.limits = NULL) {
7380
sc <- discrete_scale(
7481
aesthetics = ggplot_global$x_aes, name = name,
7582
palette = palette, ...,
@@ -78,13 +85,15 @@ scale_x_discrete <- function(name = waiver(), ..., palette = seq_len,
7885
)
7986

8087
sc$range_c <- ContinuousRange$new()
88+
sc$continuous_limits <- continuous.limits
8189
set_sec_axis(sec.axis, sc)
8290
}
8391
#' @rdname scale_discrete
8492
#' @export
8593
scale_y_discrete <- function(name = waiver(), ..., palette = seq_len,
8694
expand = waiver(), guide = waiver(),
87-
position = "left", sec.axis = waiver()) {
95+
position = "left", sec.axis = waiver(),
96+
continuous.limits = NULL) {
8897
sc <- discrete_scale(
8998
aesthetics = ggplot_global$y_aes, name = name,
9099
palette = palette, ...,
@@ -93,6 +102,7 @@ scale_y_discrete <- function(name = waiver(), ..., palette = seq_len,
93102
)
94103

95104
sc$range_c <- ContinuousRange$new()
105+
sc$continuous_limits <- continuous.limits
96106
set_sec_axis(sec.axis, sc)
97107
}
98108

@@ -106,6 +116,8 @@ scale_y_discrete <- function(name = waiver(), ..., palette = seq_len,
106116
#' @usage NULL
107117
#' @export
108118
ScaleDiscretePosition <- ggproto("ScaleDiscretePosition", ScaleDiscrete,
119+
continuous_limits = NULL,
120+
109121
train = function(self, x) {
110122
if (is.discrete(x)) {
111123
self$range$train(x, drop = self$drop, na.rm = !self$na.translate)

R/scale-expansion.R

+17-5
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ expand_range4 <- function(limits, expand) {
8181

8282
# Calculate separate range expansion for the lower and
8383
# upper range limits, and then combine them into one vector
84-
lower <- expand_range(limits, expand[1], expand[2])[1]
85-
upper <- expand_range(limits, expand[3], expand[4])[2]
86-
c(lower, upper)
84+
expand_range(limits, expand[c(1, 3)], expand[c(2, 4)])
8785
}
8886

8987
#' Calculate the default expansion for a scale
@@ -153,7 +151,8 @@ expand_limits_scale <- function(scale, expand = expansion(0, 0), limits = waiver
153151
scale$map(limits),
154152
expand,
155153
coord_limits,
156-
range_continuous = scale$range_c$range
154+
range_continuous = scale$range_c$range,
155+
continuous_limits = scale$continuous_limits
157156
)
158157
} else {
159158
# using the inverse transform to resolve the NA value is needed for date/datetime/time
@@ -170,7 +169,20 @@ expand_limits_continuous <- function(limits, expand = expansion(0, 0), coord_lim
170169
}
171170

172171
expand_limits_discrete <- function(limits, expand = expansion(0, 0), coord_limits = c(NA, NA),
173-
range_continuous = NULL) {
172+
range_continuous = NULL, continuous_limits = NULL) {
173+
if (is.function(continuous_limits)) {
174+
continuous_limits <- continuous_limits(limits)
175+
}
176+
if (!is.null(continuous_limits)) {
177+
if (!anyNA(continuous_limits)) {
178+
continuous_limits <- range(continuous_limits)
179+
}
180+
check_numeric(continuous_limits, arg = "continuous.limits")
181+
check_length(continuous_limits, 2L, arg = "continuous.limits")
182+
missing <- is.na(continuous_limits)
183+
limits <- ifelse(missing, range(limits), continuous_limits)
184+
}
185+
174186
limit_info <- expand_limits_discrete_trans(
175187
limits,
176188
expand,

man/scale_discrete.Rd

+13-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-scale-expansion.R

+21
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ test_that("expand_limits_discrete() can override limits with a both discrete and
9696
expand_limits_discrete(c("one", "two"), coord_limits = c(0, NA), range_continuous = c(1, 2)),
9797
c(0, 2)
9898
)
99+
expect_identical(
100+
expand_limits_discrete(1:2, range_continuous = c(1, 2), continuous_limits = c(0, 3)),
101+
c(0, 3)
102+
)
103+
expect_identical(
104+
expand_limits_discrete(1:2, range_continuous = c(1, 2), continuous_limits = c(NA, 4)),
105+
c(1, 4)
106+
)
107+
expect_identical(
108+
expand_limits_discrete(1:2, range_continuous = c(1, 2), continuous_limits = c(0, NA)),
109+
c(0, 2)
110+
)
111+
expect_identical(
112+
expand_limits_discrete(1:2, range_continuous = c(1, 2), continuous_limits = c(NA_real_, NA_real_)),
113+
c(1, 2)
114+
)
115+
expect_identical(
116+
expand_limits_discrete(1:2, range_continuous = 1:2,
117+
continuous_limits = function(x) x + c(-1, 1)),
118+
c(0, 3)
119+
)
99120
})
100121

101122
test_that("expand_limits_continuous_trans() works with inverted transformations", {

0 commit comments

Comments
 (0)