Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: Boilerplate functions #6143

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
9 changes: 5 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ Collate:
'geom-.R'
'annotation-custom.R'
'annotation-logticks.R'
'scale-type.R'
'layer.R'
'boilerplates.R'
'geom-polygon.R'
'geom-map.R'
'annotation-map.R'
Expand Down Expand Up @@ -134,12 +137,14 @@ Collate:
'geom-abline.R'
'geom-rect.R'
'geom-bar.R'
'geom-tile.R'
'geom-bin2d.R'
'geom-blank.R'
'geom-boxplot.R'
'geom-col.R'
'geom-path.R'
'geom-contour.R'
'geom-point.R'
'geom-count.R'
'geom-crossbar.R'
'geom-segment.R'
Expand All @@ -159,15 +164,13 @@ Collate:
'geom-jitter.R'
'geom-label.R'
'geom-linerange.R'
'geom-point.R'
'geom-pointrange.R'
'geom-quantile.R'
'geom-rug.R'
'geom-sf.R'
'geom-smooth.R'
'geom-spoke.R'
'geom-text.R'
'geom-tile.R'
'geom-violin.R'
'geom-vline.R'
'ggplot2-package.R'
Expand All @@ -186,7 +189,6 @@ Collate:
'guide-colorbar.R'
'guide-colorsteps.R'
'guide-custom.R'
'layer.R'
'guide-none.R'
'guide-old.R'
'guides-.R'
Expand Down Expand Up @@ -236,7 +238,6 @@ Collate:
'scale-shape.R'
'scale-size.R'
'scale-steps.R'
'scale-type.R'
'scale-view.R'
'scale-viridis.R'
'scales-.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ S3method(as.data.frame,mapped_discrete)
S3method(as.list,ggproto)
S3method(autolayer,default)
S3method(autoplot,default)
S3method(boilerplate,Geom)
S3method(c,mapped_discrete)
S3method(drawDetails,zeroGrob)
S3method(element_grob,element_blank)
Expand Down Expand Up @@ -297,6 +298,7 @@ export(autolayer)
export(autoplot)
export(benchplot)
export(binned_scale)
export(boilerplate)
export(borders)
export(calc_element)
export(check_device)
Expand Down
129 changes: 129 additions & 0 deletions R/boilerplates.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#' @include layer.R
#' @include scale-type.R
NULL

#' Produce boilerplate constructors
#'
#' The `boilerplate()` functions sets up a user-facing constructor for ggproto
#' classes. Currently, `boilerplate()` is implemented for `Geom` classes.
#'
#' @param x An object to setup a constructor for.
#' @param ... Name-value pairs to use as additional arguments in the
#' constructor. For layers, these are passed on to [`layer(params)`][layer()].
#' @param checks Expressions evaluated before construction of the object.
#' Can be a `{}` block to include multiple expressions.
#'
#' @return A function
#' @export
#'
#' @examples
#' # For testing purposes, a geom that returns grobs
#' GeomTest <- ggproto(
#' "GeomTest", Geom,
#' draw_group = function(..., grob = grid::pointsGrob()) {
#' return(grob)
#' }
#' )
#' # Creating a constructor
#' geom_test <- boilerplate(GeomTest)
#'
#' # Note that `grob` is automatically an argument to the function
#' names(formals(geom_test))
#'
#' # Use in a plot
#' set.seed(1234)
#' p <- ggplot(mtcars, aes(disp, mpg))
#' p + geom_test()
#' p + geom_test(grob = grid::circleGrob())
boilerplate <- function(x, ...) {
UseMethod("boilerplate")

Check warning on line 39 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L39

Added line #L39 was not covered by tests
}

#' @export
boilerplate.Geom <- function(x, ..., checks, env = caller_env()) {

# Check that we can independently find the geom
geom <- gsub("^geom_", "", snake_class(x))
check_subclass(geom, "Geom", env = env)

Check warning on line 47 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L46-L47

Added lines #L46 - L47 were not covered by tests

# Split additional arguments into required and extra ones
args <- enexprs(...)
fixed_fmls_names <- c("mapping", "data", "stat", "position", "...",
"na.rm", "show.legend", "inherit.aes")
extra_args <- setdiff(names(args), fixed_fmls_names)
if ("geom" %in% extra_args) {
cli::cli_abort("{.arg geom} is a reserved argument.")

Check warning on line 55 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L50-L55

Added lines #L50 - L55 were not covered by tests
}

# Fill in values for parameters from draw functions
known_params <-
unique(c(names(args), fixed_fmls_names, "flipped_aes", x$aesthetics()))
missing_params <- setdiff(x$parameters(), known_params)
if (length(missing_params) > 0) {
draw_args <- ggproto_formals(x$draw_panel)
if ("..." %in% names(draw_args)) {
draw_args <- ggproto_formals(x$draw_group)

Check warning on line 65 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L59-L65

Added lines #L59 - L65 were not covered by tests
}
params <- intersect(missing_params, names(draw_args))
extra_args <- c(extra_args, params)
for (param in params) {
if (!identical(draw_args[[param]], quote(expr = ))) {
args[param] <- draw_args[param]

Check warning on line 71 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L67-L71

Added lines #L67 - L71 were not covered by tests
}
}
missing_params <- setdiff(missing_params, names(args))
if (length(missing_params) > 0) {
cli::cli_warn(
"In {.fn geom_{geom}}: please consider providing default values for: \\
{missing_params}."
)

Check warning on line 79 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L74-L79

Added lines #L74 - L79 were not covered by tests
}
}

# Build function formals
fmls <- list2(
mapping = args$mapping,
data = args$data,
stat = args$stat %||% "identity",
position = args$position %||% "identity",
`...` = quote(expr = ),
!!!args[extra_args],
na.rm = args$na.rm %||% FALSE,
show.legend = args$show.legend %||% NA,
inherit.aes = args$inherit.aes %||% TRUE
)

Check warning on line 94 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L84-L94

Added lines #L84 - L94 were not covered by tests
teunbrand marked this conversation as resolved.
Show resolved Hide resolved

if (length(extra_args) > 0) {
extra_args <- paste0(
"\n ", extra_args, " = ", extra_args, ",", collapse = ""
)

Check warning on line 99 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L96-L99

Added lines #L96 - L99 were not covered by tests
teunbrand marked this conversation as resolved.
Show resolved Hide resolved
}

body <- paste0("
layer(
data = data,
mapping = mapping,
stat = stat,
geom = \"", geom, "\",
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list2(
na.rm = na.rm,",
extra_args, "
...
)
)
")
body <- str2lang(body)

Check warning on line 118 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L102-L118

Added lines #L102 - L118 were not covered by tests
teunbrand marked this conversation as resolved.
Show resolved Hide resolved

checks <- substitute(checks)
if (!missing(checks)) {
if (is_call(checks, "{")) {
checks[[1]] <- NULL

Check warning on line 123 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L120-L123

Added lines #L120 - L123 were not covered by tests
}
body <- inject(quote(`{`(!!!c(checks, body))))

Check warning on line 125 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L125

Added line #L125 was not covered by tests
teunbrand marked this conversation as resolved.
Show resolved Hide resolved
}
teunbrand marked this conversation as resolved.
Show resolved Hide resolved

new_function(fmls, body)

Check warning on line 128 in R/boilerplates.R

View check run for this annotation

Codecov / codecov/patch

R/boilerplates.R#L128

Added line #L128 was not covered by tests
teunbrand marked this conversation as resolved.
Show resolved Hide resolved
}
111 changes: 45 additions & 66 deletions R/geom-bar.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
#' @rdname ggplot2-ggproto
#' @format NULL
#' @usage NULL
#' @export
#' @include geom-rect.R
GeomBar <- ggproto("GeomBar", GeomRect,
required_aes = c("x", "y"),

# These aes columns are created by setup_data(). They need to be listed here so
# that GeomRect$handle_na() properly removes any bars that fall outside the defined
# limits, not just those for which x and y are outside the limits
non_missing_aes = c("xmin", "xmax", "ymin", "ymax"),

default_aes = aes(!!!GeomRect$default_aes, width = NULL),

setup_params = function(data, params) {
params$flipped_aes <- has_flipped_aes(data, params)
params
},

extra_params = c("just", "na.rm", "orientation"),

setup_data = function(data, params) {
data$flipped_aes <- params$flipped_aes
data <- flip_data(data, params$flipped_aes)
data$width <- data$width %||%
params$width %||% (min(vapply(
split(data$x, data$PANEL, drop = TRUE),
resolution, numeric(1), zero = FALSE
)) * 0.9)
data$just <- params$just %||% 0.5
data <- transform(data,
ymin = pmin(y, 0), ymax = pmax(y, 0),
xmin = x - width * just, xmax = x + width * (1 - just),
width = NULL, just = NULL
)
flip_data(data, params$flipped_aes)
},

rename_size = TRUE
)

#' Bar charts
#'
#' There are two types of bar charts: `geom_bar()` and `geom_col()`.
Expand Down Expand Up @@ -48,6 +90,8 @@
#' @param geom,stat Override the default connection between `geom_bar()` and
#' `stat_count()`. For more information about overriding these connections,
#' see how the [stat][layer_stats] and [geom][layer_geoms] arguments work.
#' @param lineend Line end style (round, butt, square).
#' @param linejoin Line join style (round, mitre, bevel).
#' @examples
#' # geom_bar is designed to make it easy to create bar charts that show
#' # counts (or sums of weights)
Expand Down Expand Up @@ -92,69 +136,4 @@
#' ggplot(df, aes(x, y)) + geom_col(just = 0.5)
#' # Columns begin on the first day of the month
#' ggplot(df, aes(x, y)) + geom_col(just = 1)
geom_bar <- function(mapping = NULL, data = NULL,
stat = "count", position = "stack",
...,
just = 0.5,
na.rm = FALSE,
orientation = NA,
show.legend = NA,
inherit.aes = TRUE) {
layer(
data = data,
mapping = mapping,
stat = stat,
geom = GeomBar,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list2(
just = just,
na.rm = na.rm,
orientation = orientation,
...
)
)
}

#' @rdname ggplot2-ggproto
#' @format NULL
#' @usage NULL
#' @export
#' @include geom-rect.R
GeomBar <- ggproto("GeomBar", GeomRect,
required_aes = c("x", "y"),

# These aes columns are created by setup_data(). They need to be listed here so
# that GeomRect$handle_na() properly removes any bars that fall outside the defined
# limits, not just those for which x and y are outside the limits
non_missing_aes = c("xmin", "xmax", "ymin", "ymax"),

default_aes = aes(!!!GeomRect$default_aes, width = NULL),

setup_params = function(data, params) {
params$flipped_aes <- has_flipped_aes(data, params)
params
},

extra_params = c("just", "na.rm", "orientation"),

setup_data = function(data, params) {
data$flipped_aes <- params$flipped_aes
data <- flip_data(data, params$flipped_aes)
data$width <- data$width %||%
params$width %||% (min(vapply(
split(data$x, data$PANEL, drop = TRUE),
resolution, numeric(1), zero = FALSE
)) * 0.9)
data$just <- params$just %||% 0.5
data <- transform(data,
ymin = pmin(y, 0), ymax = pmax(y, 0),
xmin = x - width * just, xmax = x + width * (1 - just),
width = NULL, just = NULL
)
flip_data(data, params$flipped_aes)
},

rename_size = TRUE
)
geom_bar <- boilerplate(GeomBar, stat = "count", position = "stack", just = 0.5)
27 changes: 6 additions & 21 deletions R/geom-bin2d.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#' @include geom-tile.R
NULL

#' Heatmap of 2d bin counts
#'
#' Divides the plane into rectangles, counts the number of cases in
Expand All @@ -14,6 +17,8 @@
#' `geom_bin_2d()` and `stat_bin_2d()`. For more information about overriding
#' these connections, see how the [stat][layer_stats] and [geom][layer_geoms]
#' arguments work.
#' @param lineend Line end style (round, butt, square).
#' @param linejoin Line join style (round, mitre, bevel).
#' @seealso [stat_bin_hex()] for hexagonal binning
#' @examples
#' d <- ggplot(diamonds, aes(x, y)) + xlim(4, 10) + ylim(4, 10)
Expand All @@ -26,27 +31,7 @@
#'
#' # Or by specifying the width of the bins
#' d + geom_bin_2d(binwidth = c(0.1, 0.1))
geom_bin_2d <- function(mapping = NULL, data = NULL,
stat = "bin2d", position = "identity",
...,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {

layer(
data = data,
mapping = mapping,
stat = stat,
geom = GeomTile,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list2(
na.rm = na.rm,
...
)
)
}
geom_bin_2d <- boilerplate(GeomTile, stat = "bin2d")

#' @export
#' @rdname geom_bin_2d
Expand Down
Loading
Loading