rggplot2facet

ggplot colorbar align to top when using facets


I am using facetted plots via ggplot that contain a colorbar. I want to scale the colorbar to the size of the facetted plot. Following the ideas of @AllanCameron in this post regarding a single plot and tweaking the function to account for both the strip size and the space between plots, I am able to correctly work out the size of the legend. Thereby, the size of the legend is correctly specified irrespective of the ncol, panel.spacing.y, or strip.text.x specification.

However, by default, the legend is centered between the two plots without strips. Adjusting the legend via legend.position or legend.justification did so far not help to correctly specify it. How can I properly align the legend with the top of the chart i.e., the top of the strip (see the plot on the right side for the exptected output)?

example

I only aligned in the right chart the legend to the top without adjusting the size using an external graphics programme. Thus, while the legend height looks too small in the picture on the left chart, the size is actually fine.

library(ggplot2)

df <- expand.grid(
  x = c(1, 2, 3),
  y = c(1, 2, 3),
  g = c("one", "two", "three", "four")
)

set.seed(1)
df$v <- runif(nrow(df), min = 0, max = 1)

make_fullsize <- function() structure("", class = "fullsizebar")

ggplot_add.fullsizebar <- function(obj, g, name = "fullsizebar") {
  h <- ggplotGrob(g)$heights
  panel <- which(grid::unitType(h) == "null")
  panel_height <- unit(1, "npc") - sum(h[-c(panel, (panel-1), grep("lines", h))])

  g + 
    guides(fill = guide_colorbar(barheight = panel_height,
                                 title.position = "right")) +
    theme(legend.title = element_text(angle = -90, hjust = 0.5))
}

ggplot(df, aes(x = x, y = y, fill = v)) +
  facet_wrap(. ~ g, ncol = 2) +
  geom_tile(color = "black") +
  scale_y_discrete(expand = c(0,0)) +
  scale_x_discrete(expand = c(0,0)) +
  theme(panel.spacing.y = unit(1, "lines"),
        strip.text.x = element_text(size = 15, margin = margin(2,0,2,0, "cm")),
        strip.background.x = element_rect(
        colour = "black", linewidth = 1)) +
  make_fullsize()

Solution

  • Building on the original code by @AllanCameron and my adapted approach we can easily compute the correct bar height. However, to achieve your desired result requires some additional tweaks via theme. Set the legend.justification to bottom and remove the top and bottom legend.margin:

    library(ggplot2)
    
    make_fullsize <- function() structure("", class = "fullsizebar")
    
    ggplot_add.fullsizebar <- function(obj, g, name = "fullsizebar") {
      p <- ggplotGrob(g)
      
      h <- p$heights
      
      ix_panel <- grep("^panel", p$layout$name)
      ix_strip <- grep("^strip", p$layout$name)
      
      panel_t <- min(p$layout[ix_strip, "t"])
      panel_b <- max(p$layout[ix_panel, "b"])
      
      barheight <- unit(1, "npc") - sum(h[-seq(panel_t, panel_b)])
      
      g +
        guides(fill = guide_colorbar(
          barheight = barheight,
          title.position = "right"
        )) +
        theme(
          legend.justification = "bottom",
          legend.margin = margin(0, 5.5, 0, 5.5),
          legend.title = element_text(angle = -90, hjust = 0.5)
        )
    }
    
    ggplot(df, aes(x = x, y = y, fill = v)) +
      facet_wrap(. ~ g, ncol = 2) +
      geom_tile(color = "black") +
      scale_y_discrete(expand = c(0, 0)) +
      scale_x_discrete(expand = c(0, 0)) +
      theme(
        panel.spacing.y = unit(1, "lines"),
        strip.text.x = element_text(size = 15, margin = margin(2, 0, 2, 0, "cm")),
        strip.background.x = element_rect(
          colour = "black", linewidth = 1
        )
      ) +
      make_fullsize()
    
    ggsave("foo.png", width = 5, height = 5)
    

    enter image description here