-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrescale.R
195 lines (166 loc) · 5.75 KB
/
rescale.R
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
#' Rescale center
#'
#' @description
#' Re-centres a scale to have a defined centre / midpoint. This is the rdeck equivalent of
#' [scales::rescale_mid()].
#'
#' centring an rdeck scale creates a new scale with the output palette or range centred at `center`.
#' This is similar to creating a diverging scale; the key difference is that the output palette or range
#' remains linear (with respect to the breaks) and is truncated on the side that is closest to `center`.
#' This is useful in creating _difference_ layers, where the output palette or range represents distance
#' from the centre.
#'
#' # Centring vs Diverging
#' The plot below shows how [rescale_center()] and [rescale_diverge()] distort the scale output. The input
#' scale in this case is `power_scale(limits = -36:4)`; this scale is centred and diverged at 0.
#'
#' The plot on the left shows the mapping between the input `-36:4` (x axis) and output `0:1` (y axis). The
#' plot on the right is a linear representation of the left and is the space that rdeck works in. The input
#' `-36:4` transformed with `power_trans()` and rescaled to `0:1`. This plot has been included because it's
#' (hopefully) easier to understand.
#'
#' In the unaltered scale, we see that 0 is mapped to 0.75 in the output, which would be the colour at 0.75
#' on a colour ramp (e.g. `scales::colour_ramp(viridis::viridis(256))(0.75)`).
#'
#' When applying [rescale_center()] we see that gradient of function has become y = 2/3x in the linear
#' scale, which is `2/3 * scales::rescale(trans$transform(x))` for our data. For [rescale_diverge()]
#' we see a piecewise scale with the break at `center`; both sides of `center` have a different gradient
#' (y = 2/3x and y = 2x - 1) and the full range of y is used.
#'
#' The colour ramp plot shows the effect rescaling has on a colour palette (in this case viridis).
#'
#' ![](rescale.png)
#'
#' @note
#' Category and identity scales aren't supported.
#'
#' @examples
#' # create a sqrt scale that is centered at 0
#' sqrt_centered <- rescale_center(
#' scale_color_power(col, limits = -36:4),
#' center = 0
#' )
#'
#' # create a discrete symlog scale that is centered at 5
#' symlog_centered <- rescale_center(
#' scale_color_threshold(col, limits = -100:100, breaks = breaks_symlog()),
#' center = 5
#' )
#'
#' @param scale <`scale`> a scale object
#' @param center <`number`> the center of the scale input
#' @family scales
#' @export
rescale_center <- function(scale, center = 0) {
UseMethod("rescale_center")
}
#' @export
rescale_center.scale_color <- function(scale, center = 0) {
get_palette <- scale$get_palette
scale$get_palette <- function(x) {
xmid <- rescale_breaks(scale, center)
ramp <- scales::rescale_mid(x, mid = xmid)
get_palette(ramp)
}
scale
}
#' @export
rescale_center.scale_numeric <- function(scale, center = 0) {
get_range <- scale$get_range
scale$get_range <- function(x) {
xmid <- rescale_breaks(scale, center)
ramp <- scales::rescale_mid(x, mid = xmid)
get_range(ramp)
}
scale
}
rescale_center_not_supported <- function(scale, center) {
rescale_not_supported("rescale_center()", scale$scale_type)
}
#' @export
rescale_center.scale_numeric_identity <- rescale_center_not_supported
#' @export
rescale_center.scale_color_category <- rescale_center_not_supported
#' @export
rescale_center.scale_numeric_category <- rescale_center_not_supported
#' Rescale diverge
#'
#' @description
#' Creates a diverging scale with defined centre / midpoint. Similar to [rescale_center()], key difference is
#' the output palette / range is piecewise linear (with respect to breaks) and the entire output range is
#' always used.
#'
#' @examples
#' # create a diverging linear scale at 0
#' linear_diverged <- rescale_diverge(
#' scale_color_linear(col, limits = -5:10),
#' center = 0
#' )
#'
#' # create a diverging log scale at 10
#' log_diverged <- rescale_diverge(
#' scale_log(col, limits = 1:1000),
#' center = 10
#' )
#' @inherit rescale_center
#' @family scales
#' @export
#' @export
rescale_diverge <- function(scale, center = 0) {
UseMethod("rescale_diverge")
}
#' @export
rescale_diverge.scale_color <- function(scale, center = 0) {
get_palette <- scale$get_palette
scale$get_palette <- function(x) {
xmid <- rescale_breaks(scale, center)
ramp <- rescale_piecewise(x, xmid)
get_palette(ramp)
}
scale
}
#' @export
rescale_diverge.scale_numeric <- function(scale, center = 0) {
get_range <- scale$get_range
scale$get_range <- function(x) {
xmid <- rescale_breaks(scale, center)
ramp <- rescale_piecewise(x, xmid)
get_range(ramp)
}
scale
}
rescale_diverge_not_supported <- function(scale, center) {
rescale_not_supported("rescale_diverge()", scale$scale_type)
}
#' @export
rescale_diverge.scale_numeric_identity <- rescale_diverge_not_supported
#' @export
rescale_diverge.scale_color_category <- rescale_diverge_not_supported
#' @export
rescale_diverge.scale_numeric_category <- rescale_diverge_not_supported
rescale_breaks <- function(scale, x) {
range <- (scale$limits %||% scale$data)$range
# use transform if available
trans <- attr(scale$get_breaks, "trans")
if (!is.null(trans)) {
scales::rescale(trans$transform(x), from = trans$transform(range))
} else {
# approximate function from breaks
breaks <- scale$get_breaks(range)
rescale <- stats::splinefun(breaks, seq.int(0, 1, length.out = length(breaks)))
rescale(x)
}
}
rescale_piecewise <- function(x, mid) {
out <- x
out[x == mid] <- 0.5
out[x < mid] <- scales::rescale(x[x < mid], c(0, 0.5), c(0, mid))
out[x > mid] <- scales::rescale(x[x > mid], c(0.5, 1), c(mid, 1))
out
}
rescale_not_supported <- function(rescale_fn, scale_type) {
rlang::abort(
paste(rescale_fn, "doesn't handle", scale_type, "scales."),
class = "rdeck_error"
)
}