Creating a custom palette

In the code chunk below, we define a custom color palette for your organization. This palette can be used in your visualizations to maintain brand consistency. We also define some functions to extract colors from the palette and create color scales for a wide variety of ggplot2 visualizations.

1 What this code provides

The purpose of this code is to create a custom color palette for your organization that is also easy to use. The code includes:

  • A named vector of your organization’s color hex codes (that you can, and should, customize)
  • A named list of palettes using your organization’s colors that are appropriate for sequential, divergent, and qualitative data that is either discrete or continuous
  • A way to use a single palette for either discrete or continuous data — there’s no need to use different functions for different types of data
  • Functions that you can use without any changes at all

2 How to use this code

The functions in this code are designed to make it easy to use your organization’s color palette in ggplot2 visualizations. You can use the functions scale_color_org() and scale_fill_org() to apply the color palette to your plots. These are the only functions that you need to use in your plots. The other functions are used internally to create the color scales.

In the following, we assume that you already have a ggplot2 plot object called my_plot. You need to know the following about the my_plot object before applying the color palette:

  • the type of data you are coloring (discrete or continuous); this will determine whether the discrete argument is set to TRUE or FALSE
  • whether you are coloring the color or the fill of the graph; this will determine whether you use scale_color_org() or scale_fill_org()
  • the type of palette you want to use (sequential, divergent, or categorical); this will determine which palette you use in the palette argument
  • only if you are using a divergent palette, you need to know the midpoint of the data; this will determine the midpoint argument

Calling scale_color_org() or scale_fill_org() works the same, so we only show one of the functions in each example.

2.1 Divergent/Discrete

This is an example of setting a color palette for a discrete variable that is diverging.

my_plot +
  scale_color_org(palette = "divergent3", 
                  midpoint = 0,
                  discrete = TRUE)

2.2 Divergent/Continuous

This is an example of setting a color palette for a continuous variable that is diverging. Note, the functions that we have defined are essentially helper functions for scale_*_grandientn() functions; as such, any arguments that are not used within the scale_*_org() functions can be passed directly to the scale_*_gradientn() functions.

my_plot +
  scale_color_org(
    palette = "divergent3",
    discrete = FALSE,
    midpoint = 0,
    limits = c(-1 * max_limit, max_limit)
  )

2.3 Sequential/Discrete

This is an example of setting a color palette for a discrete variable that is sequential.

my_plot +
  scale_fill_org(palette = "sequential3", 
                 discrete = TRUE)

2.4 Sequential/Continuous

This is an example of setting a color palette for a continuous variable that is sequential.

my_plot +
  scale_color_org(palette = "sequential2", 
                  discrete = FALSE)

2.5 Categorical

This is an example of setting a color palette for a categorical variable.

my_plot +
  scale_color_org(palette = "cool", 
                  discrete = TRUE)

3 Customizing this palette

Here’s the process for customizing this code for your organization:

  1. Copy the code below into your own R script file.
  2. Save the file as colorsorg.R in your project directory (or wherever you put your R scripts).
  3. Customize the org_colors vector with your organization’s color hex codes.
  4. Re-define the palettes in the org_palettes list to use the colors you defined in the org_colors vector. You can use the same names as the ones in the example, or you can change them to something that makes more sense for your organization.
  5. Load the colorsorg.R file into your R session (or into your own R script) using the source() function.
  6. Call the scale_color_org() or scale_fill_org() function in your plots to apply the color palette as you would in any other ggplot chart.
  7. When you are fully satisfied with any customization that you are going to do, then distribute this colorsorg.R file to your colleagues so they can use the same theme in their plots. This is just another way that you can make it easier for your analytical team’s customers to use your work.

4 The code itself

The following is the code that encapsulates all of the above functionality. You can copy and paste this into your own R script file to use it.

# Define your organization's color palettes

#' Your organization's brand colors (hex codes)
#'
#' A named vector of your organization's color hex codes.
#'
#' @export
org_colors <- c(
  `purple` = "#582c83",
  `midnight purple` = "#201547",
  `gray` = "#54585a",
  `light blue` = "#aadeeb",
  `red` = "#e3322b",
  `yellow` = "#f2be1a",
  `green` = "#669933",
  `light gray` = "#f5f5f5",
  `medium gray` = "#c0c0c0",
  `royal blue` = "#4169E1"
)

#' Extract your organization's colors by name
#'
#' @param ... Character names (from the vector above) of the colors to extract
#' @return A character vector of hex codes
#' @export
org_cols <- function(...) {
  cols <- c(...)
  
  if (is.null(cols))
    return (org_colors)
  
  org_colors[cols]
}

