Skip to content
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Collate:
'compat-plyr.R'
'utilities.R'
'aes.R'
'all-classes.R'
'utilities-checks.R'
'legend-draw.R'
'geom-.R'
Expand Down Expand Up @@ -197,9 +198,9 @@ Collate:
'margins.R'
'performance.R'
'plot-build.R'
'plot.R'
'plot-construction.R'
'plot-last.R'
'plot.R'
'position-.R'
'position-collide.R'
'position-dodge.R'
Expand Down
2 changes: 0 additions & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ S3method(fortify,tbl_df)
S3method(get_alt_text,ggplot)
S3method(get_alt_text,ggplot_built)
S3method(get_alt_text,gtable)
S3method(ggplot,"function")
S3method(ggplot,default)
S3method(ggplot_build,ggplot)
S3method(ggplot_gtable,ggplot_built)
S3method(grid.draw,absoluteGrob)
Expand Down
11 changes: 11 additions & 0 deletions R/all-classes.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Class declarations for S7 dispatch.
class_theme <- S7::new_S3_class("theme")
class_scale <- S7::new_S3_class("Scale")
class_labels <- S7::new_S3_class("labels")
class_guides <- S7::new_S3_class("Guides")
class_aes <- S7::new_S3_class("uneval")
class_coord <- S7::new_S3_class("Coord")
class_facet <- S7::new_S3_class("Facet")
class_by <- S7::new_S3_class("by")
class_layer <- S7::new_S3_class("Layer")
class_scales_list <- S7::new_S3_class("ScalesList")
52 changes: 22 additions & 30 deletions R/plot-construction.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#' @include plot.R
NULL

