rggplot2gridextracowplotggdendro

Right align horizontal y axis titles for multiple plots using R ggplot2


I'm having trouble right aligning horizontal y axis titles for multiple plots in R ggplot2. I have a main plot which is a dendrogram with leaf labels created using the ggdendro package, and I have multiple color bars below the main plot with titles to the left. If I use grid.arrange to place the plots on the same page, I'm able to get good vertical spacing between the plots, but I'm not able to right-align the y axis titles for the color bars consistently. If I use plot_grid, I can right-align the y axis titles consistently, but I'm having trouble getting appropriate vertical spacing between plots. Any help would be appreciated!

Update: Two suggested solutions work equally well so I'm accepting the first one as the answer. Using ggarrange from the egg package and using plot_grid with align = "v" instead of align = "hv" both fixed my problem.

Create main plot and color bars:

require(ggplot2)
require(gridExtra)
require(cowplot)
require(ggdendro)

hc = hclust(dist(USArrests), "ave")
df = data.frame(cluster = cutree(hc, 6),
                states = factor(hc$labels, levels = hc$labels[hc$order]))
p1_dendro = dendro_data(hc)

p1 = ggdendrogram(hc) +
  coord_cartesian(xlim = c(-1, nrow(df) + 1), ylim = c( -1, max(p1_dendro$segments$y)), expand = F)

p2 = ggplot(df, aes(states, y = 1, fill = factor(cluster))) + 
  ylab("y label") +
  geom_tile() + theme_minimal() +
  coord_cartesian(xlim = c(-1, nrow(df) + 1), expand = F) +
  theme(axis.title.x = element_blank(),
        axis.title.y = element_text(angle = 0, vjust = 0.5, hjust = 1),
        axis.ticks = element_blank(),
        axis.text = element_blank(),
        legend.position = "none",
        line = element_blank())

p3 = ggplot(df, aes(states, y = 1, fill = factor(cluster))) +
  ylab("a longer y label") +
  geom_tile() + theme_minimal() +
  coord_cartesian(xlim = c(-1, nrow(df) + 1), expand = F) +
  theme(axis.title.x = element_blank(),
        axis.title.y = element_text(angle = 0, vjust = 0.5, hjust = 1),
        axis.ticks = element_blank(),
        axis.text = element_blank(),
        legend.position = "none",
        line = element_blank())

grid.arrange approach:

gp1 = ggplotGrob(p1)
gp2 = ggplotGrob(p2)  
gp3 = ggplotGrob(p3)

maxWidth = grid::unit.pmax(gp1$widths[2:5], gp2$widths[2:5], gp3$widths[2:5])
gp1$widths[2:5] = as.list(maxWidth)
gp2$widths[2:5] = as.list(maxWidth)
gp3$widths[2:5] = as.list(maxWidth)

grid.arrange(gp1, gp2, gp3, ncol = 1, heights = c(8,1,1))

enter image description here

plot_grid approach:

plot_grid(p1, p2, p3, ncol = 1, align = "hv", axis = "tblr", rel_heights = c(8,1,1))

enter image description here


Solution

  • egg package will get the job done

    require(ggplot2)
    require(ggdendro)
    
    hc = hclust(dist(USArrests), "ave")
    df = data.frame(cluster = cutree(hc, 6),
                    states = factor(hc$labels, levels = hc$labels[hc$order]))
    p1_dendro = dendro_data(hc)
    
    p1 = ggdendrogram(hc) +
      coord_cartesian(xlim = c(-1, nrow(df) + 1), ylim = c( -1, max(p1_dendro$segments$y)), expand = F)
    
    p2 = ggplot(df, aes(states, y = 1, fill = factor(cluster))) + 
      ylab("y label") +
      geom_tile() + theme_minimal() +
      coord_cartesian(xlim = c(-1, nrow(df) + 1), expand = F) +
      theme(axis.title.x = element_blank(),
            axis.title.y = element_text(angle = 0, vjust = 0.5, hjust = 1),
            axis.ticks = element_blank(),
            axis.text = element_blank(),
            legend.position = "none",
            line = element_blank())
    
    p3 = ggplot(df, aes(states, y = 1, fill = factor(cluster))) +
      ylab("a longer y label") +
      geom_tile() + theme_minimal() +
      coord_cartesian(xlim = c(-1, nrow(df) + 1), expand = F) +
      theme(axis.title.x = element_blank(),
            axis.title.y = element_text(angle = 0, vjust = 0.5, hjust = 1),
            axis.ticks = element_blank(),
            axis.text = element_blank(),
            legend.position = "none",
            line = element_blank())
    

    Stack p1, p2 and p3 together using ggarrange()

    # install.packages("egg", dependencies = TRUE)
    library(egg)
    ggarrange(p1, p2, p3, 
              ncol = 1,
              heights = c(8, 1, 1))
    

    Created on 2020-08-06 by the reprex package (v0.3.0)