Skip to content

Commit 24dfd6e

Browse files
authored
Extracting guide data (version 3) (#5506)
* add `guide_data()` function * add tests * document * rename getter (see #5568) * swap from location-based to panel-based
1 parent f512174 commit 24dfd6e

File tree

5 files changed

+193
-0
lines changed

5 files changed

+193
-0
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ export(geom_violin)
421421
export(geom_vline)
422422
export(get_alt_text)
423423
export(get_element_tree)
424+
export(get_guide_data)
424425
export(gg_dep)
425426
export(ggplot)
426427
export(ggplotGrob)

NEWS.md

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

3+
* The `get_guide_data()` function can be used to extract position and label
4+
information from the plot (#5004).
5+
36
* The ggplot object now contains `$layout` which points to the `Layout` ggproto
47
object and will be used by the `ggplot_build.ggplot` method. This was exposed
58
so that package developers may extend the behavior of the `Layout` ggproto object

R/guides-.R

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,91 @@ Guides <- ggproto(
741741
}
742742
)
743743

744+
# Data accessor -----------------------------------------------------------
745+
746+
#' Extract tick information from guides
747+
#'
748+
#' `get_guide_data()` builds a plot and extracts information from guide keys. This
749+
#' information typically contains positions, values and/or labels, depending
750+
#' on which aesthetic is queried or guide is used.
751+
#'
752+
#' @param plot A `ggplot` or `ggplot_build` object.
753+
#' @param aesthetic A string that describes a single aesthetic for which to
754+
#' extract guide information. For example: `"colour"`, `"size"`, `"x"` or
755+
#' `"y.sec"`.
756+
#' @param panel An integer giving a panel number for which to return position guide
757+
#' information.
758+
#'
759+
#' @return
760+
#' One of the following:
761+
#' * A `data.frame` representing the guide key, when the guide is unique for
762+
#' the aesthetic.
763+
#' * A `list` when the coord does not support position axes or multiple guides
764+
#' match the aesthetic.
765+
#' * `NULL` when no guide key could be found.
766+
#' @export
767+
#' @keywords internal
768+
#'
769+
#' @examples
770+
#' # A standard plot
771+
#' p <- ggplot(mtcars) +
772+
#' aes(mpg, disp, colour = drat, size = drat) +
773+
#' geom_point() +
774+
#' facet_wrap(vars(cyl), scales = "free_x")
775+
#'
776+
#' # Guide information for legends
777+
#' get_guide_data(p, "size")
778+
#'
779+
#' # Note that legend guides can be merged
780+
#' merged <- p + guides(colour = "legend")
781+
#' get_guide_data(merged, "size")
782+
#'
783+
#' # Guide information for positions
784+
#' get_guide_data(p, "x", panel = 2)
785+
#'
786+
#' # Coord polar doesn't support proper guides, so we get a list
787+
#' polar <- p + coord_polar()
788+
#' get_guide_data(polar, "theta", panel = 2)
789+
get_guide_data <- function(plot = last_plot(), aesthetic, panel = 1L) {
790+
791+
check_string(aesthetic, allow_empty = FALSE)
792+
aesthetic <- standardise_aes_names(aesthetic)
793+
794+
if (!inherits(plot, "ggplot_built")) {
795+
plot <- ggplot_build(plot)
796+
}
797+
798+
if (!aesthetic %in% c("x", "y", "x.sec", "y.sec", "theta", "r")) {
799+
# Non position guides: check if aesthetic in colnames of key
800+
keys <- lapply(plot$plot$guides$params, `[[`, "key")
801+
keep <- vapply(keys, function(x) any(colnames(x) %in% aesthetic), logical(1))
802+
keys <- switch(sum(keep) + 1, NULL, keys[[which(keep)]], keys[keep])
803+
return(keys)
804+
}
805+
806+
# Position guides: find the right layout entry
807+
check_number_whole(panel)
808+
layout <- plot$layout$layout
809+
select <- layout[layout$PANEL == panel, , drop = FALSE]
810+
if (nrow(select) == 0) {
811+
return(NULL)
812+
}
813+
params <- plot$layout$panel_params[select$PANEL][[1]]
814+
815+
# If panel params don't have guides, we probably have old coord system
816+
# that doesn't use the guide system.
817+
if (is.null(params$guides)) {
818+
# Old system: just return relevant parameters
819+
aesthetic <- paste(aesthetic, c("major", "minor", "labels", "range"), sep = ".")
820+
params <- params[intersect(names(params), aesthetic)]
821+
return(params)
822+
} else {
823+
# Get and return key
824+
key <- params$guides$get_params(aesthetic)$key
825+
return(key)
826+
}
827+
}
828+
744829
# Helpers -----------------------------------------------------------------
745830

746831
matched_aes <- function(layer, guide) {

man/get_guide_data.Rd

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

tests/testthat/test-guides.R

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,55 @@ test_that("guide_colourbar merging preserves both aesthetics", {
323323
expect_true(all(c("colour", "fill") %in% names(merged$params$key)))
324324
})
325325

326+
test_that("get_guide_data retrieves keys appropriately", {
327+
328+
p <- ggplot(mtcars, aes(mpg, disp, colour = drat, size = drat, fill = wt)) +
329+
geom_point(shape = 21) +
330+
facet_wrap(vars(cyl), scales = "free_x") +
331+
guides(colour = "legend")
332+
b <- ggplot_build(p)
333+
334+
# Test facetted panel
335+
test <- get_guide_data(b, "x", panel = 2)
336+
expect_equal(test$.label, c("18", "19", "20", "21"))
337+
338+
# Test plain legend
339+
test <- get_guide_data(b, "fill")
340+
expect_equal(test$.label, c("2", "3", "4", "5"))
341+
342+
# Test merged legend
343+
test <- get_guide_data(b, "colour")
344+
expect_true(all(c("colour", "size") %in% colnames(test)))
345+
346+
# Unmapped data
347+
expect_null(get_guide_data(b, "shape"))
348+
349+
# Non-existent panels
350+
expect_null(get_guide_data(b, "x", panel = 4))
351+
352+
expect_error(get_guide_data(b, 1), "must be a single string")
353+
expect_error(get_guide_data(b, "x", panel = "a"), "must be a whole number")
354+
})
355+
356+
test_that("get_guide_data retrieves keys from exotic coords", {
357+
358+
p <- ggplot(mtcars, aes(mpg, disp)) + geom_point()
359+
360+
# Sanity check
361+
test <- get_guide_data(p + coord_cartesian(), "x")
362+
expect_equal(test$.label, c("10", "15", "20", "25", "30", "35"))
363+
364+
# We're not testing the formatting, so just testing output shape
365+
test <- get_guide_data(p + coord_sf(crs = 3347), "y")
366+
expect_equal(nrow(test), 5)
367+
expect_true(all(c("x", ".value", ".label", "x") %in% colnames(test)))
368+
369+
# For coords that don't use guide system, we expect a list
370+
test <- get_guide_data(p + coord_polar(), "theta")
371+
expect_true(is.list(test) && !is.data.frame(test))
372+
expect_equal(test$theta.labels, c("15", "20", "25", "30"))
373+
})
374+
326375
test_that("guide_colourbar warns about discrete scales", {
327376

328377
g <- guide_colourbar()

0 commit comments

Comments
 (0)