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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
#' Position scales for discrete data
#'
#' `scale_x_discrete()` and `scale_y_discrete()` are used to set the values for
#' discrete x and y scale aesthetics. For simple manipulation of scale labels
#' and limits, you may wish to use [labs()] and [lims()] instead.
#'
#' You can use continuous positions even with a discrete position scale -
#' this allows you (e.g.) to place labels between bars in a bar chart.
#' Continuous positions are numeric values starting at one for the first
#' level, and increasing by one for each level (i.e. the labels are placed
#' at integer positions). This is what allows jittering to work.
#'
#' @inheritDotParams discrete_scale -scale_name
#' @inheritParams discrete_scale
#' @rdname scale_discrete
#' @family position scales
#' @seealso
#' The [position documentation][aes_position].
#'
#' The `r link_book("discrete position scales section", "scales-position#sec-discrete-position")`
#' @export
#' @examples
#' ggplot(diamonds, aes(cut)) + geom_bar()
#'
#' \donttest{
#' # The discrete position scale is added automatically whenever you
#' # have a discrete position.
#'
#' (d <- ggplot(subset(diamonds, carat > 1), aes(cut, clarity)) +
#' geom_jitter())
#'
#' d + scale_x_discrete("Cut")
#' d +
#' scale_x_discrete(
#' "Cut",
#' labels = c(
#' "Fair" = "F",
#' "Good" = "G",
#' "Very Good" = "VG",
#' "Perfect" = "P",
#' "Ideal" = "I"
#' )
#' )
#'
#' # Use limits to adjust the which levels (and in what order)
#' # are displayed
#' d + scale_x_discrete(limits = c("Fair","Ideal"))
#'
#' # you can also use the short hand functions xlim and ylim
#' d + xlim("Fair","Ideal", "Good")
#' d + ylim("I1", "IF")
#'
#' # See ?reorder to reorder based on the values of another variable
#' ggplot(mpg, aes(manufacturer, cty)) +
#' geom_point()
#' ggplot(mpg, aes(reorder(manufacturer, cty), cty)) +
#' geom_point()
#' ggplot(mpg, aes(reorder(manufacturer, displ), cty)) +
#' geom_point()
#'
#' # Use abbreviate as a formatter to reduce long names
#' ggplot(mpg, aes(reorder(manufacturer, displ), cty)) +
#' geom_point() +
#' scale_x_discrete(labels = abbreviate)
#' }
scale_x_discrete <- function(name = waiver(), ..., expand = waiver(),
guide = waiver(), position = "bottom") {
sc <- discrete_scale(
aesthetics = c("x", "xmin", "xmax", "xend"), name = name,
palette = identity, ...,
expand = expand, guide = guide, position = position,
super = ScaleDiscretePosition
)
sc$range_c <- ContinuousRange$new()
sc
}
#' @rdname scale_discrete
#' @export
scale_y_discrete <- function(name = waiver(), ..., expand = waiver(),
guide = waiver(), position = "left") {
sc <- discrete_scale(
aesthetics = c("y", "ymin", "ymax", "yend"), name = name,
palette = identity, ...,
expand = expand, guide = guide, position = position,
super = ScaleDiscretePosition
)
sc$range_c <- ContinuousRange$new()
sc
}
# The discrete position scale maintains two separate ranges - one for
# continuous data and one for discrete data. This complicates training and
# mapping, but makes it possible to place objects at non-integer positions,
# as is necessary for jittering etc.
#' @rdname ggplot2-ggproto
#' @format NULL
#' @usage NULL
#' @export
ScaleDiscretePosition <- ggproto("ScaleDiscretePosition", ScaleDiscrete,
train = function(self, x) {
if (is.discrete(x)) {
self$range$train(x, drop = self$drop, na.rm = !self$na.translate)
} else {
self$range_c$train(x)
}
},
get_limits = function(self) {
# if scale contains no information, return the default limit
if (self$is_empty()) {
return(c(0, 1))
}
# if self$limits is not NULL and is a function, apply it to range
if (is.function(self$limits)){
return(self$limits(self$range$range))
}
# self$range$range can be NULL because non-discrete values use self$range_c
self$limits %||% self$range$range %||% integer()
},
is_empty = function(self) {
is.null(self$range$range) && is.null(self$limits) && is.null(self$range_c$range)
},
reset = function(self) {
# Can't reset discrete position scale because no way to recover values
self$range_c$reset()
},
map = function(self, x, limits = self$get_limits()) {
if (is.discrete(x)) {
x <- seq_along(limits)[match(as.character(x), limits)]
}
mapped_discrete(x)
},
rescale = function(self, x, limits = self$get_limits(), range = self$dimension(limits = limits)) {
rescale(self$map(x, limits = limits), from = range)
},
dimension = function(self, expand = expansion(0, 0), limits = self$get_limits()) {
expand_limits_scale(self, expand, limits)
},
clone = function(self) {
new <- ggproto(NULL, self)
new$range <- DiscreteRange$new()
new$range_c <- ContinuousRange$new()
new
}
)
# Can't use vctrs - vctrs is too restrictive for mapped_discrete
new_mapped_discrete <- function(x = double()) {
# Check the storage mode is double but don't error on additional attributes
vec_assert(as.vector(x), double())
class(x) <- c("mapped_discrete", "numeric")
x
}
mapped_discrete <- function(x = double()) {
if (is.null(x)) return(NULL)
new_mapped_discrete(vec_cast(x, double()))
}
is_mapped_discrete <- function(x) inherits(x, "mapped_discrete")
#' @export
c.mapped_discrete <- function(..., recursive = FALSE) {
mapped_discrete(unlist(lapply(list(...), unclass)))
}
#' @export
`[.mapped_discrete` <- function(x, ..., drop = TRUE) {
mapped_discrete(NextMethod())
}
#' @export
`[<-.mapped_discrete` <- function(x, ..., value) {
if (length(value) == 0) {
return(x)
}
value <- as.numeric(unclass(value))
mapped_discrete(NextMethod())
}
#' @export
as.data.frame.mapped_discrete <- function (x, ...) {
as.data.frame.vector(x = unclass(x), ...)
}
#' @export
vec_ptype2.mapped_discrete.mapped_discrete <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_ptype2.mapped_discrete.double <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_ptype2.double.mapped_discrete <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_ptype2.mapped_discrete.integer <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_ptype2.integer.mapped_discrete <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_ptype2.mapped_discrete.character <- function(x, y, ...) character()
#' @export
vec_ptype2.character.mapped_discrete <- function(x, y, ...) character()
#' @export
vec_ptype2.mapped_discrete.factor <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_ptype2.factor.mapped_discrete <- function(x, y, ...) new_mapped_discrete()
#' @export
vec_cast.mapped_discrete.mapped_discrete <- function(x, to, ...) x
#' @export
vec_cast.mapped_discrete.integer <- function(x, to, ...) mapped_discrete(x)
#' @export
vec_cast.integer.mapped_discrete <- function(x, to, ...) as.integer(as.vector(x))
#' @export
vec_cast.mapped_discrete.double <- function(x, to, ...) new_mapped_discrete(x)
#' @export
vec_cast.double.mapped_discrete <- function(x, to, ...) as.vector(x)
#' @export
vec_cast.character.mapped_discrete <- function(x, to, ...) as.character(as.vector(x))
#' @export
vec_cast.mapped_discrete.factor <- function(x, to, ...) mapped_discrete(as.vector(unclass(x)))
#' @export
vec_cast.factor.mapped_discrete <- function(x, to, ...) factor(as.vector(x), ...)
#' @export
vec_cast.mapped_discrete.logical <- function(x, to, ...) mapped_discrete(x)
|