I am trying to extend ggplot2
with a new class that we will call foo
for this example. The goal is to write a +.foo
method that will be used in place of +.gg
. However I am running into an issue of "incompatible methods"
Currently I am able to write ggplot_add.foo_layer
which will make plot
into my foo
class and then add the corresponding layer as normal.
The idea is that once the plot object inherits foo
it will dispatch to +.foo
when the next layer added.
The reason I would like to do this is because I want to check if the structure of foo
object is still valid/compatible with the incoming layer. This will prevent me from having to write a method for ggplot_build
.
library(ggplot2)
`+.foo` <- function(e1, e2){
cat("Using foo ggplot +") # for Debugging
NextMethod() #ideally just dispatches to `+.gg`
}
ggplot_add.foo_layer <- function(object, plot, object_name) {
plot <- as_foo(plot)
ggplot2:::add_ggplot(plot, object$layer, object_name)
}
as_foo <- function(x){
if(!is_foo(x)){
class(x) <- c("foo", class(x))
}
x
}
is_foo <- function(x) inherits(x, "foo")
foo_layer <- function(x) structure(list(layer = x), class = "foo_layer")
p1 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
geom_point()
class(p1)
#[1] "gg" "ggplot"
p1 + geom_density(aes(y = after_stat(density)))
p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
foo_layer(geom_point())
class(p2)
#[1] "foo" "gg" "ggplot"
p2 + geom_density(aes(y = after_stat(density)))
#Error in p2 + geom_density(aes(y = after_stat(density))) :
# non-numeric argument to binary operator
#In addition: Warning message:
#Incompatible methods ("+.foo", "+.gg") for "+"
From the code above p1 + geom_*
executes fine. However p2 + geom_*
can not be made due to the above error about Incompatible methods. From what I know about S3 method dispatch I don't understand why this would not work. Could someone explain why this is or how I could remedy this.
Ideally I would not have to write a method ggplot_build.foo
because I want other package's ggplot_build
to be used if they exist (for example gganimate
).
One thing you can do is to overwrite ggplot2:::+gg method to support double dispatch in S3. This isn't really good behaviour if you're writing a package, but it gets the job done. Note that this being naughty behaviour hasn't stopped other packages from overwriting ggplot's functions (looking at you, ggtern).
library(ggplot2)
`+.gg` <- function(e1, e2) {
UseMethod("+.gg")
}
`+.gg.default` <- ggplot2:::`+.gg`
`+.gg.foo` <- function(e1, e2) {
cat("Using foo ggplot +")
NextMethod()
}
ggplot_add.foo_layer <- function(object, plot, object_name) {
plot <- as_foo(plot)
ggplot2:::add_ggplot(plot, object$layer, object_name)
}
as_foo <- function(x){
if(!is_foo(x)){
class(x) <- c("foo", class(x))
}
x
}
is_foo <- function(x) inherits(x, "foo")
foo_layer <- function(x) structure(list(layer = x), class = "foo_layer")
p1 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
geom_point()
p2 <- ggplot(iris, aes(Sepal.Width, Sepal.Length, color = Species)) +
foo_layer(geom_point())
p2 + geom_density(aes(y = after_stat(density)))
#> Using foo ggplot +
Created on 2021-01-20 by the reprex package (v0.3.0)