Custom colour palettes for {ggplot2}

[This article was first published on The Jumping Rivers Blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Choosing which colours to use in a plot is an important design decision. A good choice of colour palette can highlight important aspects of your data, but a poor choice can make it impossible to interpret correctly. There are numerous colour palette R packages out there that are already compatible with {ggplot2}. For example, the {RColorBrewer} or {viridis} packages are both widely used.

If you regularly make plots at work, it’s great to have them be consistent with your company’s branding. Maybe you’re already doing this manually with the scale_colour_manual() function in {ggplot2} but it’s getting a bit tedious? Or maybe you just want your plots to look a little bit prettier? This blog post will show you how to make a basic colour palette that is compatible with {ggplot2}. It assumes you have some experience with {ggplot2} – you know your geoms from your aesthetics.

Building a colour palette

To make a custom colour palette, there are three basic things you need to do:

  • Define your colours
  • Generate a palette from your list of colours
  • Create {ggplot2} functions to use your palette

Data comes in all shapes and sizes. It can often be difficult to know where to start. Whatever your problem, Jumping Rivers can help.


Defining your colours

The process of adding colours is probably the simplest part of creating colour palette functions. We need to create a named list where the names are the names of our colour palettes. Each entry in the list is a vector of the colours in that palette. Using lists (instead of data frames) is essential because it allows us to create colour palettes with different numbers of colours. It’s most common to define colours by their hex codes, but we could also define colours by using their character names e.g. "blue", or their RGB values using the rgb() function. We’ll stick to hex codes here.

cvi_colours = list(
  cvi_purples = c("#381532", "#4b1b42", "#5d2252", "#702963",
                 "#833074", "#953784", "#a83e95"),
  my_favourite_colours = c("#702963", "#637029",    "#296370")
)

Here, we’ve kept the example small, and created a list called cvi_colours (short for corporate visual identity) with just two colour palettes.

Generating a palette

We need to create a function that generates an actual colour palette from our simple list of colours. This function will take four arguments to define:

  • the name of the colour palette we want to use,
  • the list of colour palettes we want to extract our choice from,
  • how many colours from it we want to use
  • whether we want a discrete or continuous colour palette
cvi_palettes = function(name, n, all_palettes = cvi_colours, type = c("discrete", "continuous")) {
  palette = all_palettes[[name]]
  if (missing(n)) {
    n = length(palette)
  }
  type = match.arg(type)
  out = switch(type,
               continuous = grDevices::colorRampPalette(palette)(n),
               discrete = palette[1:n]
  )
  structure(out, name = name, class = "palette")
}

If a user doesn’t input the number of colours, be default we use all of the colours in the palette. For a discrete palette, we simply use the vector of colours from cvi_colours as our colour palette. However, for continuous colour palettes, we need to use the colorRampPalette() function from {grDevices} to interpolate the given colours onto a spectrum. The switch() function then changes the output based on the chosen type of palette.

We don’t just want to simply return a vector of colours from the cvi_palettes() function, we want to add additional attributes using the structure() function. The first additional attribute is the name, which we match to the name we gave the palette in cvi_colours. The second additional attribute is a class which here we’ll call palette. By assigning a class to the colour palette, this means we can use S3 methods. S3 methods in R are a way of writing functions that do different things for objects of different classes. S3 methods aren’t really the topic of this post, but this blog post has a nice overview of them.

cvi_palettes("my_favourite_colours", type = "discrete")

Colour palette showing three vertical strips in pink, green, and blue with the word my favourite colours in the centre

Creating {ggplot2} functions

We need to define some functions so that {ggplot2} understands what to do with our colour palettes. We’ll start by defining a simple {ggplot2} plot that we’ll use to demonstrate our colour palettes later on.

library("ggplot2")
df = data.frame(x = c("A", "B", "C"),
                y = 1:3)
g = ggplot(data = df,
           mapping = aes(x = x, y = y)) +
  theme_minimal() +
  theme(legend.position = c(0.05, 0.95),
        legend.justification = c(0, 1),
        legend.title = element_blank(), 
        axis.title = element_blank())

Within {ggplot2}, there are two main ways to control the look of your plot: (i) using scale_*() functions to control the aesthetics that have been mapped to your data; or (ii) using themes. Themes control the aspects of your plot which do not depend on your data e.g. the background colour. In this blog post, we’ll focus on the scale_*() functions.

There are two aesthetics in {ggplot2} that involve colour: (i) colour, which changes the outline colour of a geom; and (ii) fill, which changes the inner colour of a geom. Note that not all geoms have both fill and colour options e.g. geom_line() is only affected by the colour aesthetic.

g + geom_col(aes(fill = x), colour = "black", size = 2) + ggtitle("Fill")
g + geom_col(aes(colour = x), fill = "white", size = 2) + ggtitle("Colour")

Two bars charts side by side. On the left the bars are coloured in pink, green, and blue. On the right, the outline is coloured in pink, green, and blue

For each aesthetic, colour and fill, the function needs to be able to handle both discrete and continuous colour palettes. We need to make two functions: one to handle a discrete variable, and one for continuous variables. We’ll start by dealing with discrete variables. Here, we pass our palette colours generated by cvi_palettes() as the values argument in the scale_colour_manual() function from {ggplot2}:

scale_colour_cvi_d = function(name) {
  ggplot2::scale_colour_manual(values = cvi_palettes(name,
                                                    type = "discrete"))
}

The function to use our colour palettes to change the fill colour is almost identical, we simply change the function name, and use scale_fill_manual() instead of scale_colour_manual().

scale_fill_cvi_d = function(name) {
  ggplot2::scale_fill_manual(values = cvi_palettes(name,
                                                    type = "discrete"))
}

Now for continuous variables. Continuous scales are similar but we use the scale_colour_gradientn() function instead of the manual scale functions. This creates an n-colour gradient scale. We set the colours used in the gradient scale using the cvi_palettes() function we defined earlier, and set the type as continuous.

scale_colour_cvi_c = function(name) {
  ggplot2::scale_colour_gradientn(colours = cvi_palettes(name = name,
                                                       type = "continuous"))
}

The scale_colour_gradientn() function already has the aesthetic argument set as colour by default, so we don’t need to worry about changing that. Again, the fill version of the function is analogous: change the name of the function, and use scale_fill_gradientn() instead of scale_colour_gradientn().

scale_fill_cvi_c = function(name) {
  ggplot2::scale_fill_gradientn(colours = cvi_palettes(name = name,
                                                     type = "continuous"))
}

To ensure that the scale_colour_*() functions work with either the British or American spelling of colour, we can simply set one equal to the other:

scale_color_cvi_d = scale_colour_cvi_d
scale_color_cvi_c = scale_colour_cvi_c

Testing our colour palettes

Now that we have all the functions we need, we can call them in the same way we would with any scale_*() function in {ggplot2}:

g +
  geom_point(aes(colour = y), size = 3) +
  scale_colour_cvi_c("cvi_purples")
g +
  geom_col(aes(fill = x), size = 3) +
  scale_fill_cvi_d("my_favourite_colours")

Two charts side by side. On the left the points are coloured in gradients of purple. On the right, the bars in the bar chart are coloured in pink, green, and blue.

Extending the functionality of your colour palettes

The colour palette functions we’ve defined here are fairly basic, and we may want to add some additional functionality to them.

Printing colour palettes

Users will often want to view the colours in a palette on their screen before they go the effort of implementing it. Although this technically isn’t necessary to make your colour palette work, it’s extremely useful to anyone using it. Earlier, we defined the palette class, so we could create a function print.palette() which automatically prints a plot of any object with the class palette.

Turn your colour palettes into an R package

If you have a collection of functions that work together and you need to use them in multiple projects, it’s best (at least in the long run) to turn them into an R package. All of the colour palettes I’ve used in my work have been part of an R package. Making your palette functions into a package also makes it easier to share them with other people (super helpful if the colour palettes are for work).

If you’ve never made an R package before, check out our previous blog post on Writing a Personal R Package to help you get started.

Discrete vs continuous palettes

Some of the colour palettes you define might work better for continuous variables, and some may work better for discrete variables. At the moment, any colour palette can be used for either discrete or continuous variables at the user’s discretion. You may want to restrict which palettes are used with which type of palette, or at least provide a warning message to a user.

For example using the my_favourite_colours palette doesn’t look very nice when we interpolate the colours for a continuous palette.

cvi_palettes("my_favourite_colours", type = "continuous", n = 20)

Colour palette showing three vertical strips of pink, green, and blue blended together in a gradient with the word my favourite colours in the centre

Order the colours

By default colours are returned in the order you define them in the list. For continuous colour palettes this works quite well, as it ensures colours go from light to dark, or vice versa. However, for discrete palettes we may want to rearrange the colours to ensure greater contrast between colours displayed next to each other. The cvi_palettes() function could be edited to return colours in a different order if a discrete palette is chosen.

Similarly, many colour palettes include a "direction" argument which reverses the order in which the colours in the palette are used e.g. going from light to dark instead of dark to light.

Checking for colourblind palettes

It’s a good idea to check if your colour palettes are colourblind friendly, especially for discrete palettes. Sequential colour palettes usually have a better chance of being colourblind friendly. David Nichols provides a tool for seeing what your palettes may look like to people who are colourblind. If you choose to include colour palettes that are not colourblind friendly, it may be useful to include a warning for users.

Final thoughts

By now, you should have the tools to create your own simple colour palette functions for using with {ggplot2}. Most of the functions described are based on those used in the {MetBrewer} and {wesanderson} colour palettes. If you want to see examples of some of these extensions implemented in a larger colour palette package, check out the source code for those packages on GitHub.


Jumping Rivers Logo

For updates and revisions to this article, see the original post

To leave a comment for the author, please follow the link and comment on their blog: The Jumping Rivers Blog.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)