Skip to content

Commit 8e63882

Browse files
authored
Refactor applying defaults in geom_sf() (#5834)
* add test for mixed geometry types * allow custom defaults in `Geom$use_defaults()` * Implement `GeomSf$use_defaults()` * trim default setting in `sf_grob()` * use `defer()` in case test fails * add news bullet
1 parent 8fa0fb4 commit 8e63882

File tree

5 files changed

+194
-25
lines changed

5 files changed

+194
-25
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* (Internal) Applying defaults in `geom_sf()` has moved from the internal
4+
`sf_grob()` to `GeomSf$use_defaults()` (@teunbrand).
35
* `facet_wrap()` has new options for the `dir` argument to more precisely
46
control panel directions (@teunbrand, #5212)
57
* Prevented `facet_wrap(..., drop = FALSE)` from throwing spurious errors when

R/geom-.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ Geom <- ggproto("Geom",
114114
setup_data = function(data, params) data,
115115

116116
# Combine data with defaults and set aesthetics from parameters
117-
use_defaults = function(self, data, params = list(), modifiers = aes()) {
118-
default_aes <- self$default_aes
117+
use_defaults = function(self, data, params = list(), modifiers = aes(), default_aes = NULL) {
118+
default_aes <- default_aes %||% self$default_aes
119119

120120
# Inherit size as linewidth if no linewidth aesthetic and param exist
121121
if (self$rename_size && is.null(data$linewidth) && is.null(params$linewidth)) {

R/geom-sf.R

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,63 @@ GeomSf <- ggproto("GeomSf", Geom,
131131
stroke = 0.5
132132
),
133133

134+
use_defaults = function(self, data, params = list(), modifiers = aes(), default_aes = NULL) {
135+
data <- ggproto_parent(Geom, self)$use_defaults(data, params, modifiers, default_aes)
136+
# Early exit for e.g. legend data that don't have geometry columns
137+
if (!"geometry" %in% names(data)) {
138+
return(data)
139+
}
140+
141+
# Devise splitting index for geometry types
142+
type <- sf_types[sf::st_geometry_type(data$geometry)]
143+
type <- factor(type, c("point", "line", "other", "collection"))
144+
index <- split(seq_len(nrow(data)), type)
145+
146+
# Initialise parts of the data
147+
points <- lines <- others <- collections <- NULL
148+
149+
# Go through every part, applying different defaults
150+
if (length(index$point) > 0) {
151+
points <- GeomPoint$use_defaults(
152+
vec_slice(data, index$point),
153+
params, modifiers
154+
)
155+
}
156+
if (length(index$line) > 0) {
157+
lines <- GeomLine$use_defaults(
158+
vec_slice(data, index$line),
159+
params, modifiers
160+
)
161+
}
162+
other_default <- modify_list(
163+
GeomPolygon$default_aes,
164+
list(fill = "grey90", colour = "grey35", linewidth = 0.2)
165+
)
166+
if (length(index$other) > 0) {
167+
others <- GeomPolygon$use_defaults(
168+
vec_slice(data, index$other),
169+
params, modifiers,
170+
default_aes = other_default
171+
)
172+
}
173+
if (length(index$collection) > 0) {
174+
modified <- rename(
175+
GeomPoint$default_aes,
176+
c(fill = "point_fill")
177+
)
178+
modified <- modify_list(other_default, modified)
179+
collections <- Geom$use_defaults(
180+
vec_slice(data, index$collection),
181+
params, modifiers,
182+
default_aes = modified
183+
)
184+
}
185+
186+
# Recombine data in original order
187+
data <- vec_c(points, lines, others, collections)
188+
vec_slice(data, order(unlist(index)))
189+
},
190+
134191
draw_panel = function(self, data, panel_params, coord, legend = NULL,
135192
lineend = "butt", linejoin = "round", linemitre = 10,
136193
arrow = NULL, na.rm = TRUE) {
@@ -189,36 +246,24 @@ sf_grob <- function(x, lineend = "butt", linejoin = "round", linemitre = 10,
189246
type_ind <- type_ind[!remove]
190247
is_collection <- is_collection[!remove]
191248
}
192-
defaults <- list(
193-
GeomPoint$default_aes,
194-
GeomLine$default_aes,
195-
modify_list(GeomPolygon$default_aes, list(fill = "grey90", colour = "grey35", linewidth = 0.2))
196-
)
197-
defaults[[4]] <- modify_list(
198-
defaults[[3]],
199-
rename(GeomPoint$default_aes, c(size = "point_size", fill = "point_fill"))
200-
)
201-
default_names <- unique0(unlist(lapply(defaults, names)))
202-
defaults <- lapply(setNames(default_names, default_names), function(n) {
203-
unlist(lapply(defaults, function(def) def[[n]] %||% NA))
204-
})
205-
alpha <- x$alpha %||% defaults$alpha[type_ind]
206-
col <- x$colour %||% defaults$colour[type_ind]
249+
250+
alpha <- x$alpha %||% NA
251+
fill <- fill_alpha(x$fill %||% NA, alpha)
252+
col <- x$colour %||% NA
207253
col[is_point | is_line] <- alpha(col[is_point | is_line], alpha[is_point | is_line])
208-
fill <- x$fill %||% defaults$fill[type_ind]
209-
fill <- fill_alpha(fill, alpha)
210-
size <- x$size %||% defaults$size[type_ind]
211-
linewidth <- x$linewidth %||% defaults$linewidth[type_ind]
254+
255+
size <- x$size %||% 0.5
256+
linewidth <- x$linewidth %||% 0.5
212257
point_size <- ifelse(
213258
is_collection,
214-
x$size %||% defaults$point_size[type_ind],
259+
x$size,
215260
ifelse(is_point, size, linewidth)
216261
)
217-
stroke <- (x$stroke %||% defaults$stroke[1]) * .stroke / 2
262+
stroke <- (x$stroke %||% 0) * .stroke / 2
218263
fontsize <- point_size * .pt + stroke
219264
lwd <- ifelse(is_point, stroke, linewidth * .pt)
220-
pch <- x$shape %||% defaults$shape[type_ind]
221-
lty <- x$linetype %||% defaults$linetype[type_ind]
265+
pch <- x$shape
266+
lty <- x$linetype
222267
gp <- gpar(
223268
col = col, fill = fill, fontsize = fontsize, lwd = lwd, lty = lty,
224269
lineend = lineend, linejoin = linejoin, linemitre = linemitre
Lines changed: 85 additions & 0 deletions
Loading

tests/testthat/test-geom-sf.R

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,43 @@ test_that("geom_sf draws correctly", {
196196
)
197197
})
198198

199+
test_that("geom_sf uses combinations of geometry correctly", {
200+
skip_if_not_installed("sf")
201+
202+
t <- seq(0, 2 *pi, length.out = 10)
203+
data <- sf::st_sf(sf::st_sfc(
204+
sf::st_multipoint(cbind(1:2, 3:4)),
205+
sf::st_multilinestring(list(
206+
cbind(c(1, 1.8), c(3.8, 3)),
207+
cbind(c(1.2, 2), c(4, 3.2))
208+
)),
209+
sf::st_polygon(list(
210+
cbind(cos(t), zapsmall(sin(t))),
211+
cbind(cos(t), zapsmall(sin(t))) + 5
212+
)),
213+
sf::st_geometrycollection(x = list(
214+
sf::st_point(x = c(3, 2)),
215+
sf::st_linestring(cbind(c(2, 4, 4), c(1, 1, 3)))
216+
)),
217+
sf::st_linestring(x = cbind(c(2, 6), c(-1, 3))),
218+
sf::st_point(c(5, 0))
219+
))
220+
221+
update_geom_defaults("point", list(colour = "blue"))
222+
update_geom_defaults("line", list(colour = "red"))
223+
# Note: polygon defaults are mostly ignored or overridden
224+
225+
withr::defer({
226+
update_geom_defaults("point", NULL)
227+
update_geom_defaults("line", NULL)
228+
})
229+
230+
expect_doppelganger(
231+
"mixed geometry types",
232+
ggplot(data) + geom_sf()
233+
)
234+
})
235+
199236
test_that("geom_sf_text() and geom_sf_label() draws correctly", {
200237
skip_if_not_installed("sf")
201238
if (packageVersion("sf") < "0.5.3") skip("Need sf 0.5.3")

0 commit comments

Comments
 (0)