You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: vignettes/programming-with-ggplot2.Rmd
+74-70Lines changed: 74 additions & 70 deletions
Original file line number
Diff line number
Diff line change
@@ -19,31 +19,32 @@ This vignette is intended for package developers who use __ggplot2__ functions w
19
19
As with any function from another package, you will have to list __ggplot2__ in your `DESCRIPTION` under `Imports` or `Suggests` and refer to its functions using `::` (e.g., `ggplot2::function_name`).
If you use __ggplot2__ functions frequently, you may wish to import one or more functions from __ggplot2__ into your `NAMESPACE`. If you use __roxygen2__, you can include `#' @importFrom ggplot2 <one or more function names>`. in any roxygen comment block (this will not work for datasets). If you do this, you will need to put __ggplot2__ in your `Imports` rather than your `Suggests`.
If you use infix operators from __ggplot2__ like `%+replace%` and you want to keep __ggplot2__ in `Suggests`, you can assign the operator within the function before it is used.
@@ -57,7 +58,8 @@ theme_custom <- function(...) {
57
58
```
58
59
59
60
```{r, include=FALSE}
60
-
plot_mpg_classes() + theme_custom()
61
+
# make sure this function runs!
62
+
mpg_drv_summary() + theme_custom()
61
63
```
62
64
63
65
If you have __ggplot2__ in your `Imports` anyway, it is much easier to import the infix into your namespace.
Even if you use many __ggplot2__ functions in your package, it is unwise to use __ggplot2__ in `Depends` or import the entire package into your `NAMESPACE`. Using __ggplot2__ in `Depends` will attach __ggplot2__ when your package is attached, which includes when your package is tested. This makes it difficult to ensure that others can use the functions in your package without attaching it (i.e., using `::`). Similarly, importing all 450 of __ggplot2__'s exported objects into your namespace makes it difficult to separate the responsibility of your package and the responsibility of __ggplot2__, in addition to making it difficult for readers of your code to figure out where the functions are coming from!
79
+
Even if you use many __ggplot2__ functions in your package, it is unwise to use __ggplot2__ in `Depends` or import the entire package into your `NAMESPACE`. Using __ggplot2__ in `Depends` will attach __ggplot2__ when your package is attached, which includes when your package is tested. This makes it difficult to ensure that others can use the functions in your package without attaching it (i.e., using `::`). Similarly, importing all 450 of __ggplot2__'s exported objects into your namespace makes it difficult to separate the responsibility of your package and the responsibility of __ggplot2__, in addition to making it difficult for readers of your code to figure out where functions are coming from!
To create any graphic using __ggplot2__ you will probably need to use `aes()` at least once. If your graphic uses facets, you will need to use `vars()`. Both of these functions are intended to refer to variables that exist in the data, so the CMD check will give you a warning if you try to use either inside a function.
84
-
85
-
There are two situations in which you might use these. The first is when you already know the mapping in advance. In this case, you should use the `.data` pronoun from __rlang__:
83
+
To create any graphic using __ggplot2__ you will probably need to use `aes()` at least once. If your graphic uses facets, you might be using `vars()` or formula notation to refer to columns in the data. Both of these functions use non-standard evaluation, so if you try to use them in a function within a package they will result in a CMD check note:
mpg_drv_summary: no visible binding for global variable ‘drv’
96
+
Undefined global functions or variables:
97
+
drv
96
98
```
97
99
98
-
If the user specifies a specific part of the mapping (like the variable from a data frame), you can either have this specified as a column name (e.g., `column = "class"`) or using the same kind of non-standard evaluation used by `aes()` and `vars()` (e.g., `column = class`). In the first case, use `rlang::sym()` to create an unevaluated expression with the name, then use `!!` to evaluate it within `aes()`.
100
+
If you already know the mapping in advance (like the above example) you should use the `.data` pronoun from __rlang__:
99
101
100
102
```{r}
101
-
#' @importFrom rlang !! sym
102
-
col_summary <- function(data, col) {
103
+
#' @importFrom rlang .data
104
+
mpg_drv_summary <- function() {
103
105
ggplot(mpg) +
104
-
geom_bar(aes(x = !!sym(col))) +
106
+
geom_bar(aes(x = .data$drv)) +
105
107
coord_flip()
106
108
}
107
-
108
-
col_summary(mpg, "class")
109
109
```
110
110
111
-
112
-
To use the same kind of non-standard evaluation that `aes()` uses, use `enquo()` to capture the user input, and `!!` pass the (unevaluated) user input in to `aes()`:
111
+
If the user specifies a part of the mapping, you can either have this specified as a column name (e.g., `col = "drv"`) or using the same kind of non-standard evaluation used by `aes()` and `vars()` (e.g., `col = drv`). In the first case, use `.data[[col]]`:
- Use `aes_()` or `aes_string()`: These functions are deprecated and at some point will be removed
124
+
To use the same kind of non-standard evaluation that `aes()` uses, use `{{ col }}` to pass the unevaluated expression the user typed in `col` to `aes()`.
131
125
132
-
## Common tasks + best practices
126
+
<!-- this uses development rlang, which is not yet released -->
133
127
134
-
### Creating a theme function
135
-
136
-
On themes: It's always good practice to start with an existing theme (e.g. `theme_grey()`) and then `%+replace%` the elements that should be changed. This is the right strategy even if seemingly all elements are replaced.
137
-
138
-
Call it `theme_*()`.
139
-
140
-
Examples: __cowplot__, __ggthemes__
141
-
142
-
```{r}
143
-
theme_col_summary <- function(...) {
144
-
theme_grey(...) %+replace%
145
-
theme(
146
-
panel.border = element_rect(size = 1, fill = NA),
147
-
panel.background = element_blank(),
148
-
panel.grid = element_line(colour = "grey80")
149
-
)
128
+
```{r, eval=FALSE}
129
+
col_summary <- function(data, col) {
130
+
ggplot(mpg) +
131
+
geom_bar(aes(x = {{ col }})) +
132
+
coord_flip()
150
133
}
151
134
152
-
col_summary(mpg, class) +
153
-
theme_col_summary()
135
+
col_summary(mpg, drv)
154
136
```
155
137
156
-
- Don't calculate anything during the build phase (like theme defaults). Instead, make a getter and a setter
138
+
To summarise, if you know the mapping or facet specification in advance, use `aes(.data$col)` or `vars(.data$col)`. If you have the column name as a character scalar, use `aes(.data[[col]]` or `vars(.data[[col]])`. If you would like your function to look and feel like `aes()` and `vars()`, use `aes({{ col }})` or `vars({{ col }})`.
139
+
140
+
You will see a lot of other ways to do this in the wild, but the syntax we use here is the only one we can guarantee will work in the future! In particular, don't use `aes_()` or `aes_string()`, as they are deprecated and may be removed in a future version. Finally, don't skip the step of creating a data frame and a mapping to pass in to `ggplot()` or its layers! You will see other ways of doing this in the wild, but these rely on undocumented behaviour and fail in unexpected ways.
141
+
142
+
## Best practices for common tasks
157
143
158
144
### Using ggplot2 to visualize an object
159
145
@@ -223,6 +209,36 @@ If you don't use __ggplot2__ for your visualizations but would like to implement
223
209
224
210
Don't implement an S3 generic like `plot()`, or `autoplot()` if you don't have any control over the S3 object (it makes it hard for the package developer who does have control over the S3 to change the underlying data structure).
225
211
212
+
### Creating a new theme
213
+
214
+
When creating a new theme, it's always good practice to start with an existing theme (e.g. `theme_grey()`) and then `%+replace%` the elements that should be changed. This is the right strategy even if seemingly all elements are replaced, as not doing so makes it difficult for us to improve themes by adding new elements. There are many excellent examples of themes in the __ggthemes__ package.
215
+
216
+
```{r}
217
+
#' @importFrom ggplot2 %+replace%
218
+
theme_custom <- function(...) {
219
+
theme_grey(...) %+replace%
220
+
theme(
221
+
panel.border = element_rect(size = 1, fill = NA),
222
+
panel.background = element_blank(),
223
+
panel.grid = element_line(colour = "grey80")
224
+
)
225
+
}
226
+
227
+
mpg_drv_summary() + theme_custom()
228
+
```
229
+
230
+
It is important that the theme be calculated after the package is loaded. If not, the theme object is stored in the compiled bytecode of the built package, which may or may not align with you installed version of __ggplot2__! If your package has a default theme for its visualizations, the correct way to load it is to have a function that returns the default theme:
231
+
232
+
```{r}
233
+
default_theme <- function() {
234
+
theme_custom()
235
+
}
236
+
237
+
mpg_drv_summary2 <- function() {
238
+
mpg_drv_summary() + default_theme()
239
+
}
240
+
```
241
+
226
242
### Testing ggplot2 output
227
243
228
244
We suggest testing the output of ggplot2 in using the __vdiffr__ package, which is a tool to manage visual test cases (this is how we test __ggplot2__). If changes in __ggplot2__ or your code introduce a change in the visual output of a ggplot, tests will fail when you run them locally or on Travis. These tests do not run on CRAN
@@ -238,15 +254,3 @@ test_that("output of ggplot() is stable", {
238
254
```
239
255
240
256
Don't use `expect_reference()`, or test the internal representation of a ggplot object (unless your package created some part of the internal representation).
241
-
242
-
## Imports or Suggests
243
-
244
-
In documentation (examples, vignettes) make sure you put __ggplot2__ in `Suggests`.
245
-
246
-
If your package doesn't really use ggplot2 but would like to provide compatibility for other ggplot2 users, use Suggests (you may have to use some `.onLoad()` tricks to register S3). Can't find a good example with ggplot2, but __sf__ does this for __dplyr__.
247
-
248
-
If your package uses ggplot2 to visualize the data structures used by your package (e.g., __rstan__)
249
-
250
-
- Please don't include __ggplot2__ in the Imports when you only use it in your package documentation (i.e., examples and/or vignettes).
251
-
252
-
- If you include __ggplot2__ in Suggests, you may have to include some other packages, since the dependencies of
0 commit comments