Skip to content

Commit c6b6a39

Browse files
committed
standalone docs for ?Position
1 parent fbee8dd commit c6b6a39

File tree

3 files changed

+415
-103
lines changed

3 files changed

+415
-103
lines changed

R/position-.R

Lines changed: 203 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,197 @@
1-
#' @section Positions:
1+
#' Positions
22
#'
3+
#' @description
34
#' All `position_*()` functions (like `position_dodge()`) return a
45
#' `Position*` object (like `PositionDodge`). The `Position*`
56
#' object is responsible for adjusting the position of overlapping geoms.
67
#'
8+
#' @details
79
#' The way that the `position_*` functions work is slightly different from
810
#' the `geom_*` and `stat_*` functions, because a `position_*`
911
#' function actually "instantiates" the `Position*` object by creating a
1012
#' descendant, and returns that.
1113
#'
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:
1516
#'
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.
1920
#'
20-
#' - `compute_panel(self, data, params, scales)` is called once per
21-
#' panel and should return a modified data frame.
21+
#' @section Convention:
2222
#'
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`.
3526
#'
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()`.
3931
#'
40-
#' See also the `r link_book("new positions section", "extensions#new-positions")`
41-
#'
42-
#' @rdname ggplot2-ggproto
43-
#' @format NULL
44-
#' @usage NULL
4532
#' @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.
4773
required_aes = character(),
4874

75+
#' @field default_aes A [mapping][aes()] of default values for aesthetics.
4976
default_aes = aes(),
5077

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
51143
setup_params = function(self, data) {
52144
list()
53145
},
54146

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
55167
setup_data = function(self, data, params) {
56168
check_required_aesthetics(self$required_aes, names(data), snake_class(self))
57169
data
58170
},
59171

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
60195
compute_layer = function(self, data, params, layout) {
61196
dapply(data, "PANEL", function(data) {
62197
if (empty(data)) return(data_frame0())
@@ -66,38 +201,54 @@ Position <- ggproto("Position",
66201
})
67202
},
68203

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
69227
compute_panel = function(self, data, params, scales) {
70228
cli::cli_abort("Not implemented.")
71229
},
72230

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.
73246
aesthetics = function(self) {
74247
required_aes <- self$required_aes
75248
if (!is.null(required_aes)) {
76249
required_aes <- unlist(strsplit(self$required_aes, "|", fixed = TRUE))
77250
}
78251
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-
101252
}
102253
)
103254

0 commit comments

Comments
 (0)