Fixing awkward backgrounds in ggplot2
coord_sf()
or coord_polar()
), the background colour can end up not covering the entire export area. There are a few different ways of fixing the issue - one of which I think is more elegant than the others, involving a simple function which makes use of {cowplot}.
One of my clients asked me how to fix the fact that the background colour from the team’s custom theme didn’t fill the full plotting area when they were creating maps. There are several ways to fix this, but first, let’s replicate the problem. To help us see this clearly, I have made the background of this article a light grey colour. This allows us to see where the gap is between the graph background colour and the rest of the document. But it’s also pretty clear that there’s a problem from the fact that the title and the legend stick out from the sides of the background. Here’s our starting point.
Code
library(ggplot2)
<- palmerpenguins::penguins |>
penguins_donut ggplot() +
geom_bar(aes(x = 1,
fill = species),
colour = "#2c3d4f", linewidth = 2) +
labs(title = "Three species of penguins,\nall enjoying a donut!",
legend = "") +
scale_fill_manual(values = c("Chinstrap" = "pink",
"Adelie" = "orange",
"Gentoo" = "darkgreen")) +
coord_polar(theta = "y") +
xlim(c(-0.25, 1.5)) +
theme_void() +
theme(text = element_text(size = 11,
colour = "#fafafa",
family = "Noah",
face = "bold"),
plot.title = element_text(size = rel(1.6),
margin = margin(rep(11*2, 4)),
hjust = 0.5),
legend.title = element_blank(),
legend.position = "top",
plot.background = element_rect(colour = "#2c3d4f",
fill = "#2c3d4f"))
penguins_donut
Option 1: Export with a background colour and embed the saved image
The easiest solution, which just requires us to specify the bg
argument in ggsave()
. It saves trying to figure out the optimal aspect ratio of the image to avoid white space.
|>
penguins_donut ggsave(filename = "images/penguins_donut.png", height = 3.5, width = 5, bg = "#2c3d4f")
In our document, we then just need to use 
and Tada!
This works well as a one off, and if you’re working on a parameterised report, you could code something to always bring in the right images that you are ggsave
ing by being clever with how you create and reuse paths and file names, but that can get fiddly.
In terms of consistency across the document, it also can be a bit of a headache trying to figure out the dimensions of your image. In this article, I’ve set all my figures to have a height of 3.5 at the start of the document, using knitr::opts_chunk$set(fig.height = 3.5)
. But if I want the text size to look the same across an embedded image as the other images in the report, I need to figure out the optimal width of the image (which I didn’t do here), so that it doesn’t zoom in or out to fit the width of the page (see how much bigger the text is?). I could also change the size of the image, but then I need to think about alignments, how wide the column of text is, and we’re into more decision making and hacking solutions when we really just want everything to look consistent with the promised effortless styling that comes with having your own ggplot theme… So let’s explore other solutions!
Option 2: Use cowplot to integrate your fixed coord plot into a non fixed surface area
A great advantage of the output from cowplot::plot_grid()
still being a ggplot object is that we can add a further theme()
to it! Here’s a demo with a red background, so we can see what’s going on.
::plot_grid(penguins_donut) +
cowplottheme(plot.background = element_rect(fill = "darkred", colour = "darkred"))
And now. the more useful solution:
::plot_grid(penguins_donut) +
cowplottheme(plot.background = element_rect(fill = "#2c3d4f", colour = "#2c3d4f"))
The only issue you’ll run into here is if you have a different colour around your graphs baked into your theme to create a nice frame.
Option 3: Modify the space around the plot area
Here’s our starting point, with our framed penguins.
We can modify the aspect ratio of any graph using theme(aspect.ratio=[our fraction])
. But this is not what we want to do, because it will stretch the donut (or the map, or whatever you have made that needs a fixed coordinate system)!
Intead, we can modify the margins around the plot, and end up with something more along these lines. The margins are in order top, right, bottom, left
, which I remember as TRouBLe
.
That allowed us to fix the problem of title and legend spilling over the edge of the frame imposed by the fixed coordinates, which may be all you needed when you clicked onto this post! But it didn’t fix the issue of having a white background filling in the gap between our background and the background of the image section, so we still need a solution for that.
Option 4: Write a function to take the existing background and apply it the the cowplot output
At this point in writing this post, I tried to do something clever, by working out the width of the image in the inspect console, translating that to units that ggplot2::margin
understands, doing some maths… and it got pretty messy. Instead, this is a much more elegant solution!
Here’s the verbose approach, so we can easily see what’s happening in the code:
<- function(my_plot) {
fix_background
# Extract the theme background settings from the plot
# so we can reuse them later
<- my_plot$theme$plot.background
existing_bg
<- my_plot +
plot_with_no_background # Remove the background from the plot
theme(plot.background = element_blank())
::plot_grid(plot_with_no_background) +
cowplot# Add the theme background settings into the cowplot theme
theme(plot.background = existing_bg)
}
And here’s a more succinct version. This 👇 is the tiny function I added to the client’s Dataviz Design System package, along with a short vignette on how to make use it with maps as well as graphs. Feel free to pinch it and reuse it!
<- function(my_plot) {
fix_background
::plot_grid(my_plot +
cowplottheme(plot.background = element_blank())) +
theme(plot.background = my_plot$theme$plot.background)
}
Let’s check it does what we want it to do!
Whoop! The beauty of this solution is that we can apply it to all sorts of circumstances, resolutions, screen sizes, etc without resorting to clever maths!
Happy days!
