Shift legend into empty facets of a faceted plot in ggplot2

后端 未结 3 455
攒了一身酷
攒了一身酷 2020-12-04 06:44

Consider the following plot:

library(ggplot2)

p <- ggplot(diamonds, 
            aes(x = carat, fill = cut)) +
  geom_density(position = \"stack\") +
  f         


        
3条回答
  •  北荒
    北荒 (楼主)
    2020-12-04 07:12

    Nice Q&A!

    I found something similar at this link. So, I thought that it would have been a nice addition to your function.

    More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.

    I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.

    Example data and libraries:

    library(ggplot2)
    library(gtable)
    library(lemon)
    
    p <- ggplot(diamonds, 
                aes(x = carat, fill = cut)) +
      geom_density(position = "stack") +
      facet_wrap(~ color) +
      theme(legend.direction = "horizontal")
    

    Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.

    shift_legend2 <- function(p) {
      # ...
      # to grob
      gp <- ggplotGrob(p)
      facet.panels <- grep("^panel", gp[["layout"]][["name"]])
      empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
      empty.facet.panels <- facet.panels[empty.facet.panels]
    
      # establish name of empty panels
      empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
      names <- empty.facet.panels$name
      # example of names:
      #[1] "panel-3-2" "panel-3-3"
    
    # now we just need a simple call to reposition the legend
      reposition_legend(p, 'center', panel=names)
    }
    
    shift_legend2(p)
    

    Note that this might still need some tweaking, I just thought it was something worth to be shared.

    At the moment the behaviour seems OK, and the function is a few lines shorter.


    Other cases.

    First example:

    p1 <- ggplot(economics_long, 
                 aes(date, value, color = variable)) +
      geom_line() +
      facet_wrap(~ variable, 
                 scales = "free_y", nrow = 2, 
                 strip.position = "bottom") +
      theme(strip.background = element_blank(), 
            strip.placement = "outside")
    
    shift_legend2(p1)
    

    Second example:

    p2 <- ggplot(mpg,
                 aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
      geom_point(size = 3) +
      facet_wrap(~ class, dir = "v") +
      theme(legend.box = "horizontal")
    
    #[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
    shift_legend2(p2) 
    

    Third example:

    p3 <- ggplot(mtcars, 
                 aes(x = factor(1), fill = factor(cyl))) +
      geom_bar(width = 1, position = "fill") + 
      facet_wrap(~ gear, nrow = 2) +
      coord_polar(theta = "y") +
      theme_void()
    shift_legend2(p3)
    


    Complete function:

    shift_legend2 <- function(p) {
      # check if p is a valid object
      if(!(inherits(p, "gtable"))){
        if(inherits(p, "ggplot")){
          gp <- ggplotGrob(p) # convert to grob
        } else {
          message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
          return(p)
        }
      } else {
        gp <- p
      }
    
      # check for unfilled facet panels
      facet.panels <- grep("^panel", gp[["layout"]][["name"]])
      empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]), 
                                   USE.NAMES = F)
      empty.facet.panels <- facet.panels[empty.facet.panels]
    
      if(length(empty.facet.panels) == 0){
        message("There are no unfilled facet panels to shift legend into. Returning original plot.")
        return(p)
      }
    
      # establish name of empty panels
      empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
      names <- empty.facet.panels$name
    
      # return repositioned legend
      reposition_legend(p, 'center', panel=names)
    }
    

提交回复
热议问题