Skip to content

Commit 53504c3

Browse files
committed
refine class_ggplot_built and related functions
1 parent 206c394 commit 53504c3

File tree

8 files changed

+179
-55
lines changed

8 files changed

+179
-55
lines changed

NAMESPACE

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ S3method(fortify,tbl_df)
5858
S3method(get_alt_text,ggplot)
5959
S3method(get_alt_text,ggplot_built)
6060
S3method(get_alt_text,gtable)
61-
S3method(ggplot,"function")
62-
S3method(ggplot,default)
63-
S3method(ggplot_build,ggplot)
64-
S3method(ggplot_build,ggplot_built)
65-
S3method(ggplot_gtable,ggplot_built)
6661
S3method(grid.draw,absoluteGrob)
6762
S3method(grobHeight,absoluteGrob)
6863
S3method(grobHeight,zeroGrob)
@@ -292,6 +287,8 @@ export(binned_scale)
292287
export(borders)
293288
export(calc_element)
294289
export(check_device)
290+
export(class_ggplot)
291+
export(class_ggplot_built)
295292
export(class_mapping)
296293
export(combine_vars)
297294
export(complete_theme)

R/all-classes.R

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@ class_facet <- S7::new_S3_class("Facet")
77
class_layer <- S7::new_S3_class("Layer")
88
class_layout <- S7::new_S3_class("Layout")
99
class_scales_list <- S7::new_S3_class("ScalesList")
10-
class_layout <- S7::new_S3_class("Layout")
1110
class_ggproto <- S7::new_S3_class("ggproto")
1211
class_gtable <- S7::new_S3_class("gtable")

R/plot-build.R

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
11
#' @include plot.R
2+
NULL
23

4+
#' The ggplot built class
5+
#'
6+
#' The ggplot built class is an intermediate class and represents a processed
7+
#' ggplot object ready for rendering. It is constructed by calling
8+
#' [`ggplot_build()`] on a [ggplot][class_ggplot] object and is not meant to be
9+
#' instantiated directly. The class can be rendered to a gtable object by
10+
#' calling the [`ggplot_gtable()`] function on a ggplot built class object.
11+
#'
12+
#' @param data A list of plain data frames; one for each layer.
13+
#' @param layout A Layout ggproto object.
14+
#' @param plot A completed ggplot class object.
15+
#'
16+
#' @export
317
class_ggplot_built <- S7::new_class(
418
"ggplot_built",
519
properties = list(
6-
data = S7::class_list,
20+
data = S7::class_list,
721
layout = class_layout,
8-
plot = class_ggplot
9-
)
22+
plot = class_ggplot
23+
),
24+
constructor = function(data = NULL, layout = NULL, plot = NULL) {
25+
if (is.null(data) || is.null(layout) || is.null(plot)) {
26+
cli::cli_abort(
27+
"The {.cls ggplot_built} class should be constructed by {.fn ggplot_build}."
28+
)
29+
}
30+
S7::new_object(
31+
S7::S7_object(),
32+
data = data, layout = layout, plot = plot
33+
)
34+
}
1035
)
1136