#' Add components to a plot
#'
#' `+` is the key to constructing sophisticated ggplot2 graphics. It
Expand Down Expand Up @@ -91,79 +94,68 @@ add_ggplot <- function(p, object, objectname) {
#' @export
ggplot_add <- S7::new_generic("ggplot_add", c("object", "plot"))

# Class declarations for S7 dispatch. If S7 gets implemented more broadly,
# consider moving these to a new file.
class_ggplot <- S7::new_S3_class("ggplot")
class_theme <- S7::new_S3_class("theme")
class_scale <- S7::new_S3_class("Scale")
class_labels <- S7::new_S3_class("labels")
class_guides <- S7::new_S3_class("Guides")
class_aes <- S7::new_S3_class("uneval")
class_coord <- S7::new_S3_class("Coord")
class_facet <- S7::new_S3_class("Facet")
class_by <- S7::new_S3_class("by")
class_layer <- S7::new_S3_class("Layer")

S7::method(ggplot_add, list(S7::class_any, class_ggplot)) <-
S7::method(ggplot_add, list(S7::class_any, ggplot)) <-
function(object, plot, object_name) {
cli::cli_abort("Can't add {.var {object_name}} to a {.cls ggplot} object.")
}

# Cannot currently double dispatch on NULL directly
# replace `S7::new_S3_class("NULL")` with `NULL` when S7 version > 0.1.1
S7::method(ggplot_add, list(S7::new_S3_class("NULL"), class_ggplot)) <-
S7::method(ggplot_add, list(S7::new_S3_class("NULL"), ggplot)) <-
function(object, plot, object_name) {
plot
}

S7::method(ggplot_add, list(S7::class_data.frame, class_ggplot)) <-
S7::method(ggplot_add, list(S7::class_data.frame, ggplot)) <-
function(object, plot, object_name) {
plot$data <- object
plot
}

S7::method(ggplot_add, list(S7::class_function, class_ggplot)) <-
S7::method(ggplot_add, list(S7::class_function, ggplot)) <-
function(object, plot, object_name) {
cli::cli_abort(c(
"Can't add {.var {object_name}} to a {.cls ggplot} object",
"i" = "Did you forget to add parentheses, as in {.fn {object_name}}?"
))
}

S7::method(ggplot_add, list(class_theme, class_ggplot)) <-
S7::method(ggplot_add, list(class_theme, ggplot)) <-
function(object, plot, object_name) {
plot$theme <- add_theme(plot$theme, object)
plot
}

S7::method(ggplot_add, list(class_scale, class_ggplot)) <-
S7::method(ggplot_add, list(class_scale, ggplot)) <-
function(object, plot, object_name) {
plot$scales$add(object)
plot
}

S7::method(ggplot_add, list(class_labels, class_ggplot)) <-
S7::method(ggplot_add, list(class_labels, ggplot)) <-
function(object, plot, object_name) {
update_labels(plot, object)
}

S7::method(ggplot_add, list(class_guides, class_ggplot)) <-
S7::method(ggplot_add, list(class_guides, ggplot)) <-
function(object, plot, object_name) {
update_guides(plot, object)
}

S7::method(ggplot_add, list(class_aes, class_ggplot)) <-
S7::method(ggplot_add, list(class_aes, ggplot)) <-
function(object, plot, object_name) {
plot$mapping <- defaults(object, plot$mapping)
mapping <- defaults(object, plot$mapping)
# defaults() doesn't copy class, so copy it.
class(plot$mapping) <- class(object)
class(mapping) <- class(object)
S7::prop(plot, "mapping") <- mapping


labels <- make_labels(object)
names(labels) <- names(object)
update_labels(plot, labels)
}

S7::method(ggplot_add, list(class_coord, class_ggplot)) <-
S7::method(ggplot_add, list(class_coord, ggplot)) <-
function(object, plot, object_name) {
if (!isTRUE(plot$coordinates$default)) {
cli::cli_inform("Coordinate system already present. Adding new coordinate system, which will replace the existing one.")
Expand All @@ -173,28 +165,28 @@ S7::method(ggplot_add, list(class_coord, class_ggplot)) <-
plot
}

S7::method(ggplot_add, list(class_facet, class_ggplot)) <-
S7::method(ggplot_add, list(class_facet, ggplot)) <-
function(object, plot, object_name) {
plot$facet <- object
plot
}

S7::method(ggplot_add, list(S7::class_list, class_ggplot)) <-
S7::method(ggplot_add, list(S7::class_list, ggplot)) <-
function(object, plot, object_name) {
for (o in object) {
plot <- plot %+% o
}
plot
}

S7::method(ggplot_add, list(class_by, class_ggplot)) <-
S7::method(ggplot_add, list(class_by, ggplot)) <-
function(object, plot, object_name) {
S7::method(ggplot_add, list(class_list, class_ggplot))(
S7::method(ggplot_add, list(class_list, ggplot))(
object, plot, object_name
)
}

S7::method(ggplot_add, list(class_layer, class_ggplot)) <-
S7::method(ggplot_add, list(class_layer, ggplot)) <-
function(object, plot, object_name) {
plot$layers <- append(plot$layers, object)

Expand Down
93 changes: 57 additions & 36 deletions R/plot.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

gg <- S7::new_class("gg", abstract = TRUE)

#' Create a new ggplot
#'
#' `ggplot()` initializes a ggplot object. It can be used to
Expand Down Expand Up @@ -101,49 +104,67 @@
#' mapping = aes(x = group, y = group_mean), data = group_means_df,
#' colour = 'red', size = 3
#' )
ggplot <- function(data = NULL, mapping = aes(), ...,
environment = parent.frame()) {
UseMethod("ggplot")
}
ggplot <- S7::new_class(
name = "ggplot", parent = gg,
properties = list(
data = S7::class_any,
layers = S7::class_list,
scales = class_scales_list,
guides = class_guides,
mapping = class_aes,
theme = class_theme,
coordinates = class_coord,
facet = class_facet,
labels = S7::class_list,
plot_env = S7::class_environment
),
constructor = function(data = NULL, mapping = aes(), ...,
environment = parent.frame()) {

#' @export
ggplot.default <- function(data = NULL, mapping = aes(), ...,
environment = parent.frame()) {
if (!missing(mapping) && !inherits(mapping, "uneval")) {
cli::cli_abort(c(
"{.arg mapping} should be created with {.fn aes}.",
"x" = "You've supplied a {.cls {class(mapping)[1]}} object"
))
}
if (!missing(mapping) && !inherits(mapping, "uneval")) {
cli::cli_abort(c(
"{.arg mapping} should be created with {.fn aes}.",
"x" = "You've supplied a {.cls {class(mapping)[1]}} object."
))
}

data <- fortify(data, ...)
if (is.function(data)) {
cli::cli_abort(c(
"{.arg data} cannot be a function.",
"i" = "Have you misspelled the {.arg data} argument in {.fn ggplot}?"
))
}

p <- structure(list(
data = data,
layers = list(),
scales = scales_list(),
guides = guides_list(),
mapping = mapping,
theme = list(),
coordinates = coord_cartesian(default = TRUE),
facet = facet_null(),
plot_env = environment
), class = c("gg", "ggplot"))
data <- fortify(data, ...)

p$labels <- make_labels(mapping)
obj <- S7::new_object(
S7::S7_object(),
data = data,
layers = list(),
scales = scales_list(),
guides = guides_list(),
mapping = mapping,
theme = theme(),
coordinates = coord_cartesian(default = TRUE),
facet = facet_null(),
labels = make_labels(mapping),
plot_env = environment
)

set_last_plot(p)
p
set_last_plot(obj)
obj
}
)

S7::method(`$`, ggplot) <- function(x, i) {
if (!S7::prop_exists(x, i)) {
return(NULL)
}
S7::prop(x, i)
}

#' @export
ggplot.function <- function(data = NULL, mapping = aes(), ...,
environment = parent.frame()) {
# Added to avoid functions end in ggplot.default
cli::cli_abort(c(
"{.arg data} cannot be a function.",
"i" = "Have you misspelled the {.arg data} argument in {.fn ggplot}"
))
S7::method(`$<-`, ggplot) <- function(x, ...) {
S7::`prop<-`(x, ...)
}

plot_clone <- function(plot) {
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/plot.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# ggplot() throws informative errors

`mapping` should be created with `aes()`.
x You've supplied a <character> object
x You've supplied a <character> object.

---

`data` cannot be a function.
i Have you misspelled the `data` argument in `ggplot()`
i Have you misspelled the `data` argument in `ggplot()`?

# construction have user friendly errors

Expand Down