-
Notifications
You must be signed in to change notification settings - Fork 2k
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
multiple inside guide box with different position #6210
base: main
Are you sure you want to change the base?
Conversation
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.
Hi Yunuuuu, thanks for preparing a PR for this issue!
The PR appears to break a visual test for the alignment of legends with the inside position. It is important to us to preserve current behaviour without regressions.
Next, it is hard to tell from the PR what the assembly rules for inside legends are. When legends share legend.position.inside
values, they merge (are placed in the same guide-box). It seems that justification also factors into this and I'm not convinced that it should. It'd be great if the logic for this is expressed more clearly, perhaps through code comments.
Lastly, there are a few fussy changes to the code I'd like to propose, but it'd make more sense to go over this after the PR adresses the previous points.
@teunbrand Thank you for the reviewing. I have made the following changes based on your feedback:
|
I'll check it |
Should we always keep the inside guide box, even if there are no inside guide legends? |
An empty inside guide box should give a |
should fixed |
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.
Thanks for getting the PR to work!
My overall feeling about this PR is that it is a little bit on the more invasive side, whereas I think it could be more surgical than it currently is.
If we lift the responsibility of managing the positions into Guides$assemble()
, we should have to change less code elsewhere.
R/guides-.R
Outdated
if (startsWith(position, "inside")) { | ||
# Global justification of the complete legend box | ||
global_just <- valid.just(calc_element( | ||
"legend.justification.inside", theme | ||
)) | ||
# for inside guide legends, the position was attached in | ||
# each grob of the input grobs (which should share the same position) | ||
inside_position <- attr(.subset2(grobs, 1L), "inside_position") %||% | ||
# fallback to original method of ggplot2 <=3.5.1 | ||
.subset2(theme, "legend.position.inside") %||% global_just | ||
global_xjust <- global_just[1] | ||
global_yjust <- global_just[2] | ||
x <- inside_position[1] | ||
y <- inside_position[2] | ||
global_margin <- margin() | ||
} else { | ||
# Global justification of the complete legend box | ||
global_just <- paste0("legend.justification.", position) | ||
global_just <- valid.just(calc_element(global_just, theme)) | ||
x <- global_xjust <- global_just[1] | ||
y <- global_yjust <- global_just[2] |
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.
I think we might not even have to alter the logic of Guides$package_box()
if we just manage everything well in Guides$assemble()
.
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.
Solved, but I haven't found a way to avoid changing Guides$package_box()
.
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.
I'm happy to explore this if you feel this gets stuck
I'll try to make some new changes |
Thank you for your thorough review and all the valuable advice. |
After writing some examples, I’ve realized that it’s necessary to allow users to set the inside justification for each inside legend. In this way, it would be beneficial for splitting legends based on their justifications, as users may be confused about which justification will be applied if we only split legends with the same inside position into groups. |
I’m unable to run the tests on my local machine, as there are always some errors. I’m unable to update the test snapshots. |
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.
Thanks for all the changes, Yunuuuu!
I apologise but I still have a few more to suggest. I'd be happy to help out trying to find a solution for not editing Guides$package_box()
as much as is reasonable.
I’m unable to update the test snapshots.
I'm happy to do this for you if you like.
if (is.null(inside_just)) { | ||
inside_justs[[i]] <- default_inside_just | ||
} else { | ||
inside_justs[[i]] <- valid.just(inside_just) | ||
} |
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.
if (is.null(inside_just)) { | |
inside_justs[[i]] <- default_inside_just | |
} else { | |
inside_justs[[i]] <- valid.just(inside_just) | |
} | |
inside_justs[[i]] <- valid.just(inside_just %||% default_inside_just) |
for (i in seq_along(positions)) { | ||
if (identical(positions[i], "inside")) { |
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.
for (i in seq_along(positions)) { | |
if (identical(positions[i], "inside")) { | |
for (i in seq_along(positions)[positions == "inside"]) { |
positions <- positions[keep] | ||
inside_positions <- inside_positions[keep] | ||
inside_justs <- inside_justs[keep] | ||
groups <- groups[keep] | ||
|
||
# we group the guide legends | ||
locs <- vec_group_loc(groups) | ||
indices <- locs$loc | ||
grobs <- vec_chop(grobs, indices = indices) | ||
names(grobs) <- locs$key | ||
|
||
# for each group, they share the same locations, | ||
# so we only extract the first one of `positions` and `inside_positions` | ||
first_indice <- lapply(indices, `[[`, 1L) | ||
positions <- vec_chop(positions, indices = first_indice) | ||
inside_positions <- vec_chop(inside_positions, indices = first_indice) | ||
inside_justs <- vec_chop(inside_justs, indices = first_indice) |
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.
I think this could be simplified by tracking positions/inside_positions/inside_justs in a data.frame.
vec_group_loc()
treats rows that are the same as the same group, and the locs$key
should allow you to skip having to subset the first index.
inside_legends <- legends[startsWith(names(legends), "inside")] | ||
if (length(inside_legends)) { | ||
for (i in seq_along(inside_legends)) { | ||
table <- gtable_add_grob( | ||
table, inside_legends[[i]], clip = "off", | ||
t = place$t, b = place$b, l = place$l, r = place$r, | ||
name = paste("guide-box-inside", i, sep = "-") | ||
) | ||
} | ||
} else { # to be consistent with original gtable layout | ||
table <- gtable_add_grob( | ||
table, zeroGrob(), clip = "off", | ||
t = place$t, b = place$b, l = place$l, r = place$r, | ||
name = "guide-box-inside" | ||
) | ||
} |
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.
I think it should be possible to avoid this by combining all packaged inside guides into a single grob.
positions <- positions %||% vapply( | ||
params, | ||
function(p) p$position[1] %||% default_position, | ||
character(1) | ||
function(p) p$position[1] %||% "right", | ||
character(1), USE.NAMES = 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.
Can't we assume here that the Guides$draw()
method receives a well-formed positions
argument, so we don't have to fall back here? You could even change positions = NULL
to positions
in the arguments to signal that it is a required argument.
package_box = function(grobs, position, theme, | ||
inside_position = NULL, inside_just = 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.
I still think we could prevent having to edit this method.
We can just make a theme
for every iteration that has the correct inside_position
and inside_just
baked in.
directions <- ifelse( | ||
positions %in% c("top", "bottom"), | ||
"horizontal", "vertical" | ||
) | ||
} else { | ||
directions <- rep(direction, length(positions)) |
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.
I'm not understanding this change. The else
branch repeats NULL
some times, which remains NULL
because it has 0-length?
Try to fix #5712.
This enables users to set
legend.position.inside
andlegend.justification.inside
intheme
ofguide_legend
andguide_colourbar
.Here is an example: