-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Custom guide #5496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Custom guide #5496
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
395ed9a
guides are named by their hash
teunbrand 460e8a4
Pumbing for custom guides
teunbrand df24228
draft version
teunbrand 50d4c32
Document stuff
teunbrand 02f6626
Add topic to pkgdown
teunbrand 63778b3
prepend grid namespace to example
teunbrand f2754fa
resolve conflict
teunbrand bed3afe
Adapt to new `Guide$draw()` formals
teunbrand b10a901
keep custom guides when there are no scales
teunbrand 3fe1402
sort grobs after drawing
teunbrand 0b7f32f
resolve conflict
teunbrand e47c756
Add news bullet
teunbrand 02a3db8
Merge branch 'main' into guide_custom
teunbrand File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
#' Custom guides | ||
#' | ||
#' This is a special guide that can be used to display any graphical object | ||
#' (grob) along with the regular guides. This guide has no associated scale. | ||
#' | ||
#' @param grob A grob to display. | ||
#' @param width,height The allocated width and height to display the grob, given | ||
#' in [grid::unit()]s. | ||
#' @param title A character string or expression indicating the title of guide. | ||
#' If `NULL` (default), no title is shown. | ||
#' @param title.position A character string indicating the position of a title. | ||
#' One of `"top"` (default), `"bottom"`, `"left"` or `"right"`. | ||
#' @param margin Margins around the guide. See [margin()] for more details. If | ||
#' `NULL` (default), margins are taken from the `legend.margin` theme setting. | ||
#' @param position Currently not in use. | ||
#' @inheritParams guide_legend | ||
#' | ||
#' @export | ||
#' | ||
#' @examples | ||
#' # A standard plot | ||
#' p <- ggplot(mpg, aes(displ, hwy)) + | ||
#' geom_point() | ||
#' | ||
#' # Define a graphical object | ||
#' circle <- grid::circleGrob() | ||
#' | ||
#' # Rendering a grob as a guide | ||
#' p + guides(custom = guide_custom(circle, title = "My circle")) | ||
#' | ||
#' # Controlling the size of the grob defined in relative units | ||
#' p + guides(custom = guide_custom( | ||
#' circle, title = "My circle", | ||
#' width = unit(2, "cm"), height = unit(2, "cm")) | ||
#' ) | ||
#' | ||
#' # Size of grobs in absolute units is taken directly without the need to | ||
#' # set these manually | ||
#' p + guides(custom = guide_custom( | ||
#' title = "My circle", | ||
#' grob = grid::circleGrob(r = unit(1, "cm")) | ||
#' )) | ||
guide_custom <- function( | ||
grob, width = grobWidth(grob), height = grobHeight(grob), | ||
title = NULL, title.position = "top", margin = NULL, | ||
position = waiver(), order = 0 | ||
) { | ||
check_object(grob, is.grob, "a {.cls grob} object") | ||
check_object(width, is.unit, "a {.cls unit} object") | ||
check_object(height, is.unit, "a {.cls unit} object") | ||
check_object(margin, is.margin, "a {.cls margin} object", allow_null = TRUE) | ||
if (length(width) != 1) { | ||
cli::cli_abort("{.arg width} must be a single {.cls unit}, not a unit vector.") | ||
} | ||
if (length(height) != 1) { | ||
cli::cli_abort("{.arg height} must be a single {.cls unit}, not a unit vector.") | ||
} | ||
title.position <- arg_match0(title.position, .trbl) | ||
|
||
new_guide( | ||
grob = grob, | ||
width = width, | ||
height = height, | ||
title = title, | ||
title.position = title.position, | ||
margin = margin, | ||
hash = hash(list(title, grob)), # hash is already known | ||
position = position, | ||
order = order, | ||
available_aes = "any", | ||
super = GuideCustom | ||
) | ||
} | ||
|
||
#' @rdname ggplot2-ggproto | ||
#' @format NULL | ||
#' @usage NULL | ||
#' @export | ||
GuideCustom <- ggproto( | ||
"GuideCustom", Guide, | ||
|
||
params = c(Guide$params, list( | ||
grob = NULL, width = NULL, height = NULL, | ||
margin = NULL, | ||
title = NULL, | ||
title.position = "top" | ||
)), | ||
|
||
hashables = exprs(title, grob), | ||
|
||
elements = list( | ||
background = "legend.background", | ||
theme.margin = "legend.margin", | ||
theme.title = "legend.title" | ||
), | ||
|
||
train = function(...) { | ||
params | ||
}, | ||
|
||
transform = function(...) { | ||
params | ||
}, | ||
|
||
override_elements = function(params, elements, theme) { | ||
elements$title <- elements$theme.title | ||
elements$margin <- params$margin %||% elements$theme.margin | ||
elements | ||
}, | ||
|
||
draw = function(self, theme, position = NULL, direction = NULL, | ||
params = self$params) { | ||
|
||
# Render title | ||
elems <- self$setup_elements(params, self$elements, theme) | ||
elems <- self$override_elements(params, elems, theme) | ||
if (!is.waive(params$title) && !is.null(params$title)) { | ||
title <- self$build_title(params$title, elems, params) | ||
} else { | ||
title <- zeroGrob() | ||
} | ||
title.position <- params$title.position | ||
if (is.zero(title)) { | ||
title.position <- "none" | ||
} | ||
|
||
width <- convertWidth(params$width, "cm") | ||
height <- convertHeight(params$height, "cm") | ||
gt <- gtable(widths = width, heights = height) | ||
gt <- gtable_add_grob(gt, params$grob, t = 1, l = 1, clip = "off") | ||
|
||
if (params$title.position == "top") { | ||
gt <- gtable_add_rows(gt, elems$margin[1], pos = 0) | ||
gt <- gtable_add_rows(gt, unit(height_cm(title), "cm"), pos = 0) | ||
gt <- gtable_add_grob(gt, title, t = 1, l = 1, name = "title", clip = "off") | ||
} else if (params$title.position == "bottom") { | ||
gt <- gtable_add_rows(gt, elems$margin[3], pos = -1) | ||
gt <- gtable_add_rows(gt, unit(height_cm(title), "cm"), pos = -1) | ||
gt <- gtable_add_grob(gt, title, t = -1, l = 1, name = "title", clip = "off") | ||
} else if (params$title.position == "left") { | ||
gt <- gtable_add_cols(gt, elems$margin[4], pos = 0) | ||
gt <- gtable_add_cols(gt, unit(width_cm(title), "cm"), pos = 0) | ||
gt <- gtable_add_grob(gt, title, t = 1, l = 1, name = "title", clip = "off") | ||
} else if (params$title.position == "right") { | ||
gt <- gtable_add_cols(gt, elems$margin[2], pos = -1) | ||
gt <- gtable_add_cols(gt, unit(width_cm(title), "cm"), pos = 0) | ||
gt <- gtable_add_grob(gt, title, t = 1, l = -1, name = "title", clip = "off") | ||
} | ||
gt <- gtable_add_padding(gt, elems$margin) | ||
|
||
background <- element_grob(elems$background) | ||
gt <- gtable_add_grob( | ||
gt, background, | ||
t = 1, l = 1, r = -1, b = -1, | ||
z = -Inf, clip = "off" | ||
) | ||
gt | ||
} | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, in the absence of non-position (NP) scales, we still want to render the custom guides.
Previously, if there were no NP scales, there'd be no need to render any NP guides, so we could set the guides list to empty (as position guides have been absorbed already by the coord).
However, since custom guides aren't attached to a scale, setting the guides list to empty would also discard the custom guides. Instead, we extract only the custom guides if there are no NP scales. In absence of any custom guides, the
plot$guides$get_custom()
just returns an empty guides list.