Skip to content

Commit 6ea6ead

Browse files
authored
as.list() methods for S7 classes (#6699)
* as.list for theme elements * move methods * as.list/convert for other classes * reformat * list to theme element conversion * add tests * add news bullet
1 parent 90e6c6b commit 6ea6ead

File tree

7 files changed

+97
-13
lines changed

7 files changed

+97
-13
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* Implemented `as.list()` and `S7::convert()` methods for lists and classes in ggplot2
4+
(@teunbrand, #6695)
35
* The default linetype in `geom_sf()` is derived from `geom_polygon()` for
46
polygons and from `geom_line()` for (multi)linestrings (@teunbrand, #6543).
57
* Using infinite `radius` aesthetic in `geom_spoke()` now throws a warning

R/all-classes.R

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,34 @@ class_ggplot_built <- S7::new_class(
412412
)
413413
}
414414
)
415+
416+
# Methods -----------------------------------------------------------------
417+
418+
#' @importFrom S7 convert
419+
# S7 currently attaches the S3 method to the calling environment which gives `ggplot2:::as.list`
420+
# Wrap in `local()` to provide a temp environment which throws away the attachment
421+
local({
422+
list_classes <- class_mapping | class_theme | class_labels
423+
prop_classes <- class_ggplot | class_ggplot_built
424+
425+
S7::method(convert, list(from = prop_classes, to = S7::class_list)) <-
426+
function(from, to, ...) S7::props(from)
427+
428+
S7::method(convert, list(from = list_classes, to = S7::class_list)) <-
429+
function(from, to, ...) S7::S7_data(from)
430+
431+
# We're not using union classes here because of S7#510
432+
S7::method(as.list, class_gg) <-
433+
S7::method(as.list, class_mapping) <-
434+
S7::method(as.list, class_theme) <-
435+
S7::method(as.list, class_labels) <-
436+
function(x, ...) convert(x, S7::class_list)
437+
438+
S7::method(convert, list(from = S7::class_list, to = prop_classes)) <-
439+
function(from, to, ...) inject(to(!!!from))
440+
441+
S7::method(convert, list(from = S7::class_list, to = list_classes)) <-
442+
function(from, to, ...) to(from)
443+
})
444+
445+

R/plot.R

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,3 @@ local({
267267

268268
#' @export
269269
`[[<-.ggplot2::gg` <- `$<-.ggplot2::gg`
270-
271-
#' @importFrom S7 convert
272-
# S7 currently attaches the S3 method to the calling environment which gives `ggplot2:::as.list`
273-
# Wrap in `local()` to provide a temp environment which throws away the attachment
274-
local({
275-
S7::method(convert, list(from = class_ggplot, to = S7::class_list)) <-
276-
function(from, to) {
277-
S7::props(from)
278-
}
279-
280-
S7::method(as.list, class_ggplot) <-
281-
function(x, ...) convert(x, S7::class_list)
282-
})

R/theme-elements.R

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,29 @@ local({
408408
deprecate("4.1.0", I("`<ggplot2::element>[[i]]`"), I("`S7::prop(<ggplot2::element>, i)`"))
409409
`[[`(S7::props(x), i)
410410
}
411+
S7::method(as.list, element) <- function(x, ...) {
412+
S7::convert(x, S7::class_list)
413+
}
414+
S7::method(convert, list(from = element, to = S7::class_list)) <-
415+
function(from, to, ...) S7::props(from)
416+
S7::method(
417+
convert,
418+
list(
419+
from = S7::class_list,
420+
to = element_geom | element_line | element_point |
421+
element_polygon | element_rect | element_text | element_blank
422+
)
423+
) <- function(from, to, ...) {
424+
extra <- setdiff(names(from), fn_fmls_names(to))
425+
if (length(extra) > 0) {
426+
cli::cli_warn(
427+
"Unknown {cli::qty(extra)} argument{?s} to {.fn {to@name}}: \\
428+
{.and {.arg {extra}}}."
429+
)
430+
from <- from[setdiff(names(from), extra)]
431+
}
432+
inject(to(!!!from))
433+
}
411434
})
412435

413436
# Element setter methods

tests/testthat/_snaps/theme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,7 @@
5454
The `options('ggplot2.discrete.colour')` setting is incompatible with the `palette.colour.discrete` theme setting.
5555
i You can set `options(ggplot2.discrete.colour = NULL)`.
5656

57+
# theme element conversion to lists works
58+
59+
Unknown arguments to `element_text()`: `italic`, `fontweight`, and `fontwidth`.
60+

tests/testthat/test-theme.R

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,22 @@ test_that("theme() warns about conflicting palette options", {
428428
)
429429
})
430430

431+
test_that("theme element conversion to lists works", {
432+
433+
x <- element_rect(colour = "red")
434+
expect_type(x <- as.list(x), "list")
435+
expect_s7_class(convert(x, element_rect), element_rect)
436+
437+
# For now, element_text doesn't round-trip.
438+
# Once fontwidth/fontweight/italic are implemented, it should round-trip again
439+
x <- as.list(element_text(colour = "red"))
440+
expect_snapshot_warning(
441+
convert(x, element_text)
442+
)
443+
x <- x[setdiff(names(x), c("fontwidth", "fontweight", "italic"))]
444+
expect_silent(convert(x, element_text))
445+
})
446+
431447
# Visual tests ------------------------------------------------------------
432448

433449
test_that("aspect ratio is honored", {

tests/testthat/test-utilities.R

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,24 @@ test_that("compute_data_size handles gnarly cases", {
207207
new <- compute_data_size(df, size = NULL, target = "width", default = 1)
208208
expect_all_equal(new$width, 2)
209209
})
210+
211+
test_that("list conversion works for ggplot classes", {
212+
# Test list-based class round-trips
213+
x <- aes(x = 10, y = foo)
214+
expect_type(x <- as.list(x), "list")
215+
expect_s7_class(x <- convert(x, class_mapping), class_mapping)
216+
217+
# Mapping should still be able to evaluate
218+
expect_equal(
219+
eval_aesthetics(x, data = data.frame(foo = "A")),
220+
list(x = 10, y = "A")
221+
)
222+
223+
# Test property-based class round-trips
224+
x <- ggplot()
225+
expect_type(x <- as.list(x), "list")
226+
expect_s7_class(x <- convert(x, class_ggplot), class_ggplot)
227+
228+
# Plot should still be buildable
229+
expect_s3_class(ggplotGrob(x), "gtable")
230+
})

0 commit comments

Comments
 (0)