@@ -203,38 +203,151 @@ df.grid <- function(a, b) {
203
203
))
204
204
}
205
205
206
+ # A facets spec is a list of facets. A grid facetting needs two facets
207
+ # while a wrap facetting flattens all dimensions and thus accepts any
208
+ # number of facets.
209
+ #
210
+ # A facets is a list of grouping variables. They are typically
211
+ # supplied as variable names but can be expressions.
212
+ #
213
+ # as_facets() is complex due to historical baggage but its main
214
+ # purpose is to create a facets spec from a formula: a + b ~ c + d
215
+ # creates a facets list with two components, each of which bundles two
216
+ # facetting variables.
217
+
218
+ as_facets_spec <- function (x ) {
219
+ if (inherits(x , " mapping" )) {
220
+ stop(" todo mapping" )
221
+ }
222
+
223
+ # This needs to happen early because we might get a formula.
224
+ # facet_grid() directly converted strings to a formula while
225
+ # facet_wrap() called as.quoted(). Hence this is a little more
226
+ # complicated for backward compatibility.
227
+ if (rlang :: is_string(x )) {
228
+ x <- plyr :: as.quoted(x )
229
+ if (rlang :: is_formula(x [[1 ]])) {
230
+ x <- x [[1 ]]
231
+ }
232
+ }
233
+
234
+ # At this level formulas are coerced to lists of lists for backward
235
+ # compatibility with facet_grid(). The LHS and RHS are treated as
236
+ # distinct facet dimensions and `+` defines multiple facet variables
237
+ # inside each dimension.
238
+ if (rlang :: is_formula(x )) {
239
+ return (f_as_facets_spec(x ))
240
+ }
241
+
242
+ # For backward-compatibility with facet_wrap()
243
+ if (! rlang :: is_bare_list(x )) {
244
+ x <- plyr :: as.quoted(x )
245
+ attributes(x ) <- NULL
246
+ }
247
+
248
+ # If we have a list there are two possibilities. We may already have
249
+ # a proper facet spec structure. Otherwise we coerce each element
250
+ # with plyr::as.quoted() for backward compatibility with facet_grid().
251
+ if (is.list(x )) {
252
+ x <- lapply(x , as_facets )
253
+ }
254
+
255
+ if (sum(vapply(x , length , integer(1 ))) == 0L ) {
256
+ stop(" Must specify at least one variable to facet by" , call. = FALSE )
257
+ }
258
+
259
+ x
260
+ }
261
+
262
+ f_as_facets_spec <- function (f ) {
263
+ lhs <- function (x ) if (length(x ) == 2 ) NULL else x [- 3 ]
264
+ rhs <- function (x ) if (length(x ) == 2 ) x else x [- 2 ]
265
+
266
+ rows <- f_as_facets(lhs(f ))
267
+ cols <- f_as_facets(rhs(f ))
268
+
269
+ if (length(rows ) + length(cols ) == 0 ) {
270
+ stop(" Must specify at least one variable to facet by" , call. = FALSE )
271
+ }
272
+
273
+ if (length(rows )) {
274
+ list (rows , cols )
275
+ } else {
276
+ list (cols )
277
+ }
278
+ }
279
+
280
+
281
+ as_facets <- function (x ) {
282
+ if (is_facets(x )) {
283
+ return (x )
284
+ }
285
+
286
+ if (rlang :: is_formula(x )) {
287
+ # Use different formula method because plyr's does not handle the
288
+ # environment correctly.
289
+ f_as_facets(x )
290
+ } else {
291
+ vars <- plyr :: as.quoted(x )
292
+ vars <- lapply(vars , rlang :: new_quosure , env = globalenv())
293
+ rlang :: quos_auto_name(vars )
294
+ }
295
+ }
296
+ f_as_facets <- function (f ) {
297
+ if (is.null(f )) {
298
+ return (list ())
299
+ }
300
+
301
+ env <- rlang :: f_env(f ) %|| % globalenv()
302
+
303
+ # as.quoted() handles `+` specifications
304
+ vars <- plyr :: as.quoted(f )
305
+
306
+ # `.` in formulas is ignored
307
+ vars <- discard_dots(vars )
308
+
309
+ vars <- lapply(vars , rlang :: new_quosure , env )
310
+ rlang :: quos_auto_name(vars )
311
+ }
312
+ discard_dots <- function (x ) {
313
+ x [! vapply(x , identical , logical (1 ), as.name(" ." ))]
314
+ }
315
+
316
+ is_facets <- function (x ) {
317
+ if (! is.list(x )) {
318
+ return (FALSE )
319
+ }
320
+ if (! length(x )) {
321
+ return (FALSE )
322
+ }
323
+ all(vapply(x , rlang :: is_quosure , logical (1 )))
324
+ }
325
+
326
+
206
327
# When evaluating variables in a facet specification, we evaluate bare
207
328
# variables and expressions slightly differently. Bare variables should
208
329
# always succeed, even if the variable doesn't exist in the data frame:
209
330
# that makes it possible to repeat data across multiple factors. But
210
331
# when evaluating an expression, you want to see any errors. That does
211
332
# mean you can't have background data when faceting by an expression,
212
333
# but that seems like a reasonable tradeoff.
213
- eval_facet_vars <- function (vars , data , env = emptyenv()) {
214
- nms <- names(vars )
215
- out <- list ()
216
-
217
- for (i in seq_along(vars )) {
218
- out [[ nms [[i ]] ]] <- eval_facet_var(vars [[i ]], data , env = env )
219
- }
220
-
221
- tibble :: as_tibble(out )
334
+ eval_facets <- function (facets , data , env = globalenv()) {
335
+ vars <- compact(lapply(facets , eval_facet , data , env = env ))
336
+ tibble :: as_tibble(vars )
222
337
}
338
+ eval_facet <- function (facet , data , env = emptyenv()) {
339
+ if (rlang :: quo_is_symbol(facet )) {
340
+ facet <- as.character(rlang :: quo_get_expr(facet ))
223
341
224
- eval_facet_var <- function (var , data , env = emptyenv()) {
225
- if (is.name(var )) {
226
- var <- as.character(var )
227
- if (var %in% names(data )) {
228
- data [[var ]]
342
+ if (facet %in% names(data )) {
343
+ out <- data [[facet ]]
229
344
} else {
230
- NULL
345
+ out <- NULL
231
346
}
232
- } else if (is.call(var )) {
233
- eval(var , envir = data , enclos = env )
234
- } else {
235
- stop(" Must use either variable name or expression when faceting" ,
236
- call. = FALSE )
347
+ return (out )
237
348
}
349
+
350
+ rlang :: eval_tidy(facet , data , env )
238
351
}
239
352
240
353
layout_null <- function () {
@@ -325,7 +438,7 @@ combine_vars <- function(data, env = emptyenv(), vars = NULL, drop = TRUE) {
325
438
if (length(vars ) == 0 ) return (data.frame ())
326
439
327
440
# For each layer, compute the facet values
328
- values <- compact(plyr :: llply(data , eval_facet_vars , vars = vars , env = env ))
441
+ values <- compact(plyr :: llply(data , eval_facets , facets = vars , env = env ))
329
442
330
443
# Form the base data frame which contains all combinations of faceting
331
444
# variables that appear in the data
0 commit comments