-
Notifications
You must be signed in to change notification settings - Fork 3
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
Example for using scales #57
Conversation
tidyselect | ||
RoxygenNote: 7.3.1 | ||
tidyselect, | ||
vctrs |
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.
{vctrs} is already a dependency of ggplot2, so it is 'free'
# Copied from ggplot2::geom_text | ||
if (!missing(nudge_x) || !missing(nudge_y)) { | ||
if (!missing(position)) { | ||
cli::cli_abort(c( | ||
"Both {.arg position} and {.arg nudge_x}/{.arg nudge_y} are supplied.", | ||
"i" = "Only use one approach to alter the position." | ||
)) | ||
} | ||
|
||
position <- position_nudge(nudge_x, nudge_y) | ||
} |
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.
No need for all this nudging setup. Users can just use position_nudge()
if they want.
marker_labels <- rlang::eval_tidy(data = data, expr = mapping$label) | ||
|
||
marker_key <- data.frame( | ||
marker_labels = marker_labels, | ||
marker_glyphs = glyph, | ||
marker_colors = color | ||
) |> distinct() |
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.
Moved to scale
# Reclass the layer to trigger ggplot_add and apply manual scale/guide | ||
class(layer_obj) <- c("swim_marker", class(layer_obj)) | ||
layer_obj$marker_key <- marker_key | ||
layer_obj |
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.
No need for reclassing here
StatMarker <- ggplot2::ggproto( | ||
`_class` = "StatMarker", | ||
`_inherit` = ggplot2::Stat, | ||
required_aes = c("x", "y", "label"), | ||
compute_group = function(data, scales, marker_key, ...) { | ||
compute_marker_group(data = data, scales = scales, marker_key = marker_key) | ||
} | ||
) | ||
GeomSwimMarker <- ggproto( | ||
"GeomSwimMarker", GeomText, | ||
default_aes = GeomText$default_aes[names(GeomText$default_aes) != "colour"], | ||
required_aes = c("x", "y", "marker"), | ||
|
||
#' @rdname geom_swim_marker | ||
#' @keywords internal | ||
compute_marker_group <- function(data, scales, marker_key) { | ||
data <- left_join(data, marker_key, by = c("label" = "marker_labels")) | ||
data | ||
} |
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.
Handled by scale mapping now
ggplot_add.swim_marker <- function(object, plot, object_name) { | ||
marker_key <- object$marker_key |> | ||
arrange(marker_labels) | ||
draw_panel = function(self, data, panel_params, coord, size.unit = "mm", | ||
check_overlap = FALSE, na.rm = FALSE) { | ||
|
||
plot$layers <- append(plot$layers, object) | ||
data$colour <- vctrs::field(data$marker, "colour") | ||
data$label <- vctrs::field(data$marker, "glyphs") | ||
data$marker <- NULL | ||
|
||
# Add manual color/guide changes to the scale | ||
plot + | ||
scale_color_manual( | ||
aesthetics = "label", | ||
name = plot$layers[[length(plot$layers)]]$mapping$label |> rlang::as_label(), | ||
values = setNames(marker_key$marker_glyphs, marker_key$marker_labels), | ||
guide = guide_legend( | ||
override.aes = list( | ||
color = marker_key$marker_colors | ||
) | ||
) | ||
GeomText$draw_panel( | ||
data, panel_params, coord, | ||
check_overlap = check_overlap, | ||
size.unit = size.unit, na.rm = na.rm | ||
) | ||
} |
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.
No need to add this ad hoc scale that can clash with other layer's scales
data$colour <- vctrs::field(data$marker, "colour") | ||
data$label <- vctrs::field(data$marker, "glyphs") | ||
data$marker <- NULL |
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.
Just unpacking the marker
column and forwarding to text geom
default_aes = GeomText$default_aes[names(GeomText$default_aes) != "colour"], | ||
required_aes = c("x", "y", "marker"), |
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.
We don't want colour
as an aesthetic, we have marker
that catches this.
@@ -13,122 +13,57 @@ | |||
geom_swim_marker <- function(mapping = NULL, data = NULL, | |||
stat = "identity", position = "identity", | |||
..., | |||
color = NULL, | |||
glyph = NULL, | |||
parse = FALSE, |
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.
We're not allowing expressions as markers, so no need to parse anything
markers <- data.frame(glyphs = glyphs, colors = colors, labels = limits) |> | ||
distinct() |
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.
Was previously handled in geom_swim_marker()
constructor
) | ||
|
||
discrete_scale("marker", rlang::missing_arg(), palette = palette, | ||
limits = markers$labels, ..., na.translate = FALSE) |
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.
na.translate
is needed here because the <vctr_rcrd>
doesn't work well with an ifelse()
statement somewhere in the scale mapping.
.default_glyphs <- c("⛔", "✔", "✖", "☀", "☢", "☠", "☮", "⚛", "♥", | ||
"✀") |
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.
Feel free to change to whatever
markers <- vctrs::new_rcrd( | ||
list( | ||
colour = rep(colors %||% "black", length.out = n_values), | ||
glyphs = rep(glyphs %||% .default_glyphs, length.out = n_values) | ||
), | ||
class = "marker" | ||
) |
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.
We're essentially making a 2-column data frame act like a vector, so that it works well with ggplot2's internals.
We unpack this in the drawing method.
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.
@teunbrand I've been working through this example to wrap my head around {vctrs} and how scale_marker_discrete()
is triggered during the call to geom_swim_marker()
.
I think I have a shallow handle of {vctrs} here, but wanted to test it by converting the code above to something I could more easily inspect:
markers <- structure(
list(
colour = rep(colors %||% "black", length.out = n_values),
glyphs = rep(glyphs %||% .default_glyphs, length.out = n_values)
),
class = "marker"
)
I believe this should work the same, but on stepping through a geom_swim_marker()
call it seems this set up triggers draw_panel
before it triggers draw_key
and winds up failing. Any insight as to what I'm missing here in how this behaves?
Is it also correct to assume that the definition of "marker" in required_aes
triggers ggplot to look for scale_*_discrete()
(in this case scale_marker_discrete()
)?
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.
Yeah so vctrs::new_rcrd()
invokes the <vctrs_rcrd> class that has all sorts of niceties that makes the data-frame like structure behave as if it was a vector (by treating each row as element). It avoids some type unsafeties in the internals of ggplot2 that would mangle the structure otherwise.
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.
Very cool to learn. Thanks!
Apologies for ignoring your checklist; this is just a proof-of-concept PR. Feel free to take and/or ignore as you please.
This PR follows discussion in https://github.com/teunbrand/ggplot-extension-club/discussions/38.
It shows how using a scale might simplify your code, most of it in comments.
As a demonstration; here is what your example will look like without adding a scale.
Adding a scale will override the default symbols.
Created on 2024-09-19 with reprex v2.1.1