File: guide-colorsteps.R

package info (click to toggle)
r-cran-ggplot2 3.4.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 8,748 kB
  • sloc: sh: 15; makefile: 5
file content (171 lines) | stat: -rw-r--r-- 6,130 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#' Discretized colourbar guide
#'
#' This guide is version of [guide_colourbar()] for binned colour and fill
#' scales. It shows areas between breaks as a single constant colour instead of
#' the gradient known from the colourbar counterpart.
#'
#' @param even.steps Should the rendered size of the bins be equal, or should
#'   they be proportional to their length in the data space? Defaults to `TRUE`
#' @param show.limits Logical. Should the limits of the scale be shown with
#'   labels and ticks. Default is `NULL` meaning it will take the value from the
#'   scale. This argument is ignored if `labels` is given as a vector of
#'   values. If one or both of the limits is also given in `breaks` it will be
#'   shown irrespective of the value of `show.limits`.
#' @param ticks A logical specifying if tick marks on the colourbar should be
#'   visible.
#' @inheritDotParams guide_colourbar -nbin -raster -ticks -available_aes
#'
#' @inheritSection guide_bins Use with discrete scale
#'
#' @return A guide object
#' @export
#'
#' @family guides
#' @examples
#' df <- expand.grid(X1 = 1:10, X2 = 1:10)
#' df$value <- df$X1 * df$X2
#'
#' p <- ggplot(df, aes(X1, X2)) + geom_tile(aes(fill = value))
#'
#' # Coloursteps guide is the default for binned colour scales
#' p + scale_fill_binned()
#'
#' # By default each bin in the guide is the same size irrespectively of how
#' # their sizes relate in data space
#' p + scale_fill_binned(breaks = c(10, 25, 50))
#'
#' # This can be changed with the `even.steps` argument
#' p + scale_fill_binned(
#'   breaks = c(10, 25, 50),
#'   guide = guide_coloursteps(even.steps = FALSE)
#' )
#'
#' # By default the limits is not shown, but this can be changed
#' p + scale_fill_binned(guide = guide_coloursteps(show.limits = TRUE))
#'
#' # (can also be set in the scale)
#' p + scale_fill_binned(show.limits = TRUE)
#'
guide_coloursteps <- function(even.steps = TRUE, show.limits = NULL, ticks = FALSE, ...) {
  guide <- guide_colourbar(raster = FALSE, ticks = ticks, nbin = 100, ...)
  guide$even.steps <- even.steps
  guide$show.limits <- show.limits
  class(guide) <- c('colorsteps', class(guide))
  guide
}
#' @export
#' @rdname guide_coloursteps
guide_colorsteps <- guide_coloursteps

#' @export
guide_train.colorsteps <- function(guide, scale, aesthetic = NULL) {
  breaks <- scale$get_breaks()
  breaks <- breaks[!is.na(breaks)]
  show_limits <- guide$show.limits %||% scale$show.limits %||% FALSE
  if (show_limits && (is.character(scale$labels) || is.numeric(scale$labels))) {
    cli::cli_warn(c(
      "{.arg show.limits} is ignored when {.arg labels} are given as a character vector",
      "i" = "Either add the limits to {.arg breaks} or provide a function for {.arg labels}"
    ))
    show_limits <- FALSE
  }
  if (guide$even.steps || !is.numeric(breaks)) {
    if (length(breaks) == 0 || all(is.na(breaks))) {
      return()
    }
    if (is.numeric(breaks)) {
      limits <- scale$get_limits()
      if (!is.numeric(scale$breaks)) {
        breaks <- breaks[!breaks %in% limits]
      }
      all_breaks <- unique0(c(limits[1], breaks, limits[2]))
      bin_at <- all_breaks[-1] - diff(all_breaks) / 2
    } else {
      # If the breaks are not numeric it is used with a discrete scale. We check
      # if the breaks follow the allowed format "(<lower>, <upper>]", and if it
      # does we convert it into bin specs
      if (!guide$even.steps) {
        cli::cli_warn("{.code even.steps = FALSE} is not supported when used with a discrete scale")
      }
      bin_at <- breaks
      breaks_num <- as.character(breaks)
      breaks_num <- strsplit(gsub("\\(|\\)|\\[|\\]", "", breaks_num), ",\\s?")
      breaks_num <- as.numeric(unlist(breaks_num))
      if (anyNA(breaks_num)) {
        cli::cli_abort(c(
          "Breaks not formatted correctly for a bin legend.",
          "i" = "Use {.code (<lower>, <upper>]} format to indicate bins"
        ))
      }
      all_breaks <- breaks_num[c(1, seq_along(breaks) * 2)]
      limits <- all_breaks[c(1, length(all_breaks))]
      breaks <- all_breaks[-c(1, length(all_breaks))]
    }
    ticks <- data_frame(
      scale$map(breaks),
      .name_repair = ~ aesthetic %||% scale$aesthetics[1]
    )
    ticks$.value <- seq_along(breaks) - 0.5
    ticks$.label <- scale$get_labels(breaks)
    guide$nbin <- length(breaks) + 1L
    if (breaks[1] %in% limits) {
      ticks$.value <- ticks$.value - 1L
      ticks[[1]][1] <- NA
      guide$nbin <- guide$nbin - 1L
    }
    if (breaks[length(breaks)] %in% limits) {
      ticks[[1]][nrow(ticks)] <- NA
      guide$nbin <- guide$nbin - 1L
    }
    guide$key <- ticks
    guide$bar <- data_frame0(
      colour = scale$map(bin_at),
      value = seq_along(bin_at) - 1,
      .size = length(bin_at)
    )

    if (guide$reverse) {
      guide$key <- guide$key[nrow(guide$key):1, ]
      guide$bar <- guide$bar[nrow(guide$bar):1, ]
    }
    guide$hash <- with(guide, hash(list(title, key$.label, bar, name)))
  } else {
    guide <- NextMethod()
    limits <- scale$get_limits()
  }
  if (show_limits) {
    edges <- rescale(c(0, 1), to = guide$bar$value[c(1, nrow(guide$bar))], from = c(0.5, guide$nbin - 0.5) / guide$nbin)
    if (guide$reverse) edges <- rev(edges)
    guide$key <- guide$key[c(NA, seq_len(nrow(guide$key)), NA), , drop = FALSE]
    guide$key$.value[c(1, nrow(guide$key))] <- edges
    guide$key$.label[c(1, nrow(guide$key))] <- scale$get_labels(limits)
    if (guide$key$.value[1] == guide$key$.value[2]) {
      guide$key <- guide$key[-1,]
    }
    if (guide$key$.value[nrow(guide$key)-1] == guide$key$.value[nrow(guide$key)]) {
      guide$key <- guide$key[-nrow(guide$key),]
    }
  }
  guide
}

#' Calculate the default hjust and vjust settings depending on legend
#' direction and position.
#'
#' @noRd
label_just_defaults.colorbar <- function(direction, position) {
  if (direction == "horizontal") {
    switch(
      position,
      "top" = list(hjust = 0.5, vjust = 0),
      list(hjust = 0.5, vjust = 1)
    )
  }
  else {
    switch(
      position,
      "left" = list(hjust = 1, vjust = 0.5),
      list(hjust = 0, vjust = 0.5)
    )
  }
}