Custom colour palettes for {ggplot2}
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
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")
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")
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")
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)
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.