1237
#' Build ggplot for rendering.
@@ -34,21 +59,19 @@ class_ggplot_built <- S7::new_class(
3459
#' The `r link_book("build step section", "internals#sec-ggplotbuild")`
3560
#' @keywords internal
3661
#' @export
37-
ggplot_build <- function(plot) {
62+
ggplot_build <- S7::new_generic("ggplot_build", "plot", fun = function(plot) {
3863
# Attaching the plot env to be fetched by deprecations etc.
39-
attach_plot_env(plot$plot_env)
64+
if (S7::S7_inherits(plot) && S7::prop_exists(plot, "plot_env")) {
65+
attach_plot_env(plot@plot_env)
66+
}
67+
S7::S7_dispatch()
68+
})
4069

41-
UseMethod('ggplot_build')
70+
S7::method(ggplot_build, class_ggplot_built) <- function(plot) {
71+
plot # This is a no-op
4272
}
4373

44-
#' @export
45-
ggplot_build.ggplot_built <- function(plot) {
46-
# This is a no-op
47-
plot
48-
}
49-
50-
#' @export
51-
ggplot_build.ggplot <- function(plot) {
74+
S7::method(ggplot_build, class_ggplot) <- function(plot) {
5275
plot <- plot_clone(plot)
5376
if (length(plot@layers) == 0) {
5477
plot <- plot + geom_blank()
@@ -202,15 +225,12 @@ layer_grob <- get_layer_grob
202225
#' @keywords internal
203226
#' @param data plot data generated by [ggplot_build()]
204227
#' @export
205-
ggplot_gtable <- function(data) {
206-
# Attaching the plot env to be fetched by deprecations etc.
207-
attach_plot_env(data$plot@plot_env)
208-
209-
UseMethod('ggplot_gtable')
210-
}
228+
ggplot_gtable <- S7::new_generic("ggplot_gtable", "data", function(data) {
229+
attach_plot_env(data@plot@plot_env)
230+
S7::S7_dispatch()
231+
})
211232

212-
#' @export
213-
ggplot_gtable.ggplot_built <- function(data) {
233+
S7::method(ggplot_gtable, class_ggplot_built) <- function(data) {
214234
plot <- data@plot
215235
layout <- data@layout
216236
data <- data@data

R/plot.R

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
#' @include all-classes.R
22
#' @include theme.R
3+
NULL
34

5+
#' The ggplot class
6+
#'
7+
#' The ggplot class collects the needed information to render a plot.
8+
#' This class can be constructed using the [`ggplot()`] function.
9+
#'
10+
#' @param data A property containing any data coerced by [`fortify()`].
11+
#' @param layers A list of layer instances created by [`layer()`].
12+
#' @param scales A ScalesList ggproto object.
13+
#' @param guides A Guides ggproto object created by [`guides()`].
14+
#' @param mapping A mapping class object created by [`aes()`].
15+
#' @param theme A theme class object created by [`theme()`].
16+
#' @param coordinates A Coord ggproto object created by `coord_*()` family of
17+
#' functions.
18+
#' @param facet A Facet ggproto object created by `facet_*()` family of
19+
#' functions.
20+
#' @param layout A Layout ggproto object.
21+
#' @param labels A labels object created by [`labs()`].
22+
#' @param plot_env An environment.
23+
#'
24+
#' @export
425
class_ggplot <- S7::new_class(
526
name = "ggplot", parent = class_gg,
627
properties = list(
@@ -15,7 +36,23 @@ class_ggplot <- S7::new_class(
1536
layout = class_layout,
1637
labels = labs,
1738
plot_env = S7::class_environment
18-
)
39+
),
40+
constructor = function(data = waiver(), layers = list(), scales = NULL,
41+
guides = NULL, mapping = aes(), theme = NULL,
42+
coordinates = coord_cartesian(default = TRUE),
43+
facet = facet_null(), layout = NULL,
44+
labels = labs(), plot_env = parent.frame()) {
45+
S7::new_object(
46+
S7::S7_object(),
47+
data = data, layers = layers,
48+
scales = scales %||% scales_list(),
49+
guides = guides %||% guides_list(),
50+
mapping = mapping, theme = theme %||% theme(),
51+
coordinates = coordinates, facet = facet,
52+
layout = layout %||% ggproto(NULL, Layout),
53+
labels = labels, plot_env = plot_env
54+
)
55+
}
1956
)
2057

2158
#' Create a new ggplot
@@ -123,51 +160,48 @@ class_ggplot <- S7::new_class(
123160
#' mapping = aes(x = group, y = group_mean), data = group_means_df,
124161
#' colour = 'red', size = 3
125162
#' )
126-
ggplot <- function(data = NULL, mapping = aes(), ...,
127-
environment = parent.frame()) {
128-
UseMethod("ggplot")
129-
}
163+
ggplot <- S7::new_generic(
164+
"ggplot2", "data",
165+
fun = function(data, mapping = aes(), ..., environment = parent.frame()) {
166+
S7::S7_dispatch()
167+
}
168+
)
130169

131-
#' @export
132-
ggplot.default <- function(data = NULL, mapping = aes(), ...,
133-
environment = parent.frame()) {
170+
S7::method(ggplot, S7::class_any) <- function(
171+
data, mapping = aes(), ...,
172+
environment = parent.frame()) {
134173
if (!missing(mapping) && !is.mapping(mapping)) {
135174
cli::cli_abort(c(
136175
"{.arg mapping} must be created with {.fn aes}.",
137176
"x" = "You've supplied {.obj_type_friendly {mapping}}."
138177
))
139178
}
179+
if (missing(data)) {
180+
data <- NULL
181+
}
140182

141183
data <- fortify(data, ...)
142184

143185
p <- class_ggplot(
144186
data = data,
145-
layers = list(),
146-
scales = scales_list(),
147-
guides = guides_list(),
148187
mapping = mapping,
149-
theme = theme(),
150-
coordinates = coord_cartesian(default = TRUE),
151-
facet = facet_null(),
152-
plot_env = environment,
153-
layout = ggproto(NULL, Layout),
154-
labels = labs()
188+
plot_env = environment
155189
)
156190
class(p) <- union("ggplot", class(p))
157191

158192
set_last_plot(p)
159193
p
160194
}
161195

162-
#' @export
163-
ggplot.function <- function(data = NULL, mapping = aes(), ...,
164-
environment = parent.frame()) {
165-
# Added to avoid functions end in ggplot.default
166-
cli::cli_abort(c(
167-
"{.arg data} cannot be a function.",
168-
"i" = "Have you misspelled the {.arg data} argument in {.fn ggplot}"
169-
))
170-
}
196+
S7::method(ggplot, S7::class_function) <-
197+
function(data, mapping = aes(), ...,
198+
environment = parent.frame()) {
199+
# Added to avoid functions end in ggplot.default
200+
cli::cli_abort(c(
201+
"{.arg data} cannot be a function.",
202+
"i" = "Have you misspelled the {.arg data} argument in {.fn ggplot}"
203+
))
204+
}
171205

172206
#' Reports whether x is a type of object
173207
#' @param x An object to test

man/class_ggplot.Rd

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/class_ggplot_built.Rd

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ggplot.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/prohibited-functions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
$calc_element
1313
[1] "skip_blank"
1414
15+
$class_ggplot
16+
[1] "plot_env"
17+
1518
$continuous_scale
1619
[1] "scale_name" "minor_breaks"
1720

0 commit comments

Comments
 (0)