ggplot2 with Cyberpunk 2077

ggplot2
R
Author

Matthew Harris

Published

March 13, 2022

Abstract
Creating a color palette that can be used for ggplot2 color and fill scales.

Cyberpunk 2077 is an interesting game with a unique color palette. How would visualizations looks if they could share the same color aesthetic? One way to find out is to create a custom palette that can be applied to any ggplot viz.

Packages

Code
library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)
library(ggridges)

Steps

Cyberpunk colors

Code
cp_2077_colors <- c(
  "electric_blue" = "#37EBF3",
  "b_purple" = "#9370DB",
  "blood_red" = "#710000",
  "keppel" = "#1AC5B0",
  "lemon" = "#FDF500",
  "f_pink" = "#E455AE",
  "s_pink" = "#CB1DCD",
  "pale_silver" = "#D1C5C0",
  "r_black" = "#272932")

show_col(cp_2077_colors)

Access the color hex by name

Code
cp_2077_cols <- function(...) {
  cols <- c(...)
  if (is.null(cols)) {
    return(cp_2077_colors) 
  }
  cp_2077_colors[cols]
}

cp_2077_cols("blood_red", "electric_blue")
    blood_red electric_blue 
    "#710000"     "#37EBF3" 

Custom palettes from the Cyerpunk colors

Code
cp_2077_palettes <- list(
  "main" = cp_2077_cols("electric_blue", "b_purple",  "f_pink", "keppel", "lemon", "blood_red", "s_pink"),
  "battle" = cp_2077_cols("blood_red", "f_pink", "b_purple"),
  "nc_neon" = cp_2077_cols("electric_blue", "lemon", "s_pink"),
  "nc_sunset" = cp_2077_cols("blood_red", "lemon"),
  "div1" = cp_2077_cols("f_pink", "electric_blue"),
  "nc_dark" = cp_2077_cols("pale_silver", "r_black"))

cp_2077_palettes[["nc_neon"]]
electric_blue         lemon        s_pink 
    "#37EBF3"     "#FDF500"     "#CB1DCD" 

Palette function

This function will return a certain number of colors from a given palette. Palette functions are needed to create ggplot color and fill scales.

Code
cp_2077_pal <- function(palette = "main", reverse = FALSE) {
  stopifnot(palette %in% names(cp_2077_palettes))
  pal <- cp_2077_palettes[[palette]]
  
  function(n) {
    if (n > length(pal)) {
      warning(paste0("Palette only has ", length(pal), " colors."))
    }
    color_lst <- unname(pal[1:n])
    if (reverse) {
      return (rev(color_lst))
    }
    color_lst
  }
}

show_col(cp_2077_pal()(3))

{ggplot2} scale functions

Four different functions will be needed to cover the main ggplot scales used by palettes. The color and fill aesthetics are used to apply color to a geom’s line/stroke and area respectively. Each aesthetic can also have a discrete number of colors applied or a continuous scale.

Color Scales

Discrete

Code
scale_color_cp_2077_d <- function(palette = "main", reverse = FALSE, ...) {
  pal <- cp_2077_pal(palette, reverse = reverse)
  
  discrete_scale(aesthetics = "color", 
                 scale_name = paste0("cp_2077_", palette), 
                 palette = pal, ...)
}

Continuous

Code
scale_color_cp_2077_c <- function(palette = "nc_sunset", reverse = FALSE, ...) {
  pal <- cp_2077_palettes[[palette]][1:2]
  
  if (reverse) {
    pal <- rev(pal) 
  }
  
  cr_pal <- colorRampPalette(pal, ...)
  scale_color_gradientn(colors = cr_pal(256), ...)
}

Fill Scales

Discrete

Code
scale_fill_cp_2077_d <- function(palette = "main", reverse = FALSE, ...) {
  pal <- cp_2077_pal(palette, reverse = reverse)
  
  discrete_scale(aesthetics = "fill", 
                 scale_name = paste0("cp_2077_", palette), 
                 palette = pal, ...)
}

Continuous

Code
scale_fill_cp_2077_c <- function(palette = "nc_sunset", reverse = FALSE, ...) {
  pal <- cp_2077_palettes[[palette]][1:2]
  
  if(reverse) {
    pal <- rev(pal) 
  }
  
  cr_pal <- colorRampPalette(pal, ...)
  scale_fill_gradientn(colors = cr_pal(256), ...)
}

Palette demos

Code
# Scale color discrete
mpg %>% 
  filter(class %in% unique(mpg$class)[1:3]) %>%
  ggplot(aes(displ, hwy, col = class)) + 
  geom_jitter(size = 5, alpha = 0.7) +
  scale_color_cp_2077_d() +
  theme_minimal()

