Fixing awkward backgrounds in ggplot2

Coding tips
ggplot2
When creating graphs and maps with a fixed coordinate system (e.g. 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}.
Author
Affiliation

Building Stories with Data

Published

March 6, 2025

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)

penguins_donut <- palmerpenguins::penguins |>
  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

A ggplot2 donut chart in which the background colour doesn't fill the export area, and also doesn't give enough space for the title and legend

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 ![](images/penguins_donut.png) 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 ggsaveing 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.

cowplot::plot_grid(penguins_donut) +
  theme(plot.background = element_rect(fill = "darkred", colour = "darkred"))

A ggplot2 donut chart where a dark red background colour fills all the export area; the background of the plot is dark blue

And now. the more useful solution:

cowplot::plot_grid(penguins_donut) +
  theme(plot.background = element_rect(fill = "#2c3d4f", colour = "#2c3d4f"))

A ggplot2 donut chart where a dark blue background colour fills all the export area

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.

framed_penguins <- penguins_donut +
  theme(plot.background = element_rect(fill = "#2c3d4f", colour = "pink", linewidth = 6))
  
cowplot::plot_grid(framed_penguins) +
  theme(plot.background = element_rect(fill = "#2c3d4f", colour = "#2c3d4f"))

A ggplot2 donut chart where a dark red background colour fills all the export area but there is a pink frame around the donut and the title and legend, which should be around the whole image - the frame crops the title and legend and makes the plot look messy.

Option 3: Modify the space around the plot area

Here’s our starting point, with our framed penguins.

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)!

framed_penguins +
  theme(aspect.ratio = 1/3)

A streched ggplot2 donut, because we have modified the aspect ratio of the coordinates as well as of the image dimensions

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.

framed_penguins +
  theme(plot.margin = margin(12, 60, 12, 60))

A ggplot2 donut chart where a dark red background colour fills enough of the export area that nothing looks cropped, but we still have white background either side of the dark blue background which makes it look odd against the grey background of the rest of the article.

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:


fix_background <- function(my_plot) {
  
  # Extract the theme background settings from the plot
  # so we can reuse them later
  existing_bg <- my_plot$theme$plot.background
  
  plot_with_no_background <- my_plot +
    # Remove the background from the plot
    theme(plot.background = element_blank())
  
  cowplot::plot_grid(plot_with_no_background) +
    # 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!

fix_background <- function(my_plot) {
  
  cowplot::plot_grid(my_plot +
                       theme(plot.background = element_blank())) +
    theme(plot.background = my_plot$theme$plot.background)
  
}

Let’s check it does what we want it to do!

framed_penguins |>
  fix_background()

A ggplot2 donut chart where a dark red background colour and a pink frame are the perfect size for the size of the image we're exporting

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!

# With fig.height = 2.5
framed_penguins |>
  fix_background()

A ggplot2 donut chart where a dark red background colour and a pink frame are the perfect size for the size of the image we're exporting, despite us having made the image height smaller than the previous iteration.

# With out.width=50% and centered
framed_penguins |>
  fix_background()

A ggplot2 donut chart where a dark red background colour and a pink frame are the perfect size for the size of the image we're exporting, despite us exporting it to 50% width of the article page.


Happy days!

Reuse

Citation

For attribution, please cite this work as:
Thompson, Cara. 2025. “Fixing Awkward Backgrounds in Ggplot2.” March 6, 2025. https://www.cararthompson.com/posts/2025-03-06-fixing-awkward-backgrounds-in-ggplot.