Skip to content

Commit 4380cb9

Browse files
authored
Allow creation of custom layers that have access to global plot data (#2875)
* allow creation of custom layers that have access to global plot data * properly respect inherit.aes when looking for geometry column * isFALSE() doesn't exist in R 3.4 * incorporate suggested changes by yutannihilation * Move `setup_layer()` to plot build. * update news item. fixes #2872
1 parent 92d2777 commit 4380cb9

File tree

8 files changed

+73
-56
lines changed

8 files changed

+73
-56
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export(GeomText)
180180
export(GeomTile)
181181
export(GeomViolin)
182182
export(GeomVline)
183+
export(LayerSf)
183184
export(Layout)
184185
export(Position)
185186
export(PositionDodge)

NEWS.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
# ggplot2 3.1.0.9000
22

3-
* `geom_rug()` now works with `coord_flip()` (@has2k1, #2987).
3+
* `geom_rug()` now works with `coord_flip()` (@has2k1, #2987).
4+
5+
* Layers now have a new member function `setup_layer()` which is called at the
6+
very beginning of the plot building process and which has access to the original
7+
input data and the plot object being built. This function allows the creation of
8+
custom layers that autogenerate aesthetic mappings based on the input data or that
9+
filter the input data in some form. One example is the new `LayerSf` class which
10+
locates the geometry column in sf objects and sets up an aesthetic mapping for it
11+
(@clauswilke, #2872).
412

513
* Default labels are now generated more consistently; e.g., symbols no longer
614
get backticks, and long expressions are abbreviated with `...`
715
(@yutannihilation, #2981).
816

917
* Aesthetic mappings now accept functions that return `NULL` (@yutannihilation,
10-
#2997)
18+
#2997).
1119

1220
* Closed arrows in `element_line()` are now filled (@yutannihilation, #2924).
1321

R/layer.r

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
#' supplied parameters and aesthetics are understood by the `geom` or
4242
#' `stat`. Use `FALSE` to suppress the checks.
4343
#' @param params Additional parameters to the `geom` and `stat`.
44+
#' @param layer_class The type of layer object to be constructued. This allows
45+
#' the creation of custom layers. Can usually be left at its default.
4446
#' @keywords internal
4547
#' @examples
4648
#' # geom calls are just a short cut for layer
@@ -61,7 +63,7 @@ layer <- function(geom = NULL, stat = NULL,
6163
data = NULL, mapping = NULL,
6264
position = NULL, params = list(),
6365
inherit.aes = TRUE, check.aes = TRUE, check.param = TRUE,
64-
show.legend = NA) {
66+
show.legend = NA, layer_class = Layer) {
6567
if (is.null(geom))
6668
stop("Attempted to create layer with no geom.", call. = FALSE)
6769
if (is.null(stat))
@@ -130,7 +132,7 @@ layer <- function(geom = NULL, stat = NULL,
130132
)
131133
}
132134

133-
ggproto("LayerInstance", Layer,
135+
ggproto("LayerInstance", layer_class,
134136
geom = geom,
135137
geom_params = geom_params,
136138
stat = stat,
@@ -197,6 +199,12 @@ Layer <- ggproto("Layer", NULL,
197199
}
198200
},
199201

202+
# hook to allow a layer access to the final layer data
203+
# in input form and to global plot info
204+
setup_layer = function(self, data, plot) {
205+
data
206+
},
207+
200208
compute_aesthetics = function(self, data, plot) {
201209
# For annotation geoms, it is useful to be able to ignore the default aes
202210
if (self$inherit.aes) {

R/plot-build.r

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ ggplot_build.ggplot <- function(plot) {
3939
out
4040
}
4141

42+
# Allow all layers to make any final adjustments based
43+
# on raw input data and plot info
44+
data <- layer_data
45+
data <- by_layer(function(l, d) l$setup_layer(d, plot))
46+
4247
# Initialise panels, add extra data for margins & missing faceting
4348
# variables, and add on a PANEL variable to data
4449
layout <- create_layout(plot$facet, plot$coordinates)
45-
data <- layout$setup(layer_data, plot$data, plot$plot_env)
50+
data <- layout$setup(data, plot$data, plot$plot_env)
4651

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

R/sf.R

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,37 @@ stat_sf <- function(mapping = NULL, data = NULL, geom = "rect",
135135
na.rm = na.rm,
136136
legend = if (is.character(show.legend)) show.legend else "polygon",
137137
...
138-
)
138+
),
139+
layer_class = LayerSf
139140
)
140141
}
141142

142143

144+
# A special sf layer that auto-maps geometry data to the `geometry` aesthetic
145+
146+
#' @export
147+
#' @rdname ggsf
148+
#' @usage NULL
149+
#' @format NULL
150+
LayerSf <- ggproto("LayerSf", Layer,
151+
setup_layer = function(self, data, plot) {
152+
# process generic layer setup first
153+
data <- ggproto_parent(Layer, self)$setup_layer(data, plot)
154+
155+
# automatically determine the name of the geometry column
156+
# and add the mapping if it doesn't exist
157+
if ((isTRUE(self$inherit.aes) && is.null(self$mapping$geometry) && is.null(plot$mapping$geometry)) ||
158+
(!isTRUE(self$inherit.aes) && is.null(self$mapping$geometry))) {
159+
if (is_sf(data)) {
160+
geometry_col <- attr(data, "sf_column")
161+
self$mapping$geometry <- as.name(geometry_col)
162+
}
163+
}
164+
data
165+
}
166+
)
167+
168+
143169
# geom --------------------------------------------------------------------
144170

145171
#' @export
@@ -234,17 +260,6 @@ sf_grob <- function(row, lineend = "butt", linejoin = "round", linemitre = 10) {
234260
geom_sf <- function(mapping = aes(), data = NULL, stat = "sf",
235261
position = "identity", na.rm = FALSE, show.legend = NA,
236262
inherit.aes = TRUE, ...) {
237-
238-
# Automatically determin name of geometry column
239-
if (!is.null(data) && is_sf(data)) {
240-
geometry_col <- attr(data, "sf_column")
241-
} else {
242-
geometry_col <- "geometry"
243-
}
244-
if (is.null(mapping$geometry)) {
245-
mapping$geometry <- as.name(geometry_col)
246-
}
247-
248263
c(
249264
layer(
250265
geom = GeomSf,
@@ -258,7 +273,8 @@ geom_sf <- function(mapping = aes(), data = NULL, stat = "sf",
258273
na.rm = na.rm,
259274
legend = if (is.character(show.legend)) show.legend else "polygon",
260275
...
261-
)
276+
),
277+
layer_class = LayerSf
262278
),
263279
coord_sf(default = TRUE)
264280
)
@@ -282,16 +298,6 @@ geom_sf_label <- function(mapping = aes(), data = NULL,
282298
inherit.aes = TRUE,
283299
fun.geometry = NULL) {
284300

285-
# Automatically determin name of geometry column
286-
if (!is.null(data) && is_sf(data)) {
287-
geometry_col <- attr(data, "sf_column")
288-
} else {
289-
geometry_col <- "geometry"
290-
}
291-
if (is.null(mapping$geometry)) {
292-
mapping$geometry <- as.name(geometry_col)
293-
}
294-
295301
if (!missing(nudge_x) || !missing(nudge_y)) {
296302
if (!missing(position)) {
297303
stop("Specify either `position` or `nudge_x`/`nudge_y`", call. = FALSE)
@@ -316,7 +322,8 @@ geom_sf_label <- function(mapping = aes(), data = NULL,
316322
na.rm = na.rm,
317323
fun.geometry = fun.geometry,
318324
...
319-
)
325+
),
326+
layer_class = LayerSf
320327
)
321328
}
322329

@@ -335,15 +342,6 @@ geom_sf_text <- function(mapping = aes(), data = NULL,
335342
show.legend = NA,
336343
inherit.aes = TRUE,
337344
fun.geometry = NULL) {
338-
# Automatically determin name of geometry column
339-
if (!is.null(data) && is_sf(data)) {
340-
geometry_col <- attr(data, "sf_column")
341-
} else {
342-
geometry_col <- "geometry"
343-
}
344-
if (is.null(mapping$geometry)) {
345-
mapping$geometry <- as.name(geometry_col)
346-
}
347345

348346
if (!missing(nudge_x) || !missing(nudge_y)) {
349347
if (!missing(position)) {
@@ -367,7 +365,8 @@ geom_sf_text <- function(mapping = aes(), data = NULL,
367365
na.rm = na.rm,
368366
fun.geometry = fun.geometry,
369367
...
370-
)
368+
),
369+
layer_class = LayerSf
371370
)
372371
}
373372

R/stat-sf-coordinates.R

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#' the line. `sf::st_zm()` is needed to drop Z and M dimension beforehand,
2424
#' otherwise `sf::st_point_on_surface()` may fail when the geometries have M
2525
#' dimension.
26-
#'
26+
#'
2727
#' @section Computed variables:
2828
#' \describe{
2929
#' \item{x}{X dimension of the simple feature}
@@ -33,10 +33,10 @@
3333
#' @examples
3434
#' if (requireNamespace("sf", quietly = TRUE)) {
3535
#' nc <- sf::st_read(system.file("shape/nc.shp", package="sf"))
36-
#'
36+
#'
3737
#' ggplot(nc) +
3838
#' stat_sf_coordinates()
39-
#'
39+
#'
4040
#' ggplot(nc) +
4141
#' geom_errorbarh(
4242
#' aes(geometry = geometry,
@@ -47,7 +47,7 @@
4747
#' stat = "sf_coordinates"
4848
#' )
4949
#' }
50-
#'
50+
#'
5151
#' @export
5252
#' @inheritParams stat_identity
5353
#' @inheritParams geom_point
@@ -62,16 +62,6 @@ stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point",
6262
show.legend = NA, inherit.aes = TRUE,
6363
fun.geometry = NULL,
6464
...) {
65-
# Automatically determin name of geometry column
66-
if (!is.null(data) && is_sf(data)) {
67-
geometry_col <- attr(data, "sf_column")
68-
} else {
69-
geometry_col <- "geometry"
70-
}
71-
if (is.null(mapping$geometry)) {
72-
mapping$geometry <- as.name(geometry_col)
73-
}
74-
7565
layer(
7666
stat = StatSfCoordinates,
7767
data = data,
@@ -84,7 +74,8 @@ stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point",
8474
na.rm = na.rm,
8575
fun.geometry = fun.geometry,
8676
...
87-
)
77+
),
78+
layer_class = LayerSf
8879
)
8980
}
9081

@@ -98,7 +89,7 @@ StatSfCoordinates <- ggproto(
9889
if (is.null(fun.geometry)) {
9990
fun.geometry <- function(x) sf::st_point_on_surface(sf::st_zm(x))
10091
}
101-
92+
10293
points_sfc <- fun.geometry(data$geometry)
10394
coordinates <- sf::st_coordinates(points_sfc)
10495
data$x <- coordinates[, "X"]

man/ggsf.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/layer.Rd

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

0 commit comments

Comments
 (0)