tidyverse / ggplot2

An implementation of the Grammar of Graphics in R
https://ggplot2.tidyverse.org
Other
6.48k stars 2.02k forks source link

Multiple legends with position="inside" #5712

Open MauricioCely opened 7 months ago

MauricioCely commented 7 months ago

I have recently installed the ggplot2 3.5.0 release, and after reading the new features added to the Legends Placement, I was very curious to know if it is possible to have multiple inside legends (position = "inside") at different positions by using the new legend.position.inside argument for each guide. For example, cty legend at bottom-right and cyl at top-right. However, it does not work.

library(ggplot2) # Create Elegant Data Visualisations Using the Grammar of Graphics CRAN v3.5.0

ggplot(mpg, aes(displ, hwy, shape = drv, colour = cty, size = year)) +
  geom_point(aes(alpha = cyl)) +
  guides(
    colour = guide_colourbar(position = "inside",
                             theme = theme(legend.position.inside = c(1, 0))),
    size   = guide_legend(position = "top"),
    alpha  = guide_legend(position = "inside", 
                          theme = theme(legend.position.inside = c(0, 1))),
    shape  = guide_legend(position = "left")
  )

image

It seems only works by using the global theme’s legend.position.inside arguments, grouping both legends at the same position, but for now is not possible to not place them separately.

thomasp85 commented 7 months ago

This is not possible at this time

teunbrand commented 7 months ago

Should this be possible? I cannot think of a compelling use case on top of my head, or at least, more compelling than satisfying curiosity.

MauricioCely commented 7 months ago

Some time ago, I saw this issue in Stackoverflow:

https://stackoverflow.com/questions/21798364/independently-move-2-legends-ggplot2-on-a-map

botanize commented 5 months ago

I'm running into this as well. The documentation doesn't say anything about which components of theme are respected in guide_legend, at the moment there's no warning or error, the plot just doesn't respect the API as documented.

twest820 commented 4 months ago

I seem to be hitting either a related issue or another manifestation of this, though the use case is a bit different as it's a single legend per figure with multiple figures patchworked together. The deprecation warning tells you to use legend.position.inside but changing from legend.position to legend.position.inside results in legends being shown outside the figures unless guide_legend(position = "inside") is also specified. While the OP's question isn't supported it's unclear what interactions with the global theme are currently supported.

It's easy to read the the blog post as saying this isn't expected behavior: "To cover such cases, there is now a specialised legend.position.inside argument that controls the positioning of legends with position = "inside" regardless of whether the position was specified in the theme or in the guide." but it's not entirely clear to which of position and legend.position.inside it's referring to.

teunbrand commented 4 months ago

I understand the confusion this may have caused and it should be made clearer in the documentation which theme elements are used in the legend itself versus which ones are used to compose the legend within the plot.

With regards to positions; The position argument of the guide function determines in which area of the plot it is displayed. In absence of such argument, the global theme(legend.position) argument is used. It never uses the local theme for this, as it has an argument that serves this function. The theme(legend.position.inside) argument determines where legends with position = "inside" (directly or by defaulting through the global theme) are placed in the plot panel.

With regards to which theme elements guides respond to; As a rule of thumb, these are the elements that only affect a single guide. Not elements that rule how guides are positioned in the guide box (e.g. legend.justification, legend.location, legend.box, legend.box.just and legend.box.spacing, legend.position and legend.position.inside, legend.spacing). To get an exact overview of which elements are taken from the theme, you can inspect a guide's elements field:

ggplot2::guide_legend()$elements
#> $background
#> [1] "legend.background"
#> 
#> $margin
#> [1] "legend.margin"
#> 
#> $key
#> [1] "legend.key"
#> 
#> $key_height
#> [1] "legend.key.height"
#> 
#> $key_width
#> [1] "legend.key.width"
#> 
#> $text
#> [1] "legend.text"
#> 
#> $theme.title
#> [1] "legend.title"
#> 
#> $spacing_x
#> [1] "legend.key.spacing.x"
#> 
#> $spacing_y
#> [1] "legend.key.spacing.y"
#> 
#> $text_position
#> [1] "legend.text.position"
#> 
#> $title_position
#> [1] "legend.title.position"
#> 
#> $byrow
#> [1] "legend.byrow"

Created on 2024-05-31 with reprex v2.1.0

neellab-umd commented 1 month ago

I am running into this issue as well. I have two legends I want to place inside my map and I want to control where I put each of them separately so they plot around the actual contents of the map. But it seems I can only control the location inside the plot for one legend separately with guides() or all legends together in theme(). The only workarounds seem to be creating the legends as separate objects and then plotting them together with the original ggplot with cowplot. Seems like a very inefficient way to do this. Is it really the only way?

teunbrand commented 1 month ago

Currently, that is the only way I imagine.