How to make data viz that matches your organisation’s branding

design
ggplot

Picking a colour scheme that ties in with a client’s brand or that evokes the subject matter behind the data is what allows datavisualisations to contribute to, rather than detract from, the main story the client is seeking to tell. This post covers both the design steps and the coding steps involved in creating bespoke colour schemes.

Author
Published

January 24, 2022

“Ooh look, a ggplot() plot!”

In creating polished reports and visualisations for a client, that is never the reaction we’re hoping for. Sure, the “I didn’t know you could do that in R!” reaction is always fun, but what we want above all is for the plots and tables to blend in seamlessly with the rest of the client’s aesthetic. Picking a colour scheme that ties in with a client’s brand or that evokes the subject matter behind the data is what allows the plot to contribute to, rather than detract from, the main story the client is seeking to tell.

This post explores how we go about choosing an appropriate colour scheme and how to create scale_fill/colour_discrete/continous() to align all plots with that colour scheme, using the example of a recent R for the Rest of Us project I worked on for Colibri Consulting.1

One of the first things we do when working with a client is ask if they have a branding guide. Something like this. Some of the clients do, but most don’t. How, then, do we take what we have and make plots that match the look-and-feel of the organization?

Step 1: Decide what colours to use

In the case of this project, our client had a logo with two colours

Colibri Consulting logo

Jody O’Connor, head of Colibri, was able to give us the hex codes for those colours, which was handy, but there are plenty of online tools to help you pick out the hex colours in an image or even in a website. My personal go-to is Color Picker Online which does what it says on the tin: it allows you to pick out different colours from an image or a website, and provides you with their hex codes.

So, back to Colibri Consulting. Our two starting colours were: - a deep purple: #43d293 - a bright green: #530577

Step 2: See what those colours look like when used for dataviz

There are plenty of datasets around that can be used to create plots to get a feel for the colour schemes we’ll be using. The penguins set from {palmerpenguins} is one of my favorites: it contains both continuous data (e.g. flipper lengths) and categorical data (e.g. species), along with some NAs. And, because of the way the flipper and bill lengths group together into clusters, it’s a great way of seeing how the colours highlight the different clusters of points.

Let’s put together a basic plot that we can then apply our colour schemes to:

categorical_penguins <- 
  palmerpenguins::penguins %>%
  ggplot() +
  geom_point(aes(x = bill_length_mm, y = flipper_length_mm,
                 colour = sex, size = body_mass_g),
             show.legend = F) +
  labs(title = "Palmer Penguins", 
       subtitle = "Look at them go!",
       x = "Bill length",
       y = "Flipper length") +
  theme_minimal()

categorical_penguins

I had an inkling this might happen, but when I created a plot using the colours from the client’s logo, the problem became clear: the green “popped” way more than the purple, meaning that whichever group ended up being green in our data viz would be highlighted to the detriment of the purple group.

categorical_penguins + 
  scale_colour_manual(values = c("#43d293", "#530577"))

This wasn’t the effect we were going for, so some tweaking was required.

My first step was to blend together the two colours, to see if we could get a “muted” green that tied in nicely with the original colours. I created the {monochromer} package for exactly this purpose:

monochromeR::generate_palette("#530577", modification = "blend", 
                              blend_colour = "#43d293", 
                              n_colours = 6, view_palette = T)

[1] "#530577" "#50257B" "#4D467F" "#4B6784" "#488888" "#46A98D"

The last colour in this palette is the green with a touch of purple. After some experimenting, it was still a bit too bright, so, in discussion with the client, we decided to go for a purple that was one step towards the green, and green that was one further step towards the purple as the two main colours to base the palettes on.

Here’s the penguin plot again with those two colours. It looks much more balanced!

categorical_penguins + 
  scale_colour_manual(values = c("#50257B", "#488888"))

Step 3: Check the colours work for people with different types of colour vision

Creating plots that work for as many readers as possible is important. Claire D. McWhite and Claus O. Wilke have made it easy for us #rstats users to check this with {colorblindr}, a package that simulates how plots look to readers with different colour perception deficiencies.

colorblindr::cvd_grid(
  
  categorical_penguins + 
    scale_colour_manual(values = c("#50257B", "#488888"))
  
)

The two main colours seem to come across well, but the NAs were getting lost in all but one of the plots. To rectify that, I created an NA colour which was a faded version of the middle colour between our two main colours:

middle_col <- c(monochromeR::generate_palette("#50257B",
                                              modification = "blend", 
                                              blend_colour = "#488888", 
                                              n_colours = 4), "#488888")[3]

NA_col <- monochromeR::generate_palette(middle_col, 
                                        modification = "go_both_ways", 
                                        n_colours = 5)[2]


categorical_penguins + 
  scale_colour_manual(values = c("#50257B", "#488888"), na.value = NA_col)

That made the NAs blend more into the background. Let’s check it again with {colorblindr}:

colorblindr::cvd_grid(
  
  categorical_penguins + 
    scale_colour_manual(values = c("#50257B", "#488888"), na.value = NA_col)
  
)

