1
- # ' @section Positions:
1
+ # ' Positions
2
2
# '
3
+ # ' @description
3
4
# ' All `position_*()` functions (like `position_dodge()`) return a
4
5
# ' `Position*` object (like `PositionDodge`). The `Position*`
5
6
# ' object is responsible for adjusting the position of overlapping geoms.
6
7
# '
8
+ # ' @details
7
9
# ' The way that the `position_*` functions work is slightly different from
8
10
# ' the `geom_*` and `stat_*` functions, because a `position_*`
9
11
# ' function actually "instantiates" the `Position*` object by creating a
10
12
# ' descendant, and returns that.
11
13
# '
12
- # ' Each of the `Position*` objects is a [ggproto()] object,
13
- # ' descended from the top-level `Position`, and each implements the
14
- # ' following methods:
14
+ # ' To create a new type of Position object, you typically will want to override
15
+ # ' one or more of the following:
15
16
# '
16
- # ' - `compute_layer(self, data, params, panel)` is called once
17
- # ' per layer. `panel` is currently an internal data structure, so
18
- # ' this method should not be overridden .
17
+ # ' * The `required_aes` and `default_aes` fields.
18
+ # ' * The `setup_params()` and `setup_data()` methods.
19
+ # ' * One of the `compute_layer()` or `compute_panel()` methods .
19
20
# '
20
- # ' - `compute_panel(self, data, params, scales)` is called once per
21
- # ' panel and should return a modified data frame.
21
+ # ' @section Convention:
22
22
# '
23
- # ' `data` is a data frame containing the variables named according
24
- # ' to the aesthetics that they're mapped to. `scales` is a list
25
- # ' containing the `x` and `y` scales. There functions are called
26
- # ' before the facets are trained, so they are global scales, not local
27
- # ' to the individual panels. `params` contains the parameters returned by
28
- # ' `setup_params()`.
29
- # ' - `setup_params(data, params)`: called once for each layer.
30
- # ' Used to setup defaults that need to complete dataset, and to inform
31
- # ' the user of important choices. Should return list of parameters.
32
- # ' - `setup_data(data, params)`: called once for each layer,
33
- # ' after `setup_params()`. Should return modified `data`.
34
- # ' Default checks that required aesthetics are present.
23
+ # ' The object name that a new class is assigned to is typically the same as the
24
+ # ' class name. Position class name are in UpperCamelCase and start with the
25
+ # ' `Position*` prefix, like `PositionNew`.
35
26
# '
36
- # ' And the following fields
37
- # ' - `required_aes`: a character vector giving the aesthetics
38
- # ' that must be present for this position adjustment to work.
27
+ # ' A constructor functions is usually paired with a Position class. The
28
+ # ' constructor copies the position class and populates parameters. The
29
+ # ' constructor function name is formatted by taking the Position class name and
30
+ # ' formatting it with snake_case, so that `PositionNew` becomes `position_new()`.
39
31
# '
40
- # ' See also the `r link_book("new positions section", "extensions#new-positions")`
41
- # '
42
- # ' @rdname ggplot2-ggproto
43
- # ' @format NULL
44
- # ' @usage NULL
45
32
# ' @export
46
- Position <- ggproto(" Position" ,
33
+ # ' @format NULL
34
+ # ' @usage
35
+ # ' # Creating new subclass
36
+ # ' PositionNew <- ggproto("PositionNew", Position, ...)
37
+ # '
38
+ # ' # Usage in the `layer()` function
39
+ # ' layer(position = PositionNew)
40
+ # ' @seealso The `r link_book("new positions section", "extensions#new-positions")`
41
+ # ' @examples
42
+ # ' # Extending the class
43
+ # ' PositionRank <- ggproto(
44
+ # ' "PositionRank", Position,
45
+ # ' # Fields
46
+ # ' required_aes = c("x", "y"),
47
+ # ' # Methods
48
+ # ' setup_params = function(self, data) list(width = self$width),
49
+ # ' compute_panel = function(data, params, scales) {
50
+ # ' width <- params$width %||% (resolution(data$x, zero = FALSE, TRUE) * 0.4)
51
+ # ' rank <- ave(data$y, data$group, FUN = rank)
52
+ # ' rank <- scales::rescale(rank, to = c(-width, width) / 2)
53
+ # ' data$x <- data$x + rank
54
+ # ' data
55
+ # ' }
56
+ # ' )
57
+ # '
58
+ # ' # Building a constructor
59
+ # ' position_rank <- function(width = NULL) {
60
+ # ' ggproto(NULL, PositionRank, width = width)
61
+ # ' }
62
+ # '
63
+ # ' # Use new position in plot
64
+ # ' ggplot(mpg, aes(drv, displ)) +
65
+ # ' geom_point(position = position_rank(width = 0.5))
66
+ Position <- ggproto(
67
+ " Position" ,
68
+
69
+ # Fields ------------------------------------------------------------------
70
+
71
+ # ' @field required_aes A character vector naming aesthetics that are necessary
72
+ # ' to compute the position adjustment.
47
73
required_aes = character (),
48
74
75
+ # ' @field default_aes A [mapping][aes()] of default values for aesthetics.
49
76
default_aes = aes(),
50
77
78
+ # Methods -----------------------------------------------------------------
79
+
80
+ # # compute_position -------------------------------------------------------
81
+
82
+ # ' @field use_defaults
83
+ # ' **Description**
84
+ # '
85
+ # ' A function method for completing the layer data by filling in default
86
+ # ' position aesthetics that are not present. These can come from two sources:
87
+ # ' either from the layer parameters as static, unmapped aesthetics or from
88
+ # ' the `default_aes` field.
89
+ # '
90
+ # ' **Usage**
91
+ # ' ```r
92
+ # ' Position$use_defaults(data, params)
93
+ # ' ```
94
+ # ' **Arguments**
95
+ # ' \describe{
96
+ # ' \item{`data`}{A data frame of the layer's data}
97
+ # ' \item{`params`}{A list of fixed aesthetic parameters}
98
+ # ' }
99
+ # '
100
+ # ' **Value**
101
+ # '
102
+ # ' A data frame with completed layer data
103
+ use_defaults = function (self , data , params = list ()) {
104
+
105
+ aes <- self $ aesthetics()
106
+ defaults <- self $ default_aes
107
+
108
+ params <- params [intersect(names(params ), aes )]
109
+ params <- params [setdiff(names(params ), names(data ))]
110
+ defaults <- defaults [setdiff(names(defaults ), c(names(params ), names(data )))]
111
+
112
+ if ((length(params ) + length(defaults )) < 1 ) {
113
+ return (data )
114
+ }
115
+
116
+ new <- compact(lapply(defaults , eval_tidy , data = data ))
117
+ new [names(params )] <- params
118
+ check_aesthetics(new , nrow(data ))
119
+
120
+ data [names(new )] <- new
121
+ data
122
+
123
+ },
124
+
125
+ # ' @field setup_params
126
+ # ' **Description**
127
+ # '
128
+ # ' A function method for modifying or checking the parameters based on the
129
+ # ' data. The default method returns an empty list.
130
+ # '
131
+ # ' **Usage**
132
+ # ' ```r
133
+ # ' Position$setup_params(data)
134
+ # ' ```
135
+ # ' **Arguments**
136
+ # ' \describe{
137
+ # ' \item{`data`}{A data frame with the layer's data.}
138
+ # ' }
139
+ # '
140
+ # ' **Value**
141
+ # '
142
+ # ' A list of parameters
51
143
setup_params = function (self , data ) {
52
144
list ()
53
145
},
54
146
147
+ # ' @field setup_data
148
+ # ' **Description**
149
+ # '
150
+ # ' A function method for modifying or checking the data. The default method
151
+ # ' checks for the presence of required aesthetics.
152
+ # '
153
+ # ' **Usage**
154
+ # ' ```r
155
+ # ' Position$setup_data(data, params)
156
+ # ' ```
157
+ # ' **Arguments**
158
+ # ' \describe{
159
+ # ' \item{`data`}{A data frame with the layer's data.}
160
+ # ' \item{`params`}{A list of parameters coming from the `setup_params()`
161
+ # ' method}
162
+ # ' }
163
+ # '
164
+ # ' **Value**
165
+ # '
166
+ # ' A data frame with layer data
55
167
setup_data = function (self , data , params ) {
56
168
check_required_aesthetics(self $ required_aes , names(data ), snake_class(self ))
57
169
data
58
170
},
59
171
172
+ # ' @field compute_layer
173
+ # ' **Description**
174
+ # '
175
+ # ' A function method orchestrating the position adjust of the entire layer.
176
+ # ' The default method splits the data and passes on adjustment tasks to the
177
+ # ' panel-level `compute_panel()`. In addition, it finds the correct scales
178
+ # ' in the layout object to pass to the panel computation.
179
+ # '
180
+ # ' **Usage**
181
+ # ' ```r
182
+ # ' Position$compute_layer(data, params, layout)
183
+ # ' ```
184
+ # ' **Arguments**
185
+ # ' \describe{
186
+ # ' \item{`data`}{A data frame with the layer's data.}
187
+ # ' \item{`params`}{A list of parameters coming from the `setup_params()`
188
+ # ' method}
189
+ # ' \item{`layout`}{A `<Layout>` ggproto object.}
190
+ # ' }
191
+ # '
192
+ # ' **Value**
193
+ # '
194
+ # ' A data frame with layer data
60
195
compute_layer = function (self , data , params , layout ) {
61
196
dapply(data , " PANEL" , function (data ) {
62
197
if (empty(data )) return (data_frame0())
@@ -66,38 +201,54 @@ Position <- ggproto("Position",
66
201
})
67
202
},
68
203
204
+ # ' @field compute_panel
205
+ # ' **Description**
206
+ # '
207
+ # ' A function method executing the position adjustment at the panel level.
208
+ # ' The default method is not implemented.
209
+ # '
210
+ # ' **Usage**
211
+ # ' ```r
212
+ # ' Position$compute_panel(data, params, scales)
213
+ # ' ```
214
+ # ' **Arguments**
215
+ # ' \describe{
216
+ # ' \item{`data`}{A data frame with the layer's data.}
217
+ # ' \item{`params`}{A list of parameters coming from the `setup_params()`
218
+ # ' method}
219
+ # ' \item{`scales`}{A list of pre-trained `x` and `y` scales. Note that the
220
+ # ' position scales are not finalised at this point and reflect the initial
221
+ # ' data range before computing stats.}
222
+ # ' }
223
+ # '
224
+ # ' **Value**
225
+ # '
226
+ # ' A data frame with layer data
69
227
compute_panel = function (self , data , params , scales ) {
70
228
cli :: cli_abort(" Not implemented." )
71
229
},
72
230
231
+ # # Utilities ---------------------------------------------------------------
232
+
233
+ # ' @field aesthetics
234
+ # ' **Description**
235
+ # '
236
+ # ' A function method for listing out custom position aesthetics for this
237
+ # ' position adjustment.
238
+ # '
239
+ # ' **Usage**
240
+ # ' ```r
241
+ # ' Position$aesthetics()
242
+ # ' ```
243
+ # ' **Value**
244
+ # '
245
+ # ' A character vector of aesthetic names.
73
246
aesthetics = function (self ) {
74
247
required_aes <- self $ required_aes
75
248
if (! is.null(required_aes )) {
76
249
required_aes <- unlist(strsplit(self $ required_aes , " |" , fixed = TRUE ))
77
250
}
78
251
c(union(required_aes , names(self $ default_aes )))
79
- },
80
-
81
- use_defaults = function (self , data , params = list ()) {
82
-
83
- aes <- self $ aesthetics()
84
- defaults <- self $ default_aes
85
-
86
- params <- params [intersect(names(params ), aes )]
87
- params <- params [setdiff(names(params ), names(data ))]
88
- defaults <- defaults [setdiff(names(defaults ), c(names(params ), names(data )))]
89
-
90
- if ((length(params ) + length(defaults )) < 1 ) {
91
- return (data )
92
- }
93
-
94
- new <- compact(lapply(defaults , eval_tidy , data = data ))
95
- new [names(params )] <- params
96
- check_aesthetics(new , nrow(data ))
97
-
98
- data [names(new )] <- new
99
- data
100
-
101
252
}
102
253
)
103
254
0 commit comments