-
Notifications
You must be signed in to change notification settings - Fork 162
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
Arrange legends, collect specific aesthetics. #288
Comments
+1 on this. I've a case where each plot requires a distinct color legend but they all share linetypes. In the interest of readability and space efficiency it's thus desirable to collect the linetype legend but, so far, nothing I've tried has been successful. Right now (patchwork 1.1.2) it looks like there's not support for (What I have is actually more complicated than this, in that it'd be ideal to collect two of three linetypes while leaving the third integrated with the color legend, but I'm fairly sure that's not supported by ggplot.) |
I was looking for a way to combine legends in a similar way it is possible to combine plots. ## libraries
library(ggplot2)
library(dplyr)
library(magrittr)
library(patchwork)
## plots without points
g1 <-
mpg %>%
mutate(class = as.factor(class)) %>%
filter(class == "compact") %>%
ggplot(aes(x = cyl, y = cty, color = class, alpha = hwy)) +
scale_color_discrete(drop = FALSE) +
ggtitle("Compact")
g2 <-
mpg %>%
mutate(class = as.factor(class)) %>%
filter(class == "midsize") %>%
ggplot(aes(x = cyl, y = cty, color = class, alpha = hwy)) +
scale_color_discrete(drop = FALSE) +
ggtitle("Midsize")
## plots with points without legends
g1_point <-
g1 +
geom_point( size = 3 ) +
theme( legend.position = "none" )
g2_point <-
g2 +
geom_point( size = 3 ) +
theme( legend.position = "none" )
## legends without points
g1_alpha <-
g1 +
geom_point( size = 0, stroke = 0 ) +
coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) +
guides( color = "none", alpha = guide_legend( override.aes = list( size = 3 ) ) ) +
theme(
plot.background = element_blank(),
panel.background = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
legend.box.spacing = unit( 0, "pt" ),
legend.direction = "horizontal",
legend.position = "bottom",
panel.grid = element_blank(),
plot.title = element_blank(),
plot.subtitle = element_blank(),
plot.caption = element_blank(),
plot.tag = element_blank(),
strip.background = element_blank(),
strip.text = element_blank() )
g2_alpha <-
g2 +
geom_point( size = 0, stroke = 0 ) +
coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) +
guides( color = "none", alpha = guide_legend( override.aes = list( size = 3 ) ) ) +
theme(
plot.background = element_blank(),
panel.background = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
legend.box.spacing = unit( 0, "pt" ),
legend.direction = "horizontal",
legend.position = "bottom",
panel.grid = element_blank(),
plot.title = element_blank(),
plot.subtitle = element_blank(),
plot.caption = element_blank(),
plot.tag = element_blank(),
strip.background = element_blank(),
strip.text = element_blank() )
g1_color <-
g1 +
geom_point( size = 0, stroke = 0 ) +
coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) +
guides( color = guide_legend( override.aes = list( size = 3 ) ), alpha = "none" ) +
theme(
plot.background = element_blank(),
panel.background = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
legend.box.spacing = unit( 0, "pt" ),
panel.grid = element_blank(),
plot.title = element_blank(),
plot.subtitle = element_blank(),
plot.caption = element_blank(),
plot.tag = element_blank(),
strip.background = element_blank(),
strip.text = element_blank() )
## plotting
g1_point +
g2_point +
g1_color +
g1_alpha +
g2_alpha +
plot_spacer() +
plot_layout( ncol = 3, height = c(1,0), width = c(1,1,0), guides = "keep" ) |
My problem is about arranging multiple legends. Taking a similar example to before: ## setup
dataMPG <- mpg %>% filter( trans == "auto(l4)" ) %>% mutate(across( where(is.character), ~ factor(.x) ))
pMPG <- ggplot( dataMPG, aes( x = displ, y = cty ) )
## individual plots
pMPG_color <- pMPG + geom_point( aes( color = manufacturer ) )
pMPG_shape <- pMPG + geom_point( aes( shape = drv ) ) + scale_shape_manual( values = c(21,22,23) )
pMPG_size <- pMPG + geom_point( aes( size = hwy ) )
pMPG_alpha <- pMPG + geom_point( aes( alpha = cyl ) ) If you have many legends (can be multiple from one plot or from multiple plots using ## plotting attempt
pMPG_color + guides( color = guide_legend( ncol = 1 ) ) +
pMPG_shape +
pMPG_size +
pMPG_alpha +
plot_layout( guides = "collect" ) &
theme( legend.direction = "vertical", legend.box = "horizontal" ) Using the approach described above, we can get a solution I want (code below). But the result is still not optimal (inconsistent padding between guides because of equal vertical distirbution of the three legend subplots, but different number of legend elements). Maybe this could be improved with better Also, this procedure does not allow for merging of legends as Note: In the code below I left ## theme for legends without plots
theme_legend <- function() {
theme_grey() %+replace%
theme(
legend.box.spacing = unit( 0, "pt" ),
legend.spacing = unit( 0, "pt" ),
legend.justification = c("left", "top"),
legend.box.just = "top",
plot.background = element_rect( fill = "lightblue" ),
panel.background = element_rect( fill = "darkblue" ),
legend.background = element_rect( fill = "darkgreen" ),
# plot.background = element_blank(),
# panel.background = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(),
plot.title = element_blank(),
plot.subtitle = element_blank(),
plot.caption = element_blank(),
plot.tag = element_blank(),
strip.background = element_blank(),
strip.text = element_blank() )
}
## legends without plots
pMPG_color_guide <- pMPG_color + coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) + theme_legend()
pMPG_shape_guide <- pMPG_shape + coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) + theme_legend()
pMPG_size_guide <- pMPG_size + coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) + theme_legend()
pMPG_alpha_guide <- pMPG_alpha + coord_cartesian( xlim = c(Inf, Inf), ylim = c(Inf, Inf) ) + theme_legend()
## plots without legends
pMPG_color <- pMPG_color + theme( legend.position = "none" )
pMPG_shape <- pMPG_shape + theme( legend.position = "none" )
pMPG_size <- pMPG_size + theme( legend.position = "none" )
pMPG_alpha <- pMPG_alpha + theme( legend.position = "none" )
## plotting
design <- c(
area(1, 1, 3, 1),
area(1, 2, 3, 2),
area(4, 1, 6, 1),
area(4, 2, 6, 2),
area(1, 3, 6, 3),
area(1, 4, 2, 4),
area(3, 4, 4, 4),
area(5, 4, 6, 4) )
pMPG_color +
pMPG_shape +
pMPG_size +
pMPG_alpha +
pMPG_color_guide +
pMPG_shape_guide +
pMPG_size_guide +
pMPG_alpha_guide +
plot_layout( design = design, widths = c(1,1,0,0) ) |
@Marc-Ruebsam, I ended up with the same approach of hacking together figures that are just legends and positioning them with I've found a bit more control over spacing is possible with approaches like |
@twest820, thank you for the confirmation! The remaining problem with the padding of the legends cannot be fixed. At least I cannot see a way without manually adjusting the heights to reflect the number of entries in the legend. Otherwise patchwork would need a feature to automatically adjust the height according to it's contents in the layout:
|
Thanks for this discussion - it is definitely an area that could be improved |
I have a similar question about arranging and combining legends. I have some data I'm trying to use this exact technique on, the issue is that not all my legend items are present across all plots, which is creating something exactly like the hwy legend in the original post. So using the 'compact' and 'mid-size' example at the top of the thread, what if I wanted to merge the 'hwy' legend item so it appeared once but showed the information for both plots? So the hwy legend would read 25, 27.5, 30, 35, 40, instead of two separate legends. |
@chelseafowler: If I understand your problem correctly it is about merging legends, rather than arranging them!? I don't think patchwork handles merging of legends with different breaks. Neither does ggplot2 by itself. You can however make sure that breaks, labels and title match and patchwork will automatically drop the duplicate for you (which can also be used to merge different legend ascetics), similar to why we include the ## libraries
library(ggplot2)
library(dplyr)
library(patchwork)
## data
mpg_data <-
mpg %>%
mutate(class = as.factor(class)) %>%
filter(class %in% c("compact","midsize"))
## plots
g1_point <-
mpg_data %>% filter(class == "compact") %>%
ggplot(aes(x = cyl, y = cty, color = class, alpha = hwy)) +
geom_point(size = 3) +
scale_alpha_continuous(limits = range(mpg_data$hwy)) +
scale_color_discrete(drop = FALSE) +
ggtitle("Compact")
g2_point <-
mpg_data %>% filter(class == "midsize") %>%
ggplot(aes(x = cyl, y = cty, color = class, alpha = hwy)) +
geom_point(size = 3) +
scale_alpha_continuous(limits = range(mpg_data$hwy)) +
scale_color_discrete(drop = FALSE) +
ggtitle("Midsize")
## plotting
g1_point +
g2_point +
plot_layout(guides = "collect") &
theme_bw() +
theme(legend.position = "bottom") Note that the breaks are chosen automatically here, while I have adjusted the limits. If you want to adjust the breaks (e.g. to 25, 27.5, 30, 35, 40), this can be achieved by using Maybe facets would be an easier solution here? ## all in one
mpg %>%
filter(class %in% c("compact","midsize")) %>%
ggplot(aes(x = cyl, y = cty, color = class, alpha = hwy)) +
theme_bw() +
theme(legend.position = "bottom") +
geom_point(size = 3) +
facet_grid(cols = vars(class)) I feel like there could be a feature request in here to merge legends with the same title automatically by adjusting the limits/breaks, when |
Hi, I was wondering if it is possible to collect only specific legends and manipulate their positions independently. For instance:
In the above example I would like to collect the color scale only, and set it at a side (
... + theme(legend.position = "right")
), whereas leaving the alpha scales at the bottom of each plot (... + theme(legend.position = "bottom")
),.Is it possible?
I didn't use facets in this case on purpose, it is just a reprex to illustrate my question.
Thank you for this amazing package, btw.
The text was updated successfully, but these errors were encountered: