Skip to content

Commit 0cd0bac

Browse files
committed
Merge pull request #1527 from thomasp85/function_as_data
Function as data
2 parents 7be4c89 + 13be8a5 commit 0cd0bac

File tree

8 files changed

+90
-6
lines changed

8 files changed

+90
-6
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ S3method(format,facet)
3636
S3method(format,ggproto)
3737
S3method(format,ggproto_method)
3838
S3method(fortify,"NULL")
39+
S3method(fortify,"function")
3940
S3method(fortify,Line)
4041
S3method(fortify,Lines)
4142
S3method(fortify,Polygon)

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# ggplot2 2.0.0.9000
22

3+
* `layer()` now accepts a function as the data argument. The function will be
4+
applied to the data passed to the `ggplot()` function and must return a
5+
data.frame (#1527).
6+
37
* The theme can now modify the margins of legend title and text (#1502).
48

59
* `scale_size()` warns when used with categorical data.

R/fortify.r

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ fortify.data.frame <- function(model, data, ...) model
1616
#' @export
1717
fortify.NULL <- function(model, data, ...) waiver()
1818
#' @export
19+
fortify.function <- function(model, data, ...) model
20+
#' @export
1921
fortify.default <- function(model, data, ...) {
2022
stop(
2123
"ggplot2 doesn't know how to deal with data of class ",

R/layer.r

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
#' Create a new layer
22
#'
3+
#' A layer is a combination of data, stat and geom with a potential position
4+
#' adjustment. Usually layers are created using \code{geom_*} or \code{stat_*}
5+
#' calls but it can also be created directly using the \code{layer} function.
6+
#'
7+
#' @details
8+
#' The data in a layer can be specified in one of three ways:
9+
#'
10+
#' \itemize{
11+
#' \item{If the data argument is \code{NULL} (the default) the data is
12+
#' inherited from the global plot data as specified in the call to
13+
#' \code{\link{ggplot}}.}
14+
#' \item{If the data argument is a function, that function is called with the
15+
#' global data as the only argument and the return value is used as the layer
16+
#' data. The function must return a data.frame.}
17+
#' \item{Any other type of value passed to \code{data} will be passed through
18+
#' \code{\link{fortify}}, and there must thus be a \code{fortify} method
19+
#' defined for the class of the value. Passing a data.frame is a special case
20+
#' of this as \code{fortify.data.frame} returns the data.frame untouched.}
21+
#' }
22+
#'
323
#' @export
424
#' @inheritParams geom_point
525
#' @param geom,stat,position Geom, stat and position adjustment to use in
@@ -16,6 +36,13 @@
1636
#' layer(geom = "point", stat = "identity", position = "identity",
1737
#' params = list(na.rm = FALSE)
1838
#' )
39+
#'
40+
#' # use a function as data to plot a subset of global data
41+
#' ggplot(mpg, aes(displ, hwy)) +
42+
#' layer(geom = "point", stat = "identity", position = "identity",
43+
#' data = head, params = list(na.rm = FALSE)
44+
#' )
45+
#'
1946
layer <- function(geom = NULL, stat = NULL,
2047
data = NULL, mapping = NULL,
2148
position = NULL, params = list(),
@@ -101,6 +128,20 @@ Layer <- ggproto("Layer", NULL,
101128
cat(snakeize(class(self$position)[[1]]), "\n")
102129
},
103130

131+
layer_data = function(self, plot_data) {
132+
if (is.waive(self$data)) {
133+
data <- plot_data
134+
} else if (is.function(self$data)) {
135+
data <- self$data(plot_data)
136+
if (!is.data.frame(data)) {
137+
stop("Data function must return a data.frame", call. = FALSE)
138+
}
139+
} else {
140+
data <- self$data
141+
}
142+
data
143+
},
144+
104145
compute_aesthetics = function(self, data, plot) {
105146
# For annotation geoms, it is useful to be able to ignore the default aes
106147
if (self$inherit.aes) {

R/panel.r

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,8 @@ train_layout <- function(panel, facet, data, plot_data) {
4545
# @param panel a trained panel object
4646
# @param the facetting specification
4747
# @param data list of data frames (one for each layer)
48-
# @param plot_data default plot data frame
49-
map_layout <- function(panel, facet, data, plot_data) {
48+
map_layout <- function(panel, facet, data) {
5049
lapply(data, function(data) {
51-
if (is.waive(data)) data <- plot_data
5250
facet_map_layout(facet, data, panel$layout)
5351
})
5452
}

R/plot-build.r

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ ggplot_build <- function(plot) {
2222
}
2323

2424
layers <- plot$layers
25-
layer_data <- lapply(layers, function(y) y$data)
25+
layer_data <- lapply(layers, function(y) y$layer_data(plot$data))
2626

2727
scales <- plot$scales
2828
# Apply function to layer and matching data
@@ -39,7 +39,7 @@ ggplot_build <- function(plot) {
3939

4040
panel <- new_panel()
4141
panel <- train_layout(panel, plot$facet, layer_data, plot$data)
42-
data <- map_layout(panel, plot$facet, layer_data, plot$data)
42+
data <- map_layout(panel, plot$facet, layer_data)
4343

4444
# Compute aesthetics to produce data with generalised variable names
4545
data <- by_layer(function(l, d) l$compute_aesthetics(d, plot))

man/layer.Rd

Lines changed: 26 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-layer.r

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,16 @@ test_that("strip_dots remove dots around calculated aesthetics", {
3232
expect_equal(strip_dots(aes(sapply(..density.., function(x) mean(x)))$x),
3333
quote(sapply(density, function(x) mean(x))))
3434
})
35+
36+
# Data extraction ---------------------------------------------------------
37+
38+
test_that("layer_data returns a data.frame", {
39+
l <- geom_point()
40+
expect_equal(l$layer_data(mtcars), mtcars)
41+
l <- geom_point(data = head(mtcars))
42+
expect_equal(l$layer_data(mtcars), head(mtcars))
43+
l <- geom_point(data = head)
44+
expect_equal(l$layer_data(mtcars), head(mtcars))
45+
l <- geom_point(data = nrow)
46+
expect_error(l$layer_data(mtcars), "Data function must return a data.frame")
47+
})

0 commit comments

Comments
 (0)