#' Your organization's color palettes
#'
#' A list of named palettes using your organization's colors
#' @export
org_palettes <- list(
  `main` = org_cols("purple", "gray", "light blue", "red", "yellow", "green"),
  `cool` = org_cols("green", "light blue", "gray"),
  `gray` = org_cols("gray", "light gray", "medium gray"),
  `divergent1` = org_cols("red", "light gray", "purple"),
  `divergent2` = org_cols("royal blue", "light gray", "midnight purple"),
  `divergent3` = org_cols("red", "light gray", "green"),
  `sequential1` = org_cols("light blue", "royal blue"),
  `sequential2` = org_cols("light gray", "midnight purple"),
  `sequential3` = org_cols("light gray", "purple"),
  `evaluate` = org_cols("red", "yellow", "green")
)

#' Interpolated color palette from your organization's palette
#'
#' @param palette Name of palette to use
#' @param reverse Logical. Should the palette be reversed?
#' @param ... Additional arguments passed to colorRampPalette()
#' @return A function to generate interpolated colors
#' @export
org_pal <- function(palette = "main", 
                    reverse = FALSE, ...) {
  pal <- org_palettes[[palette]]
  
  if (reverse) pal <- rev(pal)
  
  colorRampPalette(pal, ...)
}

#' Color scale using your organization's color palettes
#'
#' @param palette Palette name
#' @param discrete Is the scale discrete?
#' @param reverse Reverse the palette?
#' @param midpoint Optional midpoint value for diverging scales
#' @param ... Other arguments to scale functions
#' @export
scale_color_org <- function(palette = "main", 
                            discrete = TRUE, 
                            reverse = FALSE, 
                            midpoint = NULL, ...) {
  pal <- org_pal(palette = palette, reverse = reverse)
  
  if (discrete) {
    ggplot2::discrete_scale(aesthetic = "colour", palette = pal, ...)
  } else {
    colors <- pal(256)
    
    args <- list(...)
    
    if (!is.null(midpoint) && length(org_palettes[[palette]]) == 3) {
      lims <- if ("limits" %in% names(args)) args$limits else range(c(0, midpoint))
      
      vals <- c(lims[1], midpoint, lims[2])
      if (length(unique(vals)) < 3) {
        warning("Color scale values are not unique. Consider adjusting limits or midpoint.")
      }
      ggplot2::scale_color_gradientn(
        colours = colors,
        values = scales::rescale(vals, from = range(vals)),
        ...
      )
    } else {
      ggplot2::scale_color_gradientn(colours = colors, ...)
    }
  }
}

#' Fill scale using your organization's color palettes
#'
#' @inheritParams scale_color_org
#' @export
scale_fill_org <- function(palette = "main",
                               discrete = TRUE,
                               reverse = FALSE,
                               midpoint = NULL, ...) {
  pal <- org_pal(palette = palette, reverse = reverse)
  
  if (discrete) {
    ggplot2::discrete_scale(aesthetic = "fill", palette = pal, ...)
  } else {
    colors <- pal(256)
    
    args <- list(...)
    
    if (!is.null(midpoint) && length(org_palettes[[palette]]) == 3) {
      lims <- if ("limits" %in% names(args)) args$limits else range(c(0, midpoint))
      
      vals <- c(lims[1], midpoint, lims[2])
      if (length(unique(vals)) < 3) {
        warning("Color scale values are not unique. Consider adjusting limits or midpoint.")
      }
      ggplot2::scale_color_gradientn(
        colours = colors,
        values = scales::rescale(vals, from = range(vals)),
        ...
      )
    } else {
      ggplot2::scale_fill_gradientn(colours = colors, ...)
    }
  }
}

#' Display all your organization's palettes visually
#'
#' Shows contents of all named palettes as horizontal bars.
#'
#' @export
show_org_palettes <- function() {
  n <- length(org_palettes)
  max_cols <- max(sapply(org_palettes, length))
  palette_names <- names(org_palettes)
  
  # Set larger left margin (bottom, left, top, right)
  old_par <- graphics::par(mar = c(1, 6, 3, 1))
  
  graphics::plot(
    NULL, xlim = c(0, max_cols), ylim = c(0, n),
    type = "n", axes = FALSE, xlab = "", ylab = "",
    main = "Color Palettes"
  )
  
  for (i in seq_along(org_palettes)) {
    pal <- org_palettes[[i]]
    for (j in seq_along(pal)) {
      graphics::rect(j - 1, n - i, j, n - i + 0.8, col = pal[j], border = NA)
    }
    graphics::text(-0.2, n - i + 0.4, labels = palette_names[i], adj = 1, xpd = TRUE)
  }
  
  # Reset margin settings
  graphics::par(old_par)
}