I'm trying to build a package for data visualisation that relies heavily on ggplot2, but has some custom shortcuts for some of the day to day problems I face.
I am able to use ggplot_add
function to extend the functionality of +
for custom classes from scripts, however when I add these scripts to a package, ggplot_add
no longer works.
Below I paste a minrep, to replicate first one needs to create a package (I'm using RStudio), that I've called SOExa. That project contains the following files:
.Rbuildignore
^.*\.Rproj$
^\.Rproj\.user$
DESCRIPTION
Package: SOExa
Type: Package
Title: An minrep for a problem I'm having
Version: 0.1.0
Author: Col Bates
Maintainer: The package maintainer <yourself@somewhere.net>
Description: I want to use ggplot2's ggplot_add from inside another package, i.e. this one.
It seems that when I do I get an error.
License: GPLv2
Encoding: UTF-8
Imports:
dplyr,
magrittr,
tidyr,
glue,
ggplot2
LazyData: true
RoxygenNote: 7.1.1
My project file SOExa.Rproj
A folder called R, containing the minrep example of use:
R/designed_by.R
#' the function to add a 'designed by' to the plot
#' as a designed_by class
#'@export
designed_by<-function(x){
return(new_designed_by(x))
}
#' generic constructor.
#' @export
new_designed_by<-function(x){
x <- list('designed_by' = x)
class(x) <- 'designed_by'
return(x)
}
#' generic print for designed_by
#' @export
print.designed_by <- function(x){
print(paste('Designed by:', format(x)))
}
#' defines the addition of an designed_by object for
#' @export
ggplot_add.designed_by <- function(object, plot, objectname){
plot$designed_by <- object$designed_by
plot
}
ggplot_add <- function(x){
UseMethod("ggplot_add")
}
I run the following code to build the Namespace file
devtools::document()
A new file is created:
NAMESPACE
# Generated by roxygen2: do not edit by hand
S3method(ggplot_add,designed_by)
S3method(print,designed_by)
export(designed_by)
export(new_designed_by)
After this I install and load the library:
devtools::install()
library(SOExa)
Then creating an empty plot:
p <- ggplot2::ggplot()
The following will give rise to an error:
p <- p + designed_by('Col Bates')
The error I get is:
# Error: Can't add `designed_by("Col Bates")` to a ggplot object.
# Run `rlang::last_error()` to see where the error occurred.
So following those steps:
rlang::last_error()
Which returns
# <error/rlang_error>
# Can't add `designed_by("Col Bates")` to a ggplot object.
# Backtrace:
# 1. ggplot2:::`+.gg`(p, designed_by("Col Bates"))
# 2. ggplot2:::add_ggplot(e1, e2, e2name)
# 4. ggplot2:::ggplot_add.default(object, p, objectname)
# Run `rlang::last_trace()` to see the full context.
Running
rlang::last_trace()
I get
<error/rlang_error>
Can't add `designed_by("Col Bates")` to a ggplot object.
Backtrace:
x
1. \-ggplot2:::`+.gg`(p, designed_by("Col Bates"))
2. \-ggplot2:::add_ggplot(e1, e2, e2name)
3. +-ggplot2::ggplot_add(object, p, objectname)
4. \-ggplot2:::ggplot_add.default(object, p, objectname)
From this I can deduce that ggplot2::ggplot_add(), which calls UseMethod('ggplot_add')
has invoked decided to apply the function ggplot_add.default
, and hasn't recognised my class designed_by
.
Incidentally, using the print()
function does work from the library.
print(designed_by('Col Bates'))
However, if I were to source the script, rather than use the package like the following:
source('./R/designed_by.R')
p <- p + designed_by('Col Bates')
It does indeed work the way I would expect.
Looking deeper into things, I can see that the source of the generic ggplot_add
on the class designed_by
is my package.
sloop::s3_methods_generic("ggplot_add")
## A tibble: 1 x 4
# generic class visible source
# <chr> <chr> <lgl> <chr>
# 1 ggplot_add designed_by TRUE SOExa
Whereas with ggplot classes it is 'registered S3method'
> sloop::s3_methods_generic("ggplot_add")
## A tibble: 14 x 4
# generic class visible source
# <chr> <chr> <lgl> <chr>
# 1 ggplot_add by FALSE registered S3method
# 2 ggplot_add Coord FALSE registered S3method
# 3 ggplot_add data.frame FALSE registered S3method
# 4 ggplot_add default FALSE registered S3method
# 5 ggplot_add Facet FALSE registered S3method
# ...
I looked inside the ggplot2 source code, but couldn't really figure out how this works. I've also been reading https://adv-r.hadley.nz/s3.html but haven't seen anything about using S3methods which apply to classes from another library.
It would be great to figure out if it is possible to package the calls into my custom package, or if I would always need to rely on sourcing.
Thanks.
Your setup should work if you add ggplot2
to Depends
rather than Imports
in the DESCRIPTION
file. For example, if I create a new package with the following files:
.Rbuildignore
^.*\.Rproj$
^\.Rproj\.user$
DESCRIPTION
Package: SOExa
Type: Package
Title: An minrep for a problem I'm having
Version: 0.1.0
Author: Col Bates
Maintainer: The package maintainer <yourself@somewhere.net>
Description: I want to use ggplot2's ggplot_add from inside another package, i.e. this one.
It seems that when I do I get an error.
License: GPLv2
Encoding: UTF-8
Depends: ggplot2
Imports:
dplyr,
magrittr,
tidyr,
glue
LazyData: true
RoxygenNote: 7.1.1
designed_by.R
#' the function to add a 'designed by' to the plot
#' as a designed_by class
#'@export
designed_by<-function(x){
return(new_designed_by(x))
}
#' generic constructor.
#' @export
new_designed_by<-function(x){
x <- list('designed_by' = x)
class(x) <- 'designed_by'
return(x)
}
#' generic print for designed_by
#' @export
print.designed_by <- function(x){
print(paste('Designed by:', format(x)))
}
#' defines the addition of an designed_by object for
#' @export
ggplot_add.designed_by <- function(object, plot, objectname){
plot$designed_by <- object$designed_by
plot
}
Then running devtools::document()
gives me:
NAMESPACE
# Generated by roxygen2: do not edit by hand
S3method(ggplot_add,designed_by)
S3method(print,designed_by)
export(designed_by)
export(new_designed_by)
So after I do devtools::install()
I get the following output:
library(SOExa)
#> Loading required package: ggplot2
p <- ggplot(data = NULL, aes(x = 1:10, y = 1:10)) + geom_point()
p <- p + designed_by('Col Bates')
p
p$designed_by
#> [1] "Col Bates"