Code
# Scale color continuous
economics %>% 
  ggplot(aes(date, uempmed, col = psavert)) + 
  geom_jitter(size = 5, alpha = 0.5) +
  scale_color_cp_2077_c(reverse = TRUE) +
  theme_minimal()

Code
# Scale fill discrete
mpg %>%
  filter(class %in% unique(mpg$class)[1:5]) %>%
  ggplot(aes(hwy, class, fill = class)) + 
  geom_density_ridges() +
  theme_minimal() +
  scale_fill_cp_2077_d()
Picking joint bandwidth of 0.824

Code
# Scale fill continuous
economics %>%
  group_by(d_year = lubridate::year(date)) %>% 
  summarize(avg_psavert = mean(psavert, na.rm = TRUE),
            avg_unemploy = mean(unemploy, na.rm = TRUE)) %>%
  tail(15) %>%
  ggplot(aes(d_year, avg_psavert, fill = avg_unemploy)) + 
  geom_col(col = "black") +
  theme_minimal() +
  scale_fill_cp_2077_c("div1", reverse = TRUE)

All code

Here’s all the code in one chunk to create the palette. Enjoy!!!

Code
xfun::pkg_attach("dplyr", "tidyr",
                 "ggplot2", "scales", "ggridges")

# Named vector of colors
cp_2077_colors <- c(
  "electric_blue" = "#37EBF3",
  "b_purple" = "#9370DB",
  "blood_red" = "#710000",
  "keppel" = "#1AC5B0",
  "lemon" = "#FDF500",
  "f_pink" = "#E455AE",
  "s_pink" = "#CB1DCD",
  "pale_silver" = "#D1C5C0",
  "r_black" = "#272932")

# Function to access colors by name
cp_2077_cols <- function(...) {
  cols <- c(...)
  if (is.null(cols)) {
    return(cp_2077_colors) 
  }
  cp_2077_colors[cols]
}

# Color palettes
cp_2077_palettes <- list(
  "main" = cp_2077_cols("electric_blue", "b_purple",  "f_pink", "keppel", "lemon", "blood_red", "s_pink"),
  "battle" = cp_2077_cols("blood_red", "f_pink", "b_purple"),
  "nc_neon" = cp_2077_cols("electric_blue", "lemon", "s_pink"),
  "nc_sunset" = cp_2077_cols("blood_red", "lemon"),
  "div1" = cp_2077_cols("f_pink", "electric_blue"),
  "nc_dark" = cp_2077_cols("pale_silver", "r_black"))

# Palette function
cp_2077_pal <- function(palette = "main", reverse = FALSE) {
  stopifnot(palette %in% names(cp_2077_palettes))
  pal <- cp_2077_palettes[[palette]]
  
  function(n) {
    if (n > length(pal)) {
      warning(paste0("Palette only has ", length(pal), " colors."))
    }
    color_lst <- unname(pal[1:n])
    if (reverse) {
      return (rev(color_lst))
    }
    color_lst
  }
}

# Discrete Color Scale
scale_color_cp_2077_d <- function(palette = "main", reverse = FALSE, ...) {
  pal <- cp_2077_pal(palette, reverse = reverse)
  
  discrete_scale(aesthetics = "color", 
                 scale_name = paste0("cp_2077_", palette), 
                 palette = pal, ...)
}

# Continuous Color Scale
scale_color_cp_2077_c <- function(palette = "nc_sunset", reverse = FALSE, ...) {
  pal <- cp_2077_palettes[[palette]][1:2]
  
  if (reverse) {
    pal <- rev(pal) 
  }
  cr_pal <- colorRampPalette(pal, ...)
  scale_color_gradientn(colors = cr_pal(256), ...)
}

# Discrete Fill Scale
scale_fill_cp_2077_d <- function(palette = "main", reverse = FALSE, ...) {
  pal <- cp_2077_pal(palette, reverse = reverse)
  
  discrete_scale(aesthetics = "fill", 
                 scale_name = paste0("cp_2077_", palette), 
                 palette = pal, ...)
}

# Continuous Fill Scale
scale_fill_cp_2077_c <- function(palette = "nc_sunset", reverse = FALSE, ...) {
  pal <- cp_2077_palettes[[palette]][1:2]
  
  if(reverse) {
    pal <- rev(pal) 
  }
  cr_pal <- colorRampPalette(pal, ...)
  scale_fill_gradientn(colors = cr_pal(256), ...)
}