The are a different colour to the two main colours in all four of the plots - mission accomplished!

Step 4: Create colour/fill scales

For this project, we’ll be creating a lot of plots! We have plots some comparing two groups, some requiring more colours, and some using continuous data. We don’t want to be adding those colours manually every time! Instead, let’s create a few palettes we can call upon. First we need to get our anchors at either end of the palettes. We already have our “Purple-to-Green” anchor colours, but we also needed an all-purple palette and an all-green palette, so we needed a light purple and a light green to fade to:

# Purple extremes
monochromeR::generate_palette("#50257B", 
                              modification = "go_lighter", 
                              n_colours = 2, view_palette = T)

[1] "#50257B" "#DCD3E4"
# Green extremes
monochromeR::generate_palette("#488888",
                              modification = "go_lighter", 
                              n_colours = 2, view_palette = T)

[1] "#488888" "#DAE7E7"

Next, we use those values to create palettes that we call upon within scale_colour/fill functions. Note the NA_col applied to na_values from earlier in this post.

colibri_pal <- function(palette = "Main",
                        reverse = FALSE,
                        ...) {
  
  colibri_palettes <- list(
    "Main" = c("#50257B", "#488888"),
    "Purple" =  c("#50257B", "#DCD3E4"),
    "Green" = c("#488888", "#DAE7E7")
  )
  
  pal <- colibri_palettes[[palette]]
  
  if (reverse)
    pal <- rev(pal)
  
  grDevices::colorRampPalette(pal, ...)
  
}

# Discrete scales
scale_colour_colibri_discrete <-
  function(palette = "Main",
           reverse = FALSE,
           ...) {
    pal <- colibri_pal(palette = palette, reverse = reverse)
    
    ggplot2::discrete_scale("colour", pal,
                            na.value = NA_col,
                            palette = pal, ...)
    
  }

scale_fill_colibri_discrete <-
  function(palette = "Main",
           reverse = FALSE,
           ...) {
    pal <- colibri_pal(palette = palette, reverse = reverse)
    
    ggplot2::discrete_scale("colour", pal,
                            na.value = NA_col,
                            palette = pal, ...)
    
  }

# Continuous scales
scale_colour_colibri_continuous <-
  function(palette = "Main",
           reverse = FALSE,
           ...) {
    pal <- colibri_pal(palette = palette, reverse = reverse)
    
    ggplot2::scale_colour_gradientn(colours = pal(256),
                                   na.value = NA_col,
                                   ...)
    
  }

scale_fill_colibri_continuous <-
  function(palette = "Main",
           reverse = FALSE,
           ...) {
    pal <- colibri_pal(palette = palette, reverse = reverse)
    
    ggplot2::scale_colour_gradientn(colours = pal(256),
                                   na.value = NA_col,
                                   ...)
    
  }

Step 5: Align the text and grid lines with the colour scheme for a more polished look

The final touch is to make the rest of the colours we see in each plot line up nicely with the colour scheme: the grid lines and the text. For this, I used the “middle” colour again as a basis, creating a “light text” colour, a “light text” colour (the same as the NAs - the colour we want to blend into the background), and a “light text” colour.

light_text_col <- NA_col

dark_text_col <- monochromeR::generate_palette(middle_col, 
                                               modification = "go_both_ways", 
                                               n_colours = 5)[4]

light_col <- monochromeR::generate_palette(middle_col, 
                                           modification = "go_both_ways", 
                                           n_colours = 5)[1]

We can then use these to modify theme_minimal():

categorical_penguins +
  scale_fill_colibri_discrete("Main") +
  theme_minimal() +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major = element_line(colour = light_col),
        text = element_text(colour = light_text_col),
        plot.subtitle = element_text(size = 14, colour = light_text_col),
        axis.ticks = element_blank(),
        axis.text = element_text(colour = light_text_col),
        plot.title = element_text(colour = dark_text_col, size = 20))

Bringing it all together

Here are some plots with continuous colour scales, making use of all the steps above.

continuous_penguins +
  scale_colour_colibri_continuous()

continuous_penguins +
  scale_colour_colibri_continuous("Green")

continuous_penguins +
  scale_colour_colibri_continuous("Purple", reverse = T)

We now have custom colour palettes that allow us to make plots throughout the report that will reflect Colibri branding. A little setup in the beginning of a project sets us up for success.

There are other tricks we use to move further away from the ggplot() defaults with fonts, margins, line heights, etc. But there’s only so much we can cover in one post! We’ll get to those another day.

In the meantime, happy plotting!

Footnotes

  1. This blog post is adapted from one I wrote for the R for the Rest of Us blog↩︎

Reuse

Citation

For attribution, please cite this work as:
Thompson, Cara. 2022. “How to Make Data Viz That Matches Your Organisation’s Branding.” January 24, 2022. https://www.cararthompson.com/posts/2022-01-24-creating-and-applying-bespoke-colour-schemes/creating-and-applying-bespoke-colour-schemes.html.