3

I'm building a custom ggplot theme to standardize the look & feel of graphs I produce. The goal is more complex than this minimal example, so I'm looking for a general solution. I have a few key goals:

  • I want all graphs to export at the same size (3000 pixels wide, 1500 pixels high).
  • I want to control the aspect ratio of the plot panel itself.
  • I want to use textGrobs to include figure numbers.
  • I want the image to be left-aligned

The challenge I'm facing is that when combining these two constraints, the image that gets saved centers the ggplot graph within the window, which makes sense as a default, but looks bad in this case.

I'm hoping there's a general solution to left-align the ggplot panel when I export. Ideally, this will also work similarly for faceted graphs.

It seems that something should be possible using one of or some combination of the gridExtra, gtable, cowplot, and egg packages, but after experimenting for a few hours I'm at a bit of a loss. Does anybody know how I can accomplish this? My code is included below.

This is the image that gets produced. As you can see, the caption is left-aligned at the bottom, but the ggplot itself is horizontally centered. I want the ggplot graph left-aligned as well.

Graph output: https://i.stack.imgur.com/5EM2c.png

library(ggplot2)

# Generate dummy data
x <- paste0("var", seq(1,10))
y <- LETTERS[1:10]
data <- expand.grid(X=x, Y=y)
data$Z <- runif(100, -2, 2)

# Generate heatmap with fixed aspect ratio
p1 <- ggplot(data, aes(X, Y, fill= Z)) + 
    geom_tile() +
    labs(title = 'A Heatmap Graph') +
    theme(aspect.ratio = 1)


# A text grob for the footer
figure_number_grob <- grid::textGrob('Figure 10',
                                     x = 0.004,
                                     hjust = 0,
                                     gp = grid::gpar(fontsize = 10,
                                                     col = '#01A184'))

plot_grid <- ggpubr::ggarrange(p1,
                               figure_number_grob,
                               ncol = 1,
                               nrow = 2,
                               heights = c(1,
                                           0.05))

# save it
png(filename = '~/test.png', width = 3000, height = 1500, res = 300, type = 'cairo')
print(plot_grid)
dev.off()
Michael Toth
  • 75
  • 10

1 Answers1

0

I was able to find a solution to this that works for my needs, though it does feel a bit hacky.

Here's the core idea:

  • Generate the plot without a fixed aspect ratio.
  • Split the legend from the plot as its own component
  • Use GridExtra's arrangeGrob to combine the plot, a spacer, the legend, and another spacer horizontally
  • Set the width of the plot to some fraction of npc (normal parent coordinates), in this case 0.5. This means that the plot will take up 50% of the horizontal space of the output file.
    • Note that this is not exactly the same as setting a fixed aspect ratio for the plot. If you know the size of the output file, it's close to the same thing, but the size of axis text & axis titles will affect the output aspect ratio for the panel itself, so while it gets you close, it's not ideal if you need a truly fixed aspect ratio
  • Set the width of the spacers to the remaining portion of the npc (in this case, 0.5 again), minus the width of the legend to horizontally center the legend in the remaining space.

Here's my code:

library(ggplot2)

# Generate dummy data
x <- paste0("var", seq(1,10))
y <- LETTERS[1:10]
data <- expand.grid(X=x, Y=y)
data$Z <- runif(100, -2, 2)

# Generate heatmap WITHOUT fixed aspect ratio. I address this below
p1 <- ggplot(data, aes(X, Y, fill= Z)) + 
    geom_tile() +
    labs(title = 'A Heatmap Graph')

# Extract the legend from our plot
legend = gtable::gtable_filter(ggplotGrob(p1), "guide-box")


plot_output <- gridExtra::arrangeGrob(
    p1 + theme(legend.position="none"),                           # Remove legend from base plot
    grid::rectGrob(gp=grid::gpar(col=NA)),                        # Add a spacer
    legend,                                                       # Add the legend back
    grid::rectGrob(gp=grid::gpar(col=NA)),                        # Add a spacer
    nrow=1,                                                       # Format plots in 1 row
    widths=grid::unit.c(unit(0.5, "npc"),                         # Plot takes up half of width
                        (unit(0.5, "npc") - legend$width) * 0.5,  # Spacer width
                        legend$width,                             # Legend width
                        (unit(0.5, "npc") - legend$width) * 0.5)) # Spacer width

# A text grob for the footer
figure_number_grob <- grid::textGrob('Figure 10',
                                     x = 0.004,
                                     hjust = 0,
                                     gp = grid::gpar(fontsize = 10,
                                                     col = '#01A184'))

plot_grid <- ggpubr::ggarrange(plot_output,
                               figure_number_grob,
                               ncol = 1,
                               nrow = 2,
                               heights = c(1,
                                           0.05))

# save it
png(filename = '~/test.png', width = 3000, height = 1500, res = 300, type = 'cairo')
print(plot_grid)
dev.off()

And here's the output image: https://i.stack.imgur.com/rgzFy.png

Michael Toth
  • 75
  • 10