Skip to content

Commit 6c975e8

Browse files
authored
Let Layer$compute_geom_2() handle legend defaults (#5903)
* use ellipses in `use_defaults()`/`compute_geom_2()` * return empty keys as-is * let `compute_geom_2()` handle populating defaults * ensure legends can be rendered for unrelated geoms * add visual tests for geom_sf legend types * simplify sf legend type detection * geom_sf can compute defaults for legend * remove vestigial `default_aesthetics()`
1 parent 28aec3a commit 6c975e8

10 files changed

+357
-71
lines changed

R/geom-.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ 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(), default_aes = NULL) {
117+
use_defaults = function(self, data, params = list(), modifiers = aes(), default_aes = NULL, ...) {
118118
default_aes <- default_aes %||% self$default_aes
119119

120120
# Inherit size as linewidth if no linewidth aesthetic and param exist

R/geom-sf.R

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,21 @@ GeomSf <- ggproto("GeomSf", Geom,
131131
stroke = 0.5
132132
),
133133

134-
use_defaults = function(self, data, params = list(), modifiers = aes(), default_aes = NULL) {
134+
use_defaults = function(self, data, params = list(), modifiers = aes(),
135+
default_aes = NULL, ...) {
135136
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
137137
if (!"geometry" %in% names(data)) {
138138
return(data)
139139
}
140140

141+
# geometry column is a character if we're populating legend keys
142+
type <- if (is.character(data$geometry)) {
143+
data$geometry
144+
} else {
145+
sf_types[sf::st_geometry_type(data$geometry)]
146+
}
147+
141148
# Devise splitting index for geometry types
142-
type <- sf_types[sf::st_geometry_type(data$geometry)]
143149
type <- factor(type, c("point", "line", "other", "collection"))
144150
index <- split(seq_len(nrow(data)), type)
145151

@@ -202,27 +208,15 @@ GeomSf <- ggproto("GeomSf", Geom,
202208
},
203209

204210
draw_key = function(data, params, size) {
205-
data <- modify_list(default_aesthetics(params$legend), data)
206-
if (params$legend == "point") {
207-
draw_key_point(data, params, size)
208-
} else if (params$legend == "line") {
209-
draw_key_path(data, params, size)
210-
} else {
211+
switch(
212+
params$legend %||% "other",
213+
point = draw_key_point(data, params, size),
214+
line = draw_key_path(data, params, size),
211215
draw_key_polygon(data, params, size)
212-
}
216+
)
213217
}
214218
)
215219

216-
default_aesthetics <- function(type) {
217-
if (type == "point") {
218-
GeomPoint$default_aes
219-
} else if (type == "line") {
220-
GeomLine$default_aes
221-
} else {
222-
modify_list(GeomPolygon$default_aes, list(fill = "grey90", colour = "grey35"))
223-
}
224-
}
225-
226220
sf_grob <- function(x, lineend = "butt", linejoin = "round", linemitre = 10,
227221
arrow = NULL, arrow.fill = NULL, na.rm = TRUE) {
228222
type <- sf_types[sf::st_geometry_type(x$geometry)]

R/guide-legend.R

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -225,31 +225,36 @@ GuideLegend <- ggproto(
225225

226226
get_layer_key = function(params, layers, data) {
227227

228+
# Return empty guides as-is
229+
if (nrow(params$key) < 1) {
230+
return(params)
231+
}
232+
228233
decor <- Map(layer = layers, df = data, f = function(layer, df) {
229234

235+
# Subset key to the column with aesthetic matching the layer
230236
matched_aes <- matched_aes(layer, params)
237+
key <- params$key[matched_aes]
238+
key$.id <- seq_len(nrow(key))
231239

232-
if (length(matched_aes) > 0) {
233-
# Filter out aesthetics that can't be applied to the legend
234-
n <- lengths(layer$aes_params, use.names = FALSE)
235-
layer_params <- layer$aes_params[n == 1]
240+
# Filter static aesthetics to those with single values
241+
single_params <- lengths(layer$aes_params) == 1L
242+
single_params <- layer$aes_params[single_params]
236243

237-
aesthetics <- layer$computed_mapping
238-
is_modified <- is_scaled_aes(aesthetics) | is_staged_aes(aesthetics)
239-
modifiers <- aesthetics[is_modified]
244+
# Use layer to populate defaults
245+
key <- layer$compute_geom_2(key, single_params)
240246

241-
data <- layer$geom$use_defaults(params$key[matched_aes], layer_params, modifiers)
242-
data$.draw <- keep_key_data(params$key, df, matched_aes, layer$show.legend)
243-
} else {
244-
reps <- rep(1, nrow(params$key))
245-
data <- layer$geom$use_defaults(NULL, layer$aes_params)[reps, ]
247+
# Filter non-existing levels
248+
if (length(matched_aes) > 0) {
249+
key$.draw <- keep_key_data(params$key, df, matched_aes, layer$show.legend)
246250
}
247251

248-
data <- modify_list(data, params$override.aes)
252+
# Apply overrides
253+
key <- modify_list(key, params$override.aes)
249254

250255
list(
251256
draw_key = layer$geom$draw_key,
252-
data = data,
257+
data = key,
253258
params = c(layer$computed_geom_params, layer$computed_stat_params)
254259
)
255260
})

R/layer-sf.R

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ layer_sf <- function(geom = NULL, stat = NULL,
3838
LayerSf <- ggproto("LayerSf", Layer,
3939
legend_key_type = NULL,
4040

41-
# This field carry state throughout rendering but will always be
42-
# calculated before use
43-
computed_legend_key_type = NULL,
44-
4541
setup_layer = function(self, data, plot) {
4642
# process generic layer setup first
4743
data <- ggproto_parent(Layer, self)$setup_layer(data, plot)
@@ -56,35 +52,28 @@ LayerSf <- ggproto("LayerSf", Layer,
5652
self$computed_mapping$geometry <- sym(geometry_col)
5753
}
5854
}
59-
60-
# automatically determine the legend type
61-
if (is.null(self$legend_key_type)) {
62-
# first, set default value in case downstream tests fail
63-
self$computed_legend_key_type <- "polygon"
64-
65-
# now check if the type should not be polygon
66-
if (!is.null(self$computed_mapping$geometry) && quo_is_symbol(self$computed_mapping$geometry)) {
67-
geometry_column <- as_name(self$computed_mapping$geometry)
68-
if (inherits(data[[geometry_column]], "sfc")) {
69-
sf_type <- detect_sf_type(data[[geometry_column]])
70-
if (sf_type == "point") {
71-
self$computed_legend_key_type <- "point"
72-
} else if (sf_type == "line") {
73-
self$computed_legend_key_type <- "line"
74-
}
75-
}
76-
}
77-
} else {
78-
self$computed_legend_key_type <- self$legend_key_type
79-
}
8055
data
8156
},
8257
compute_geom_1 = function(self, data) {
8358
data <- ggproto_parent(Layer, self)$compute_geom_1(data)
8459

60+
# Determine the legend type
61+
legend_type <- self$legend_key_type
62+
if (is.null(legend_type)) {
63+
legend_type <- switch(
64+
detect_sf_type(data$geometry),
65+
point = "point", line = "line", "other"
66+
)
67+
}
68+
8569
# Add legend type after computed_geom_params has been calculated
86-
self$computed_geom_params$legend <- self$computed_legend_key_type
70+
self$computed_geom_params$legend <- legend_type
8771
data
72+
},
73+
74+
compute_geom_2 = function(self, data, params = self$aes_params, ...) {
75+
data$geometry <- data$geometry %||% self$computed_geom_params$legend
76+
ggproto_parent(Layer, self)$compute_geom_2(data, params, ...)
8877
}
8978
)
9079

@@ -113,6 +102,9 @@ scale_type.sfc <- function(x) "identity"
113102

114103
# helper function to determine the geometry type of sf object
115104
detect_sf_type <- function(sf) {
105+
if (is.null(sf)) {
106+
return("other")
107+
}
116108
geometry_type <- unique0(as.character(sf::st_geometry_type(sf)))
117109
if (length(geometry_type) != 1) geometry_type <- "GEOMETRY"
118110
sf_types[geometry_type]

R/layer.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,14 +438,14 @@ Layer <- ggproto("Layer", NULL,
438438
self$position$compute_layer(data, params, layout)
439439
},
440440

441-
compute_geom_2 = function(self, data) {
441+
compute_geom_2 = function(self, data, params = self$aes_params, ...) {
442442
# Combine aesthetics, defaults, & params
443443
if (empty(data)) return(data)
444444

445445
aesthetics <- self$computed_mapping
446446
modifiers <- aesthetics[is_scaled_aes(aesthetics) | is_staged_aes(aesthetics)]
447447

448-
self$geom$use_defaults(data, self$aes_params, modifiers)
448+
self$geom$use_defaults(data, params, modifiers, ...)
449449
},
450450

451451
finish_statistics = function(self, data) {
Lines changed: 82 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)