Align multiple plots in ggplot2 when some have legends and others don't

前端 未结 6 1451
离开以前
离开以前 2020-11-30 03:09

I have used the method indicated here to align graphs sharing the same abscissa.

But I can\'t make it work when some of my graphs have a legend and others don\'t.

相关标签:
6条回答
  • 2020-11-30 03:45

    Expanding on @Axeman's answer, you can do all of this with cowplot without ever needing to use draw_plot directly. Essentially, you just make the plot in two columns -- one for the plots themselves and one for the legends -- and then place them next to each other. Note that, because g2 has no legend, I am using an empty ggplot object to hold the place of that legend in the legends column.

    library(cowplot)
    
    theme_set(theme_minimal())
    
    plot_grid(
      plot_grid(
        g1 + theme(legend.position = "none")
        , g2
        , g3 + theme(legend.position = "none")
        , ncol = 1
        , align = "hv")
      , plot_grid(
        get_legend(g1)
        , ggplot()
        , get_legend(g3)
        , ncol =1)
      , rel_widths = c(7,3)
      )
    

    Gives

    The main advantage here, in my mind, is the ability to set and skip legends as needed for each of the subplots.

    Of note is that, if all of the plots have a legend, plot_grid handles the alignment for you:

    plot_grid(
      g1
      , g3
      , align = "hv"
      , ncol = 1
    )
    

    gives

    It is only the missing legend in g2 that causes problems.

    Therefore, if you add a dummy legend to g2 and hide it's elements, you can get plot_grid to do all of the alignment for you, instead of worrying about manually adjusting rel_widths if you change the size of the output

    plot_grid(
      g1
      , g2 + 
          geom_line(aes(color = "Test")) +
          scale_color_manual(values = NA) +
          theme(legend.text = element_blank()
                , legend.title = element_blank())
      , g3
      , align = "hv"
      , ncol = 1
    )
    

    gives

    This also means that you can easily have more than one column, but still keep the plot areas the same. Simply removing , ncol = 1 from above yields a plot with 2 columns, but still correctly spaced (though you'll need to adjust the aspect ratio to make it useable):

    As @baptiste suggested, you can also move the legends over so that they are all aligned to the left of in the "legend" portion of the plot by adding theme(legend.justification = "left") to the plots with the legends (or in theme_set to set globally), like this:

    plot_grid(
      g1 +
        theme(legend.justification = "left")
      , 
      g2 + 
        geom_line(aes(color = "Test")) +
        scale_color_manual(values = NA) +
        theme(legend.text = element_blank()
              , legend.title = element_blank())
      , g3 +
        theme(legend.justification = "left")
      , align = "hv"
      , ncol = 1
    )
    

    gives

    0 讨论(0)
  • 2020-11-30 03:56

    Thanks to this and that, posted in the comments (and then removed), I came up with the following general solution.

    I like the answer from Sandy Muspratt and the egg package seems to do the job in a very elegant manner, but as it is "experimental and fragile", I preferred using this method:

    #' Vertically align a list of plots.
    #' 
    #' This function aligns the given list of plots so that the x axis are aligned.
    #' It assumes that the graphs share the same range of x data.
    #'
    #' @param ... The list of plots to align.
    #' @param globalTitle The title to assign to the newly created graph.
    #' @param keepTitles TRUE if you want to keep the titles of each individual
    #' plot.
    #' @param keepXAxisLegends TRUE if you want to keep the x axis labels of each
    #' individual plot. Otherwise, they are all removed except the one of the graph
    #' at the bottom.
    #' @param nb.columns The number of columns of the generated graph.
    #'
    #' @return The gtable containing the aligned plots.
    #' @examples
    #' g <- VAlignPlots(g1, g2, g3, globalTitle = "Alignment test")
    #' grid::grid.newpage()
    #' grid::grid.draw(g)
    VAlignPlots <- function(...,
                           globalTitle = "",
                           keepTitles = FALSE,
                           keepXAxisLegends = FALSE,
                           nb.columns = 1) {
      # Retrieve the list of plots to align
      plots.list <- list(...)
    
      # Remove the individual graph titles if requested
      if (!keepTitles) {
        plots.list <- lapply(plots.list, function(x) x <- x + ggtitle(""))
        plots.list[[1]] <- plots.list[[1]] + ggtitle(globalTitle)
      }
    
      # Remove the x axis labels on all graphs, except the last one, if requested
      if (!keepXAxisLegends) {
        plots.list[1:(length(plots.list)-1)] <-
          lapply(plots.list[1:(length(plots.list)-1)],
                 function(x) x <- x + theme(axis.title.x = element_blank()))
      }
    
      # Builds the grobs list
      grobs.list <- lapply(plots.list, ggplotGrob)
    
      # Get the max width
      widths.list <- do.call(grid::unit.pmax, lapply(grobs.list, "[[", 'widths'))
    
      # Assign the max width to all grobs
      grobs.list <- lapply(grobs.list, function(x) {
        x[['widths']] = widths.list
        x})
    
      # Create the gtable and display it
      g <- grid.arrange(grobs = grobs.list, ncol = nb.columns)
      # An alternative is to use arrangeGrob that will create the table without
      # displaying it
      #g <- do.call(arrangeGrob, c(grobs.list, ncol = nb.columns))
    
      return(g)
    }
    
    0 讨论(0)
  • 2020-11-30 04:01

    One trick is to plot and align the graphs without any legends, and then plotting the legend separately next to it. cowplot has a convenience function for quickly getting the legend from a plot, and plot_grid allows for automatic allignment.

    library(cowplot)
    theme_set(theme_grey())
    
    l <- get_legend(g1)
    ggdraw() +
      draw_plot(plot_grid(g1 + theme(legend.position = 'none'), g2, ncol = 1, align = 'hv'),
                width = 0.9) +
      draw_plot(l, x = 0.9, y = 0.55, width = 0.1, height = 0.5)
    

    0 讨论(0)
  • 2020-11-30 04:03

    There might now be easier ways to do this, but your code was not far wrong.

    After you have ensured that the widths of columns 2 and 3 in gA are the same as those in gB, check the widths of the two gtables: gA$widths and gB$widths. You will notice that the gA gtable has two additional columns not present in the gB gtable, namely widths 7 and 8. Use the gtable function gtable_add_cols() to add the columns to the gB gtable:

    gB = gtable::gtable_add_cols(gB, sum(gA$widths[7:8]), 6)
    

    Then proceed with arrangeGrob() ....

    Edit: For a more general solution

    Package egg (available on github) is experimental and fragile, but works nicely with your revised set of plots.

    # install.package(devtools)
    devtools::install_github("baptiste/egg")
    
    library(egg)
    grid.newpage()
    grid.draw(ggarrange(g1,g2,g3, ncol = 1))
    

    0 讨论(0)
  • 2020-11-30 04:03

    Using grid.arrange

    library(ggplot2)
    library(reshape2)
    library(gridExtra)
    
    x = seq(0, 10, length.out = 200)
    y1 = sin(x)
    y2 = cos(x)
    y3 = sin(x) * cos(x)
    df1 <- data.frame(x, y1, y2)
    df1 <- melt(df1, id.vars = "x")
    g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line()
    df2 <- data.frame(x, y3)
    g2 <- ggplot(df2, aes(x, y3)) + geom_line()
    
    #extract the legend from the first graph
    temp <- ggplotGrob(g1)
    leg_index <- which(sapply(temp$grobs, function(x) x$name) == "guide-box")
    legend <- temp$grobs[[leg_index]]
    
    #remove the legend of the first graph
    g1 <- g1 + theme(legend.position="none")
    
    #define position of each grobs/plots and width and height ratio
    grid_layout <- rbind(c(1,3),
                        c(2,NA))
    grid_width <- c(5,1)
    grid_heigth <- c(1,1)
    
    
    grid.arrange(
      grobs=list(g1, g2,legend),
      layout_matrix = grid_layout,
      widths = grid_width,
      heights = grid_heigth)
    

    0 讨论(0)
  • 2020-11-30 04:06

    The patchwork package by Thomas Lin Pedersen does this all automagically:

    library(patchwork)
    g1 + g2 + plot_layout(ncol = 1)
    

    Can hardly get any easier than that.

    0 讨论(0)
提交回复
热议问题