-
Notifications
You must be signed in to change notification settings - Fork 84
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
Shared legend feature stopped workin with ggplot2 v3.5.0 #202
Comments
Thanks for bringing this up. I'm happy to consider a PR that addresses this. I haven't used this feature a lot myself lately and I haven't paid much attention to how ggplot2's legends have changed so I'm not sure what the best approach is. Happy to hear suggestions. |
Hi @clauswilke It now seems overly complicated when combining plots with legends at various locations and still uses the shared legend feature. If I have not figured out how to determine from all these arbitrary positions introduced in v3.5.0, which are the actual ones set in If users applied more than 1 legend positions, then maybe provides a warning and default position to |
Maybe @teunbrand has a suggestion on what the best way forward is? Teun, I don't think we need a general solution that works with multiple legends, but if there is only one legend the function should reliably return it regardless of where it is located in the plot. |
Ok, here is a version of the function that seems to work for the various possible legend positions. It simply returns the first non-zero legend each time. library(ggplot2)
library(cowplot)
get_legend_35 <- function(plot) {
# return all legend candidates
legends <- get_plot_component(plot, "guide-box", return_all = TRUE)
# find non-zero legends
nonzero <- vapply(legends, \(x) !inherits(x, "zeroGrob"), TRUE)
idx <- which(nonzero)
# return first non-zero legend if exists, and otherwise first element (which will be a zeroGrob)
if (length(idx) > 0) {
return(legends[[idx[1]]])
} else {
return(legends[[1]])
}
}
set.seed(1123)
dsamp = diamonds[sample(nrow(diamonds), 1000), ]
p1 = ggplot(dsamp, aes(carat, price, color = clarity)) +
geom_point() + theme(legend.position="none")
p2 = ggplot(dsamp, aes(carat, depth, color = clarity)) +
geom_point() + theme(legend.position="none")
prow = plot_grid(p1, p2, align = 'vh', nrow = 1)
# right legend
legend1 = get_legend_35(
p1 + theme(legend.position = "right")
)
plot_grid(prow, legend1, nrow = 1, rel_widths = c(7, 1)) # bottom legend
legend2 = get_legend_35(
p1 + guides(color = guide_legend(nrow = 1)) +
theme(legend.position = "bottom")
)
plot_grid(prow, legend2, ncol = 1, rel_heights = c(7, 1)) # no legend (negative control)
legend3 = get_legend_35(
p1 + guides(color = "none")
)
plot_grid(prow, legend3, ncol = 1, rel_heights = c(7, 1)) Created on 2024-03-06 with reprex v2.0.2 If you guys could try it that would be great. And you can also use it as workaround for now. |
Slightly more general function that can also return a legend other than the first, in case there are multiple ones. get_legend_35 <- function(plot, legend_number = 1) {
# find all legend candidates
legends <- get_plot_component(plot, "guide-box", return_all = TRUE)
# find non-zero legends
idx <- which(vapply(legends, \(x) !inherits(x, "zeroGrob"), TRUE))
# return either the chosen or the first non-zero legend if it exists,
# and otherwise the first element (which will be a zeroGrob)
if (length(idx) >= legend_number) {
return(legends[[idx[legend_number]]])
} else if (length(idx) >= 0) {
return(legends[[idx[1]]])
} else {
return(legends[[1]])
}
} |
The first non-zero legend solution you pointed out seems like a good compromise. library(ggplot2)
get_legend <- function(plot, legend = NULL) {
gt <- ggplotGrob(plot)
pattern <- "guide-box"
if (!is.null(legend)) {
pattern <- paste0(pattern, "-", legend)
}
indices <- grep(pattern, gt$layout$name)
not_empty <- !vapply(
gt$grobs[indices],
inherits, what = "zeroGrob",
FUN.VALUE = logical(1)
)
indices <- indices[not_empty]
if (length(indices) > 0) {
return(gt$grobs[[indices[1]]])
}
return(NULL)
}
p <- ggplot(mpg, aes(displ, hwy, colour = factor(cyl), shape = factor(year))) +
geom_point() +
guides(shape = guide_legend(position = "bottom"))
plot(get_legend(p)) plot(get_legend(p, "bottom")) Created on 2024-03-06 with reprex v2.1.0 plot(get_legend(p, "bottom")) Created on 2024-03-06 with reprex v2.1.0 |
Hi, I tested both functions (#202 (comment) and #202 (comment)), they produced the same output that my function relies on. Thanks! library(scRUtils)
#> Loading required package: grid
data(sce)
plotProjections(sce, "label", dimname = c("TSNE", "UMAP"), text_by = "label",
feat_desc = "Cluster", point_size = 2) plotProjections(sce, "label", dimname = c("TSNE", "UMAP"), text_by = "label",
feat_desc = "Cluster", point_size = 2, legend_pos= "bottom") Created on 2024-03-06 with reprex v2.1.0 |
Thanks @teunbrand, this makes sense. Could you point me to the complete list of possible legend positions, so I can write the appropriate documentation? Is it right, left, bottom, top, inside, or are there other options? |
Sure, the possible options are 'guide-box-right', 'guide-box-left', 'guide-box-bottom', 'guide-box-top' and 'guide-box-inside'. They're added in the following lines of ggplot2's source code: |
A pull request and commit made to ggplot2 last Dec tidyverse/ggplot2#5488 means from v3.5.0 onwards cowplot's shared legends feature will not work as before.
Below is the reproducible demo.
The
plot_component_names()
that looks for the"guide-box"
pattern now returns multiple matches.Because
guide-box-right
is the first match returned byplot_component_names()
, therefore only the shared right-sided legend works.Created on 2024-03-05 with reprex v2.1.0
The text was updated successfully, but these errors were encountered: