Skip to content

Commit

Permalink
Add gg()
Browse files Browse the repository at this point in the history
  • Loading branch information
phgrosjean committed Aug 23, 2022
1 parent 257091e commit f5e354a
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 5 deletions.
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
Package: chart
Type: Package
Version: 1.3.1
Version: 1.4.0
Title: General Charting (Plotting) Function
Description: Chart generalizes plot generation in R, being with base R plot
function, lattice or ggplot2. A formula interface is available for both.
Authors@R: c(person("Philippe", "Grosjean", role = c("aut", "cre"),
email = "phgrosjean@sciviews.org",
comment = c(ORCID = "0000-0002-2694-9471")))
Maintainer: Philippe Grosjean <phgrosjean@sciviews.org>
Depends: R (>= 3.3.0), lattice, ggplot2 (>= 3.0)
Depends: R (>= 4.0.0), lattice, ggplot2 (>= 3.0)
Imports: stats, utils, rlang, cowplot, grDevices, graphics, scales, latticeExtra, viridis, pryr, data.io, ggplotify, ggpubr
Suggests: MASS, covr, knitr, testthat, rmarkdown, spelling
Remotes: SciViews/data.io
Expand Down
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Generated by roxygen2: do not edit by hand

S3method("$",subsettable_gg)
S3method("$",subsettable_type)
S3method(.DollarNames,subsettable_gg)
S3method(chart,"function")
S3method(chart,default)
export(chart)
export(chart_theme)
export(combine_charts)
export(f_aes)
export(gg)
export(ggarrange)
export(theme_sciviews)
export(theme_sciviews_graphics)
Expand Down Expand Up @@ -40,5 +43,7 @@ importFrom(scales,gradient_n_pal)
importFrom(scales,hue_pal)
importFrom(stats,as.formula)
importFrom(stats,asOneSidedFormula)
importFrom(utils,.DollarNames)
importFrom(utils,apropos)
importFrom(utils,modifyList)
importFrom(viridis,viridis_pal)
6 changes: 5 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# chart 1.4.0

- The `gg()` subsettable function allows to use the pipe instead of the `+` in ggplot2, like `ggplot() |> gg$geom_point()`. Also, a completion list is available when typing `gg$<tab>`.

# chart 1.3.1

- Code in `chart.default()` choked `R CMD Check` (variable `y` not defined in scope) is commented out. Expect a slightly different behavior with automatic `geom_point()` with only one variable.

- GitHub repository cleaned up (spelling + GitHub ations added).
- GitHub repository cleaned up (spelling + GitHub actions added).

## chart 1.3.0

Expand Down
2 changes: 1 addition & 1 deletion R/chart-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#' @importFrom cowplot theme_cowplot
#' @importFrom rlang abort warn f_env f_lhs f_rhs is_true is_quosure
#' @importFrom stats as.formula asOneSidedFormula
#' @importFrom utils modifyList
#' @importFrom utils .DollarNames apropos modifyList
#' @importFrom pryr modify_lang
#' @importFrom data.io label
#' @importFrom ggplotify as.ggplot
Expand Down
133 changes: 133 additions & 0 deletions R/gg.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#' Make a ggplot function pipeable
#'
#' @description The function [gg()] should be used like this: `gg$geom_point()`.
#' It transforms on the fly an original {ggplot2} function supposed to be used
#' with the `+` operator (like `p + geom_point()`) into a pipeable version
#' (like `p %>% gg$geom_point()`).
#'
#' @param ggplot An object of class "ggplot" (or "theme").
#' @param ... Further arguments passed to the the ggplot function (see Details).
#' @param x The `gg()`function.
#' @param name The name of the ggplot function to make pipeable.
#' @param pattern A regular expression to list matching names.
#'
#' @return The `gg()` function just returns an error message. When subsetted
#' with the name of a {ggplot2} function (e.g., `gg$geom_point()`), it
#' returns a modified version of that function in such a way that it can be
#' used with a pipe operator.
#'
#' @details The function returned by `gg$fun` is a modified version of the
#' function `fun` where a first argument `ggplot =` is added, and the
#' instruction `ggplot + ...` is added in its body. A message
#' is also added in the body to explicitly warn about these changes. All the
#' other arguments of `fun` remain valid and should keep their original meaning.
#'
#' The changes are done on the fly, and the original function `fun` is **not**
#' altered anywhere else (and in particular, no alteration is done in a package
#' or a namespace). When using this construct, make sure that: (1) you
#' understand what is done, (2) you are aware that you use an altered version of
#' the original function, (3) a bug or strange behavior may occur due to the
#' patch and the original author of the function is not responsible in this case
#' (the problem must be reported to the author of [gg()] and the maintainer of
#' the present package instead), and (4) the patched function exhibits an
#' additional argument and behaves differently to what is described in the help
#' page of the original, non-patched, function!
#'
#' @export
#'
#' @examples
#' library(ggplot2)
#' data(iris)
#' ggplot(aes(x = Petal.Length, y = Sepal.Length, col = Species) , data = iris) |>
#' gg$geom_point() |>
#' gg$labs(x = "Sepal length (mm)", y = "Petal length (mm)")
#' # Also try completion with gg$<tab>
gg <- structure(function(ggplot, ...) {
stop("You must indicate gg$<something>(), like gg$geom_point() or gg$labs()")
}, class = c("subsettable_gg", "function"))

.make_pipeable_gg <- function(fun) {
if (is.function(fun)) {
f <- fun
} else {
fun_name <- as.character(fun)
f <- get0(fun_name, envir = parent.frame(2), mode = "function",
inherits = TRUE)
if (is.null(f))
stop("Cannot found function '", fun_name, "'")
}
# Change arguments to prepend ggplot =
formals(f) <- c(pairlist(ggplot = NULL), formals(f))
# Change body: prepend ggplot + {...}
patched_body <- body(function(ggplot, ...) {
"Warning: this is a patched version of a ggplot function to make it pipeable. Please, read ?gg first!"
ggplot + 1
})
patched_body[[3]][[3]] <- body(f)
body(f) <- patched_body
f
}

#' @export
#' @rdname gg
#' @method $ subsettable_gg
`$.subsettable_gg` <- function(x, name) {
if (name == "title")
name <- "ggtitle"
.make_pipeable_gg(name)
}

#' @export
#' @rdname gg
#' @method .DollarNames subsettable_gg
.DollarNames.subsettable_gg <- function(x, pattern = "") {
l <- list("annotate", "expand_limits", "guides", "labs", "lims", "theme",
"title", # Instead of ggtitle (special case!)
"xlab", "xlim", "ylab", "ylim")
l <- grep(pattern = pattern, l, value = TRUE)
l <- c(l, apropos(paste0("^geom_", pattern)))
l <- c(l, apropos(paste0("^annotation_", pattern)))
l <- c(l, apropos(paste0("^coord_", pattern)))
l <- c(l, apropos(paste0("^facet_", pattern)))
l <- c(l, apropos(paste0("^scale_", pattern)))
l <- c(l, apropos(paste0("^stat_", pattern)))
l <- c(l, apropos(paste0("^theme_", pattern)))
sort(l)
}

# TODO: allow custom extensions to this list
# (trials that do not work for now)
# gg_help_handler <- function(type = c("completion", "parameter", "url"),
# topic, source, ...) {
# type <- match.arg(type)
# if (type == "completion") {
# # Just a trial
# list(title = "title", signature = "function(x, y)", returns = "1",
# description = "desc", details = "details", sections = "sections")
# } else if (type == "parameter") {
# list(args = c("x", "y"), arg_descriptions = c("x param", "y param"))
# } else if (type == "url") {
# "https://wp.sciviews.org"
# }
# }
#
# .DollarNames.subsettable_gg <- function(x, pattern = "") {
# l <- list("annotate", "expand_limits", "guides", "labs", "lims", "theme",
# "title", # Instead of ggtitle (special case!)
# "xlab", "xlim", "ylab", "ylim")
# l <- grep(pattern = pattern, l, value = TRUE)
# l <- c(l, apropos(paste0("^geom_", pattern)))
# l <- c(l, apropos(paste0("^annotation_", pattern)))
# l <- c(l, apropos(paste0("^coord_", pattern)))
# l <- c(l, apropos(paste0("^facet_", pattern)))
# l <- c(l, apropos(paste0("^scale_", pattern)))
# l <- c(l, apropos(paste0("^stat_", pattern)))
# l <- c(l, apropos(paste0("^theme_", pattern)))
# # This works and my object is recognized as a function, but with only ... arg
# #l <- paste0(sort(l), "(")
# # This does not work!
# #attr(l, "types") <- rep(6, length.out = length(l))
# # This does not work!
# #attr(l, "helpHandler") <- gg_help_handler
# l
# }
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<!-- badges: end -->

{chart} propose a formula interface to {ggplot2}, and it also homogenize plot outputs from base R plots, {lattice} and {ggplot}. If labels and/or units attributes are defined for variables in the data, they are used automatically to construct the label (with units) of the axes.
{chart} proposes a formula interface to {ggplot2}, and it also homogenize plot outputs from base R plots, {lattice} and {ggplot}. If labels and/or units attributes are defined for variables in the data, they are used automatically to construct the label (with units) of the axes.

## Installation

Expand Down
2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- `autoplot()` and `plot()` methods (+ `hist()`, etc.)

- Implement `.DollarNames()` to get a list of `chart()` types available, with a mechanism to add more types from additional packages.

- Refine themes and look at themes for {lattice} and base.

- Use labels for base and {lattice} plots.
2 changes: 2 additions & 0 deletions inst/WORDLIST
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ devtools
ggplot
GitHub
OSX
pipeable
subsettable
thetics
viridis
63 changes: 63 additions & 0 deletions man/gg.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f5e354a

Please sign in to comment.