Skip to content

Commit f89a007

Browse files
authored
Discrete minor ticks (#5777)
* plumbing for `minor_breaks` * proper `get_breaks_minor()` method * view scales treat minor breaks equally * tweak param docs * Add news bullet * protect axis against mixing types * remove duplicate news entry * accept new underscore argument
1 parent 0b348cd commit f89a007

16 files changed

+139
-23
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# ggplot2 (development version)
22

3+
* Discrete scales now support `minor_breaks`. This may only make sense in
4+
discrete position scales, where it affects the placement of minor ticks
5+
and minor gridlines (#5434).
36
* Discrete position scales now expose the `palette` argument, which can be used
47
to customise spacings between levels (@teunbrand, #5770).
58
* The default `se` parameter in layers with `geom = "smooth"` will be `TRUE`

R/guide-axis.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ GuideAxis <- ggproto(
136136

137137
if (nrow(major) > 0) {
138138
major$.type <- "major"
139+
if (!vec_is(minor$.value, major$.value)) {
140+
# If we have mixed types of values, which may happen in discrete scales,
141+
# discard minor values in favour of the major values.
142+
minor$.value <- NULL
143+
}
139144
vec_rbind(major, minor)
140145
} else {
141146
minor

R/scale-.R

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
#' Also accepts rlang [lambda][rlang::as_function()] function notation.
2424
#' @param minor_breaks One of:
2525
#' - `NULL` for no minor breaks
26-
#' - `waiver()` for the default breaks (one minor break between
27-
#' each major break)
26+
#' - `waiver()` for the default breaks (none for discrete, one minor break
27+
#' between each major break for continuous)
2828
#' - A numeric vector of positions
2929
#' - A function that given the limits returns a vector of minor breaks. Also
3030
#' accepts rlang [lambda][rlang::as_function()] function notation. When
3131
#' the function has two arguments, it will be given the limits and major
32-
#' breaks.
32+
#' break positions.
3333
#' @param n.breaks An integer guiding the number of major breaks. The algorithm
3434
#' may choose a slightly different number to ensure nice break labels. Will
3535
#' only have an effect if `breaks = waiver()`. Use `NULL` to use the default
@@ -200,7 +200,8 @@ continuous_scale <- function(aesthetics, scale_name = deprecated(), palette, nam
200200
#' The `r link_book("new scales section", "extensions#sec-new-scales")`
201201
#' @keywords internal
202202
discrete_scale <- function(aesthetics, scale_name = deprecated(), palette, name = waiver(),
203-
breaks = waiver(), labels = waiver(), limits = NULL, expand = waiver(),
203+
breaks = waiver(), minor_breaks = waiver(),
204+
labels = waiver(), limits = NULL, expand = waiver(),
204205
na.translate = TRUE, na.value = NA, drop = TRUE,
205206
guide = "legend", position = "left",
206207
call = caller_call(),
@@ -218,6 +219,7 @@ discrete_scale <- function(aesthetics, scale_name = deprecated(), palette, name
218219
limits <- allow_lambda(limits)
219220
breaks <- allow_lambda(breaks)
220221
labels <- allow_lambda(labels)
222+
minor_breaks <- allow_lambda(minor_breaks)
221223

222224
if (!is.function(limits) && (length(limits) > 0) && !is.discrete(limits)) {
223225
cli::cli_warn(c(
@@ -247,6 +249,7 @@ discrete_scale <- function(aesthetics, scale_name = deprecated(), palette, name
247249

248250
name = name,
249251
breaks = breaks,
252+
minor_breaks = minor_breaks,
250253
labels = labels,
251254
drop = drop,
252255
guide = guide,
@@ -1022,7 +1025,34 @@ ScaleDiscrete <- ggproto("ScaleDiscrete", Scale,
10221025
structure(in_domain, pos = match(in_domain, breaks))
10231026
},
10241027

1025-
get_breaks_minor = function(...) NULL,
1028+
get_breaks_minor = function(self, n = 2, b = self$break_positions(),
1029+
limits = self$get_limits()) {
1030+
breaks <- self$minor_breaks
1031+
# The default is to draw no minor ticks
1032+
if (is.null(breaks %|W|% NULL)) {
1033+
return(NULL)
1034+
}
1035+
if (is.function(breaks)) {
1036+
# Ensure function gets supplied numeric limits and breaks
1037+
if (!is.numeric(b)) {
1038+
b <- self$map(b)
1039+
}
1040+
if (!is.numeric(limits)) {
1041+
limits <- self$map(limits)
1042+
limits <- self$dimension(self$expand, limits)
1043+
}
1044+
1045+
# Allow for two types of minor breaks specifications
1046+
break_fun <- fetch_ggproto(self, "minor_breaks")
1047+
arg_names <- fn_fmls_names(break_fun)
1048+
if (length(arg_names) == 1L) {
1049+
breaks <- break_fun(limits)
1050+
} else {
1051+
breaks <- break_fun(limits, b)
1052+
}
1053+
}
1054+
breaks
1055+
},
10261056

10271057
get_labels = function(self, breaks = self$get_breaks()) {
10281058
if (self$is_empty()) {

R/scale-view.R

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@
1515
view_scale_primary <- function(scale, limits = scale$get_limits(),
1616
continuous_range = scale$dimension(limits = limits)) {
1717

18+
# continuous_range can be specified in arbitrary order, but
19+
# scales expect the one in ascending order.
20+
continuous_scale_sorted <- sort(continuous_range)
1821
if(!scale$is_discrete()) {
19-
# continuous_range can be specified in arbitrary order, but
20-
# continuous scales expect the one in ascending order.
21-
continuous_scale_sorted <- sort(continuous_range)
2222
breaks <- scale$get_breaks(continuous_scale_sorted)
23-
minor_breaks <- scale$get_breaks_minor(b = breaks, limits = continuous_scale_sorted)
2423
breaks <- censor(breaks, continuous_scale_sorted, only.finite = FALSE)
2524
} else {
2625
breaks <- scale$get_breaks(limits)
27-
minor_breaks <- scale$get_breaks_minor(b = breaks, limits = limits)
2826
}
27+
minor_breaks <- scale$get_breaks_minor(b = breaks, limits = continuous_scale_sorted)
2928
minor_breaks <- censor(minor_breaks, continuous_range, only.finite = FALSE)
3029

3130
ggproto(NULL, ViewScale,

man/continuous_scale.Rd

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

man/discrete_scale.Rd

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

man/scale_continuous.Rd

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

man/scale_discrete.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.

man/scale_gradient.Rd

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

man/scale_grey.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)