Skip to content

Commit 4a7ce18

Browse files
committed
Complete follow.scale feature for stat_bin()
1 parent 0b601ee commit 4a7ce18

File tree

6 files changed

+77
-13
lines changed

6 files changed

+77
-13
lines changed

R/breaks_cached.R

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
1-
#' Caching scale breaks
1+
#' Cache scale breaks
22
#'
33
#' This helper caches the output of another breaks function the first time it is
44
#' evaluated. All subsequent calls will return the same breaks vector
55
#' regardless of the provided limits. In general this is not what you want
66
#' since the breaks should change when the limits change. It is helpful in the
7-
#' specific case that you are using `follow.scale` on `stat_bin()` and related
7+
#' specific case that you are using `follow.scale` on [stat_bin()] and related
88
#' binning stats, because it ensures that the breaks are not recomputed after
99
#' they are used to define the bin edges.
1010
#'
11+
#' @export
1112
#' @param breaks A function that takes the limits as input and returns breaks
12-
#' as output. See `ggplot2::continuous_scale` for details.
13+
#' as output. See [continuous_scale()] for details.
1314
#'
1415
#' @return A wrapped breaks function suitable for use with ggplot scales.
15-
#' @export
16+
#' @examples
17+
#' discoveries_df <- data.frame(
18+
#' year = unlist(mapply(rep, time(discoveries), discoveries))
19+
#' )
20+
#' p <- ggplot(discoveries_df, aes(year)) +
21+
#' geom_histogram(follow.scale = "minor")
22+
#'
23+
#' # Using follow.scale with function breaks can cause misalignment as the scale
24+
#' # can update the breaks after the bin edges are fixed by the stat
25+
#' p + scale_x_continuous(breaks = scales::breaks_extended())
26+
#'
27+
#' # Wrapping the same breaks function avoids this issue but can leave you with
28+
#' # sub-optimal breaks since they are no longer updated after stats
29+
#' p + scale_x_continuous(breaks = breaks_cached(scales::breaks_extended()))
1630
breaks_cached <- function(breaks) {
1731
if (! rlang::is_function(breaks)) {
1832
cli::cli_abort("{.arg breaks} must be a function")

R/geom-histogram.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@
133133
#' ggplot(economics_long, aes(value)) +
134134
#' facet_wrap(~variable, scales = 'free_x') +
135135
#' geom_histogram(binwidth = function(x) 2 * IQR(x) / (length(x)^(1/3)))
136+
#'
137+
#' # If you've already got your scale breaks set up how you want them, you can
138+
#' # tell stat_bin() to align bin edges with the breaks. This works best when
139+
#' # your scale uses fixed breaks, otherwise the breaks can be updated later
140+
#' ggplot(diamonds, aes(carat)) +
141+
#' geom_histogram(follow.scale = "minor") +
142+
#' scale_x_continuous(breaks = seq(0, 5, 0.5))
136143
geom_histogram <- function(mapping = NULL, data = NULL,
137144
stat = "bin", position = "stack",
138145
...,

R/stat-bin.R

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
#' @param breaks Alternatively, you can supply a numeric vector giving
2323
#' the bin boundaries. Overrides `binwidth`, `bins`, `center`,
2424
#' and `boundary`. Can also be a function that takes group-wise values as input and returns bin boundaries.
25+
#' @param follow.scale Alternatively, the bin edges can be copied from the scale
26+
#' breaks, either `"major"` or `"minor"`. Ignored when `"off"`. Note that if
27+
#. the scale's limits are updated by other layers or expansions then its
28+
#. breaks are recomputed and might end up different to the value copied for
29+
#. the bin edges. This is not an issue when the scale uses a fixed breaks
30+
#. vector.
2531
#' @param closed One of `"right"` or `"left"` indicating whether right
2632
#' or left edges of bins are included in the bin.
2733
#' @param pad If `TRUE`, adds empty bins at either end of x. This ensures
@@ -58,8 +64,8 @@ stat_bin <- function(mapping = NULL, data = NULL,
5864
breaks = NULL,
5965
closed = c("right", "left"),
6066
pad = FALSE,
67+
follow.scale = c("off", "minor", "major"),
6168
na.rm = FALSE,
62-
follow.scale = FALSE,
6369
keep.zeroes = "all",
6470
orientation = NA,
6571
show.legend = NA,
@@ -81,8 +87,8 @@ stat_bin <- function(mapping = NULL, data = NULL,
8187
breaks = breaks,
8288
closed = closed,
8389
pad = pad,
84-
na.rm = na.rm,
8590
follow.scale = follow.scale,
91+
na.rm = na.rm,
8692
orientation = orientation,
8793
keep.zeroes = keep.zeroes,
8894
...
@@ -138,7 +144,15 @@ StatBin <- ggproto("StatBin", Stat,
138144
cli::cli_abort("Only one of {.arg boundary} and {.arg center} may be specified in {.fn {snake_class(self)}}.")
139145
}
140146

141-
if (is.null(params$breaks) && is.null(params$binwidth) && is.null(params$bins)) {
147+
if (!is.null(params$follow.scale)) {
148+
params$follow.scale <- match.arg(params$follow.scale, c("off", "minor", "major"))
149+
if (params$follow.scale == "off") params$follow.scale <- NULL
150+
}
151+
if (!is.null(params$follow.scale) && !is.null(params$breaks)) {
152+
cli::cli_abort("Only one of {.arg follow.scale} and {.arg breaks} may be specified in {.fn {snake_class(self)}}.")
153+
}
154+
155+
if (is.null(params$breaks) && is.null(params$binwidth) && is.null(params$bins) && is.null(params$follow.scale)) {
142156
cli::cli_inform("{.fn {snake_class(self)}} using {.code bins = 30}. Pick better value with {.arg binwidth}.")
143157
params$bins <- 30
144158
}
@@ -152,13 +166,15 @@ StatBin <- ggproto("StatBin", Stat,
152166
center = NULL, boundary = NULL,
153167
closed = c("right", "left"), pad = FALSE,
154168
breaks = NULL, flipped_aes = FALSE, keep.zeroes = "all",
155-
follow.scale = FALSE,
169+
follow.scale = NULL,
156170
# The following arguments are not used, but must
157171
# be listed so parameters are computed correctly
158172
origin = NULL, right = NULL, drop = NULL) {
159173
x <- flipped_names(flipped_aes)$x
160-
if (follow.scale) {
161-
breaks <- scales[[x]]$get_breaks()
174+
if (!is.null(follow.scale)) {
175+
breaks <- switch(follow.scale,
176+
minor = scales[[x]]$get_breaks_minor(),
177+
major = scales[[x]]$get_breaks())
162178
bins <- bin_breaks(breaks, closed)
163179
} else if (!is.null(breaks)) {
164180
if (is.function(breaks)) {

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ reference:
116116
- expansion
117117
- starts_with("scale_")
118118
- get_alt_text
119+
- breaks_cached
119120

120121
- title: "Guides: axes and legends"
121122
desc: >

man/breaks_cached.Rd

Lines changed: 18 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/geom_histogram.Rd

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)