Skip to content

Commit 891ee88

Browse files
authored
Make bin guides compatible with discrete scales (#3703)
1 parent 54a2f2f commit 891ee88

File tree

6 files changed

+87
-12
lines changed

6 files changed

+87
-12
lines changed

R/guide-bins.R

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616
#' @param show.limits Logical. Should the limits of the scale be shown with
1717
#' labels and ticks.
1818
#'
19+
#' @section Use with discrete scale:
20+
#' This guide is intended to show binned data and work together with ggplot2's
21+
#' binning scales. However, it is sometimes desirable to perform the binning in
22+
#' a separate step, either as part of a stat (e.g. [stat_contour_filled()]) or
23+
#' prior to the visualisation. If you want to use this guide for discrete data
24+
#' the levels must follow the naming scheme implemented by [base::cut()]. This
25+
#' means that a bin must be encoded as `"(<lower>, <upper>]"` with `<lower>`
26+
#' giving the lower bound of the bin and `<upper>` giving the upper bound
27+
#' (`"[<lower>, <upper>)"` is also accepted). If you use [base::cut()] to
28+
#' perform the binning everything should work as expected, if not, some recoding
29+
#' may be needed.
30+
#'
1931
#' @return A guide object
2032
#' @family guides
2133
#' @export
@@ -123,13 +135,28 @@ guide_train.bins <- function(guide, scale, aesthetic = NULL) {
123135
if (length(breaks) == 0 || all(is.na(breaks))) {
124136
return()
125137
}
126-
limits <- scale$get_limits()
127-
all_breaks <- c(limits[1], breaks, limits[2])
128-
bin_at <- all_breaks[-1] - diff(all_breaks) / 2
129138
# in the key data frame, use either the aesthetic provided as
130139
# argument to this function or, as a fall back, the first in the vector
131140
# of possible aesthetics handled by the scale
132141
aes_column_name <- aesthetic %||% scale$aesthetics[1]
142+
143+
if (is.numeric(breaks)) {
144+
limits <- scale$get_limits()
145+
all_breaks <- c(limits[1], breaks, limits[2])
146+
bin_at <- all_breaks[-1] - diff(all_breaks) / 2
147+
} else {
148+
# If the breaks are not numeric it is used with a discrete scale. We check
149+
# if the breaks follow the allowed format "(<lower>, <upper>]", and if it
150+
# does we convert it into bin specs
151+
bin_at <- breaks
152+
breaks <- as.character(breaks)
153+
breaks <- strsplit(gsub("\\(|\\)|\\[|\\]", "", breaks), ",\\s?")
154+
breaks <- as.numeric(unlist(breaks))
155+
if (anyNA(breaks)) {
156+
abort('Breaks not formatted correctly for a bin legend. Use `(<lower>, <upper>]` format to indicate bins')
157+
}
158+
all_breaks <- breaks[c(1, seq_along(bin_at) * 2)]
159+
}
133160
key <- new_data_frame(setNames(list(c(scale$map(bin_at), NA)), aes_column_name))
134161
key$.label <- scale$get_labels(all_breaks)
135162
guide$show.limits <- guide$show.limits %||% scale$show_limits %||% FALSE

R/guide-colorsteps.R

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#' visible.
1313
#' @inheritDotParams guide_colourbar -nbin -raster -ticks -available_aes
1414
#'
15+
#' @inheritSection guide_bins Use with discrete scale
16+
#'
1517
#' @return A guide object
1618
#' @export
1719
#'
@@ -54,30 +56,49 @@ guide_colorsteps <- guide_coloursteps
5456

5557
#' @export
5658
guide_train.colorsteps <- function(guide, scale, aesthetic = NULL) {
57-
if (guide$even.steps) {
58-
breaks <- scale$get_breaks()
59-
if (length(breaks) == 0 || all(is.na(breaks)))
59+
breaks <- scale$get_breaks()
60+
if (guide$even.steps || !is.numeric(breaks)) {
61+
if (length(breaks) == 0 || all(is.na(breaks))) {
6062
return()
61-
limits <- scale$get_limits()
62-
all_breaks <- c(limits[1], breaks, limits[2])
63-
bin_at <- all_breaks[-1] - diff(all_breaks) / 2
63+
}
64+
if (is.numeric(breaks)) {
65+
limits <- scale$get_limits()
66+
all_breaks <- c(limits[1], breaks, limits[2])
67+
bin_at <- all_breaks[-1] - diff(all_breaks) / 2
68+
} else {
69+
# If the breaks are not numeric it is used with a discrete scale. We check
70+
# if the breaks follow the allowed format "(<lower>, <upper>]", and if it
71+
# does we convert it into bin specs
72+
bin_at <- breaks
73+
breaks_num <- as.character(breaks)
74+
breaks_num <- strsplit(gsub("\\(|\\)|\\[|\\]", "", breaks_num), ",\\s?")
75+
breaks_num <- as.numeric(unlist(breaks_num))
76+
if (anyNA(breaks_num)) {
77+
abort('Breaks not formatted correctly for a bin legend. Use `(<lower>, <upper>]` format to indicate bins')
78+
}
79+
all_breaks <- breaks_num[c(1, seq_along(breaks) * 2)]
80+
limits <- all_breaks[c(1, length(all_breaks))]
81+
breaks <- all_breaks[-c(1, length(all_breaks))]
82+
}
6483
ticks <- new_data_frame(setNames(list(scale$map(breaks)), aesthetic %||% scale$aesthetics[1]))
6584
ticks$.value <- seq_along(breaks) - 0.5
6685
ticks$.label <- scale$get_labels(breaks)
6786
guide$nbin <- length(breaks) + 1
6887
guide$key <- ticks
6988
guide$bar <- new_data_frame(list(colour = scale$map(bin_at), value = seq_along(bin_at) - 1), n = length(bin_at))
89+
7090
if (guide$reverse) {
7191
guide$key <- guide$key[nrow(guide$key):1, ]
7292
guide$bar <- guide$bar[nrow(guide$bar):1, ]
7393
}
7494
guide$hash <- with(guide, digest::digest(list(title, key$.label, bar, name)))
7595
} else {
7696
guide <- NextMethod()
97+
limits <- scale$get_limits()
7798
}
7899
if (guide$show.limits %||% scale$show.limits %||% FALSE) {
79100
edges <- rescale(c(0, 1), to = guide$bar$value[c(1, nrow(guide$bar))], from = c(0.5, guide$nbin - 0.5) / guide$nbin)
80-
limits <- scale$get_limits()
101+
if (guide$reverse) edges <- rev(edges)
81102
guide$key <- guide$key[c(NA, seq_len(nrow(guide$key)), NA), , drop = FALSE]
82103
guide$key$.value[c(1, nrow(guide$key))] <- edges
83104
guide$key$.label[c(1, nrow(guide$key))] <- scale$get_labels(limits)

R/scale-.r

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,6 @@ ScaleDiscrete <- ggproto("ScaleDiscrete", Scale,
848848
}
849849

850850
if (is.waive(self$labels)) {
851-
breaks <- self$get_breaks()
852851
if (is.numeric(breaks)) {
853852
# Only format numbers, because on Windows, format messes up encoding
854853
format(breaks, justify = "none")

R/stat-contour.r

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ StatContourFilled <- ggproto("StatContourFilled", Stat,
108108
names(isobands) <- pretty_isoband_levels(names(isobands))
109109
path_df <- iso_to_polygon(isobands, data$group[1])
110110

111-
path_df$level <- factor(path_df$level, levels = names(isobands))
111+
path_df$level <- ordered(path_df$level, levels = names(isobands))
112112

113113
path_df
114114
}

man/guide_bins.Rd

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

man/guide_coloursteps.Rd

Lines changed: 14 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)