Skip to content

Commit cd6f7ca

Browse files
authored
Customizable key glyphs (#3236)
* implement customizable key glyphs. closes #3145 * provide all draw_key functions with sane defaults. * fix comment * improve news item * add unit tests for customizable key glyphs
1 parent a92042d commit cd6f7ca

File tree

9 files changed

+303
-51
lines changed

9 files changed

+303
-51
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export(draw_key_polygon)
273273
export(draw_key_rect)
274274
export(draw_key_smooth)
275275
export(draw_key_text)
276+
export(draw_key_timeseries)
276277
export(draw_key_vline)
277278
export(draw_key_vpath)
278279
export(dup_axis)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ core developer team.
3535
* `geom_rug()` gains an "outside" option to allow for moving the rug tassels to
3636
outside the plot area (@njtierney, #3085) and a `length` option to allow for
3737
changing the length of the rug lines (@daniel-wells, #3109).
38+
39+
* All geoms now take a `key_glyph` paramter that allows users to customize
40+
how legend keys are drawn (@clauswilke, #3145). In addition, a new key glyph
41+
`timeseries` is provided to draw nice legends for time series
42+
(@mitchelloharawild, #3145).
3843

3944
## Extensions
4045

R/layer.r

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
#' supplied parameters and aesthetics are understood by the `geom` or
4343
#' `stat`. Use `FALSE` to suppress the checks.
4444
#' @param params Additional parameters to the `geom` and `stat`.
45+
#' @param key_glyph A legend key drawing function or a string providing the
46+
#' function name minus the `draw_key_` prefix. See [draw_key] for details.
4547
#' @param layer_class The type of layer object to be constructued. This is
4648
#' intended for ggplot2 internal use only.
4749
#' @keywords internal
@@ -64,7 +66,7 @@ layer <- function(geom = NULL, stat = NULL,
6466
data = NULL, mapping = NULL,
6567
position = NULL, params = list(),
6668
inherit.aes = TRUE, check.aes = TRUE, check.param = TRUE,
67-
show.legend = NA, layer_class = Layer) {
69+
show.legend = NA, key_glyph = NULL, layer_class = Layer) {
6870
if (is.null(geom))
6971
stop("Attempted to create layer with no geom.", call. = FALSE)
7072
if (is.null(stat))
@@ -103,6 +105,13 @@ layer <- function(geom = NULL, stat = NULL,
103105
params$na.rm <- FALSE
104106
}
105107

108+
# Special case for key_glyph parameter which is handed in through
109+
# params since all geoms/stats forward ... to params
110+
if (!is.null(params$key_glyph)) {
111+
key_glyph <- params$key_glyph
112+
params$key_glyph <- NULL # remove to avoid warning about unknown parameter
113+
}
114+
106115
# Split up params between aesthetics, geom, and stat
107116
params <- rename_aes(params)
108117
aes_params <- params[intersect(names(params), geom$aesthetics())]
@@ -133,6 +142,9 @@ layer <- function(geom = NULL, stat = NULL,
133142
)
134143
}
135144

145+
# adjust the legend draw key if requested
146+
geom <- set_draw_key(geom, key_glyph)
147+
136148
ggproto("LayerInstance", layer_class,
137149
geom = geom,
138150
geom_params = geom_params,
@@ -388,3 +400,18 @@ obj_desc <- function(x) {
388400
)
389401
}
390402
}
403+
404+
# helper function to adjust the draw_key slot of a geom
405+
# if a custom key glyph is requested
406+
set_draw_key <- function(geom, draw_key = NULL) {
407+
if (is.null(draw_key)) {
408+
return(geom)
409+
}
410+
if (is.character(draw_key)) {
411+
draw_key <- paste0("draw_key_", draw_key)
412+
}
413+
draw_key <- match.fun(draw_key)
414+
415+
ggproto("", geom, draw_key = draw_key)
416+
}
417+

R/legend-draw.r

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,42 @@
1-
#' Key drawing functions
1+
#' Key glyphs for legends
22
#'
3-
#' Each Geom has an associated function that draws the key when the geom needs
4-
#' to be displayed in a legend. These are the options built into ggplot2.
3+
#' Each geom has an associated function that draws the key when the geom needs
4+
#' to be displayed in a legend. These functions are called `draw_key_*()`, where
5+
#' `*` stands for the name of the respective key glyph. The key glyphs can be
6+
#' customized for individual geoms by providing a geom with the `key_glyph`
7+
#' argument (see [`layer()`] or examples below.)
58
#'
69
#' @return A grid grob.
710
#' @param data A single row data frame containing the scaled aesthetics to
811
#' display in this key
912
#' @param params A list of additional parameters supplied to the geom.
1013
#' @param size Width and height of key in mm.
11-
#' @keywords internal
14+
#' @examples
15+
#' p <- ggplot(economics, aes(date, psavert, color = "savings rate"))
16+
#' # key glyphs can be specified by their name
17+
#' p + geom_line(key_glyph = "timeseries")
18+
#'
19+
#' # key glyphs can be specified via their drawing function
20+
#' p + geom_line(key_glyph = draw_key_rect)
1221
#' @name draw_key
1322
NULL
1423

1524
#' @export
1625
#' @rdname draw_key
1726
draw_key_point <- function(data, params, size) {
18-
if (is.character(data$shape)) {
27+
if (is.null(data$shape)) {
28+
data$shape <- 19
29+
} else if (is.character(data$shape)) {
1930
data$shape <- translate_shape_string(data$shape)
2031
}
2132

2233
pointsGrob(0.5, 0.5,
2334
pch = data$shape,
2435
gp = gpar(
25-
col = alpha(data$colour, data$alpha),
26-
fill = alpha(data$fill, data$alpha),
27-
fontsize = data$size * .pt + data$stroke * .stroke / 2,
28-
lwd = data$stroke * .stroke / 2
36+
col = alpha(data$colour %||% "black", data$alpha),
37+
fill = alpha(data$fill %||% "black", data$alpha),
38+
fontsize = (data$size %||% 1.5) * .pt + (data$stroke %||% 0.5) * .stroke / 2,
39+
lwd = (data$stroke %||% 0.5) * .stroke / 2
2940
)
3041
)
3142
}
@@ -35,9 +46,9 @@ draw_key_point <- function(data, params, size) {
3546
draw_key_abline <- function(data, params, size) {
3647
segmentsGrob(0, 0, 1, 1,
3748
gp = gpar(
38-
col = alpha(data$colour, data$alpha),
39-
lwd = data$size * .pt,
40-
lty = data$linetype,
49+
col = alpha(data$colour %||% data$fill %||% "black", data$alpha),
50+
lwd = (data$size %||% 0.5) * .pt,
51+
lty = data$linetype %||% 1,
4152
lineend = "butt"
4253
)
4354
)
@@ -48,22 +59,26 @@ draw_key_abline <- function(data, params, size) {
4859
draw_key_rect <- function(data, params, size) {
4960
rectGrob(gp = gpar(
5061
col = NA,
51-
fill = alpha(data$fill, data$alpha),
52-
lty = data$linetype
62+
fill = alpha(data$fill %||% data$colour %||% "grey20", data$alpha),
63+
lty = data$linetype %||% 1
5364
))
5465
}
5566
#' @export
5667
#' @rdname draw_key
5768
draw_key_polygon <- function(data, params, size) {
69+
if (is.null(data$size)) {
70+
data$size <- 0.5
71+
}
72+
5873
lwd <- min(data$size, min(size) / 4)
5974

6075
rectGrob(
6176
width = unit(1, "npc") - unit(lwd, "mm"),
6277
height = unit(1, "npc") - unit(lwd, "mm"),
6378
gp = gpar(
64-
col = data$colour,
65-
fill = alpha(data$fill, data$alpha),
66-
lty = data$linetype,
79+
col = data$colour %||% NA,
80+
fill = alpha(data$fill %||% "grey20", data$alpha),
81+
lty = data$linetype %||% 1,
6782
lwd = lwd * .pt,
6883
linejoin = "mitre"
6984
))
@@ -84,10 +99,10 @@ draw_key_boxplot <- function(data, params, size) {
8499
rectGrob(height = 0.5, width = 0.75),
85100
linesGrob(c(0.125, 0.875), 0.5),
86101
gp = gpar(
87-
col = data$colour,
88-
fill = alpha(data$fill, data$alpha),
89-
lwd = data$size * .pt,
90-
lty = data$linetype
102+
col = data$colour %||% "grey20",
103+
fill = alpha(data$fill %||% "white", data$alpha),
104+
lwd = (data$size %||% 0.5) * .pt,
105+
lty = data$linetype %||% 1
91106
)
92107
)
93108
}
@@ -99,24 +114,28 @@ draw_key_crossbar <- function(data, params, size) {
99114
rectGrob(height = 0.5, width = 0.75),
100115
linesGrob(c(0.125, 0.875), 0.5),
101116
gp = gpar(
102-
col = data$colour,
103-
fill = alpha(data$fill, data$alpha),
104-
lwd = data$size * .pt,
105-
lty = data$linetype
117+
col = data$colour %||% "grey20",
118+
fill = alpha(data$fill %||% "white", data$alpha),
119+
lwd = (data$size %||% 0.5) * .pt,
120+
lty = data$linetype %||% 1
106121
)
107122
)
108123
}
109124

110125
#' @export
111126
#' @rdname draw_key
112127
draw_key_path <- function(data, params, size) {
113-
data$linetype[is.na(data$linetype)] <- 0
128+
if (is.null(data$linetype)) {
129+
data$linetype <- 0
130+
} else {
131+
data$linetype[is.na(data$linetype)] <- 0
132+
}
114133

115134
segmentsGrob(0.1, 0.5, 0.9, 0.5,
116135
gp = gpar(
117-
col = alpha(data$colour, data$alpha),
118-
lwd = data$size * .pt,
119-
lty = data$linetype,
136+
col = alpha(data$colour %||% data$fill %||% "black", data$alpha),
137+
lwd = (data$size %||% 0.5) * .pt,
138+
lty = data$linetype %||% 1,
120139
lineend = "butt"
121140
),
122141
arrow = params$arrow
@@ -128,9 +147,9 @@ draw_key_path <- function(data, params, size) {
128147
draw_key_vpath <- function(data, params, size) {
129148
segmentsGrob(0.5, 0.1, 0.5, 0.9,
130149
gp = gpar(
131-
col = alpha(data$colour, data$alpha),
132-
lwd = data$size * .pt,
133-
lty = data$linetype,
150+
col = alpha(data$colour %||% data$fill %||% "black", data$alpha),
151+
lwd = (data$size %||% 0.5) * .pt,
152+
lty = data$linetype %||% 1,
134153
lineend = "butt"
135154
),
136155
arrow = params$arrow
@@ -143,8 +162,8 @@ draw_key_dotplot <- function(data, params, size) {
143162
pointsGrob(0.5, 0.5, size = unit(.5, "npc"),
144163
pch = 21,
145164
gp = gpar(
146-
col = alpha(data$colour, data$alpha),
147-
fill = alpha(data$fill, data$alpha)
165+
col = alpha(data$colour %||% "black", data$alpha),
166+
fill = alpha(data$fill %||% "black", data$alpha)
148167
)
149168
)
150169
}
@@ -154,14 +173,14 @@ draw_key_dotplot <- function(data, params, size) {
154173
draw_key_pointrange <- function(data, params, size) {
155174
grobTree(
156175
draw_key_vpath(data, params, size),
157-
draw_key_point(transform(data, size = data$size * 4), params)
176+
draw_key_point(transform(data, size = (data$size %||% 1.5) * 4), params)
158177
)
159178
}
160179

161180
#' @export
162181
#' @rdname draw_key
163182
draw_key_smooth <- function(data, params, size) {
164-
data$fill <- alpha(data$fill, data$alpha)
183+
data$fill <- alpha(data$fill %||% "grey60", data$alpha)
165184
data$alpha <- 1
166185

167186
grobTree(
@@ -174,14 +193,14 @@ draw_key_smooth <- function(data, params, size) {
174193
#' @rdname draw_key
175194
draw_key_text <- function(data, params, size) {
176195
if(is.null(data$label)) data$label <- "a"
177-
196+
178197
textGrob(data$label, 0.5, 0.5,
179-
rot = data$angle,
198+
rot = data$angle %||% 0,
180199
gp = gpar(
181-
col = alpha(data$colour, data$alpha),
182-
fontfamily = data$family,
183-
fontface = data$fontface,
184-
fontsize = data$size * .pt
200+
col = alpha(data$colour %||% data$fill %||% "black", data$alpha),
201+
fontfamily = data$family %||% "",
202+
fontface = data$fontface %||% 1,
203+
fontsize = (data$size %||% 3.88) * .pt
185204
)
186205
)
187206
}
@@ -200,9 +219,30 @@ draw_key_label <- function(data, params, size) {
200219
draw_key_vline <- function(data, params, size) {
201220
segmentsGrob(0.5, 0, 0.5, 1,
202221
gp = gpar(
203-
col = alpha(data$colour, data$alpha),
204-
lwd = data$size * .pt,
205-
lty = data$linetype,
222+
col = alpha(data$colour %||% data$fill %||% "black", data$alpha),
223+
lwd = (data$size %||% 0.5) * .pt,
224+
lty = data$linetype %||% 1,
225+
lineend = "butt"
226+
)
227+
)
228+
}
229+
230+
#' @export
231+
#' @rdname draw_key
232+
draw_key_timeseries <- function(data, params, size) {
233+
if (is.null(data$linetype)) {
234+
data$linetype <- 0
235+
} else {
236+
data$linetype[is.na(data$linetype)] <- 0
237+
}
238+
239+
grid::linesGrob(
240+
x = c(0, 0.4, 0.6, 1),
241+
y = c(0.1, 0.6, 0.4, 0.9),
242+
gp = gpar(
243+
col = alpha(data$colour %||% data$fill %||% "black", data$alpha),
244+
lwd = (data$size %||% 0.5) * .pt,
245+
lty = data$linetype %||% 1,
206246
lineend = "butt"
207247
)
208248
)

man/draw_key.Rd

Lines changed: 17 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/layer.Rd

Lines changed: 4 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)