#' Civilytics brand colors #' #' A named character vector of all Civilytics brand colors, matching the CSS #' custom properties defined on www.civilytics.com (`--cv-*` variables). #' #' @format A named character vector of hex color codes. #' @export #' #' @examples #' civilytics_colors["accent"] #' civilytics_colors[c("ink", "paper")] civilytics_colors <- c( # Paper (backgrounds) — warm off-white scale paper = "#FAF7F2", paper_2 = "#F2EDE4", paper_3 = "#E6DFD1", # Rules (borders/dividers) rule = "#D6CEBD", rule_strong = "#B8AE97", # Ink (text / foreground) — dark-to-light navy-grey scale ink = "#0E1A2B", ink_2 = "#2B3A52", ink_3 = "#5A6A82", ink_4 = "#8C97AB", # Navy — primary brand blue navy = "#22406A", navy_dark = "#1A2E4A", # Accent — burnt orange accent = "#C25311", accent_dark = "#E07840", accent_50 = "#FDF1E4", accent_100 = "#FBE0C6", accent_200 = "#F8C8A3" ) # Internal named list of visualization palettes. # Each entry is a character vector of hex codes ordered for visual distinction. .cv_palettes <- list( # Qualitative: distinct hues for categorical data (up to 6 categories) main = unname(civilytics_colors[c( "navy_dark", "accent", "ink_3", "rule_strong", "navy", "accent_200" )]), # Sequential: light-to-dark navy for ordered/continuous data sequential = unname(civilytics_colors[c( "paper", "ink_4", "ink_3", "navy", "navy_dark" )]), # Diverging: orange <-> neutral <-> navy for data with a meaningful midpoint diverging = unname(civilytics_colors[c( "accent", "accent_200", "paper", "ink_4", "navy_dark" )]) ) # Internal palette generator: returns a function(n) -> character vector. .cv_pal_fun <- function(name) { pal <- .cv_palettes[[name]] if (is.null(pal)) { stop(sprintf( "'%s' is not a valid Civilytics palette. Choose from: %s", name, paste(names(.cv_palettes), collapse = ", ") ), call. = FALSE) } function(n) { if (n > length(pal)) grDevices::colorRampPalette(pal)(n) else pal[seq_len(n)] } } #' Civilytics brand color palette #' #' Returns a character vector of hex codes from a named Civilytics palette. #' Available palettes: `"main"` (qualitative, up to 6), `"sequential"` (light #' to dark navy), `"diverging"` (orange–neutral–navy). #' #' @param name Character. Palette name: `"main"`, `"sequential"`, or #' `"diverging"`. Defaults to `"main"`. #' @param n Integer or `NULL`. Number of colors to return. If `NULL`, returns #' all colors in the palette. If `n` exceeds the number of defined stops, #' colors are interpolated via [grDevices::colorRampPalette()]. #' #' @return A character vector of hex color codes. #' @export #' #' @examples #' civilytics_pal() # all 6 qualitative colors #' civilytics_pal("sequential", n = 3) #' civilytics_pal("diverging", n = 5) civilytics_pal <- function(name = "main", n = NULL) { f <- .cv_pal_fun(name) len <- length(.cv_palettes[[name]]) f(if (is.null(n)) len else n) } #' Civilytics color scale for ggplot2 #' #' Applies a Civilytics brand palette to the `colour` aesthetic. Use #' `discrete = TRUE` for categorical variables and `discrete = FALSE` for #' continuous gradients. #' #' @param palette Character. Palette name passed to [civilytics_pal()]. #' Defaults to `"main"`. #' @param discrete Logical. `TRUE` (default) for a discrete scale; `FALSE` for #' a continuous gradient via [ggplot2::scale_color_gradientn()]. #' @param ... Additional arguments passed to the underlying ggplot2 scale #' function. #' #' @return A ggplot2 scale object. #' @export #' #' @examples #' library(ggplot2) #' ggplot(mpg, aes(displ, hwy, colour = class)) + #' geom_point() + #' scale_color_civilytics() #' #' ggplot(mpg, aes(displ, hwy, colour = cty)) + #' geom_point() + #' scale_color_civilytics("sequential", discrete = FALSE) scale_color_civilytics <- function(palette = "main", discrete = TRUE, ...) { if (discrete) { ggplot2::discrete_scale("colour", palette = .cv_pal_fun(palette), ...) } else { ggplot2::scale_color_gradientn( colours = grDevices::colorRampPalette(.cv_palettes[[palette]])(256), ... ) } } #' Civilytics fill scale for ggplot2 #' #' Applies a Civilytics brand palette to the `fill` aesthetic. Use #' `discrete = TRUE` for categorical variables and `discrete = FALSE` for #' continuous gradients. #' #' @param palette Character. Palette name passed to [civilytics_pal()]. #' Defaults to `"main"`. #' @param discrete Logical. `TRUE` (default) for a discrete scale; `FALSE` for #' a continuous gradient via [ggplot2::scale_fill_gradientn()]. #' @param ... Additional arguments passed to the underlying ggplot2 scale #' function. #' #' @return A ggplot2 scale object. #' @export #' #' @examples #' library(ggplot2) #' ggplot(mpg, aes(class, fill = class)) + #' geom_bar() + #' scale_fill_civilytics() #' #' ggplot(faithfuld, aes(waiting, eruptions, fill = density)) + #' geom_tile() + #' scale_fill_civilytics("sequential", discrete = FALSE) scale_fill_civilytics <- function(palette = "main", discrete = TRUE, ...) { if (discrete) { ggplot2::discrete_scale("fill", palette = .cv_pal_fun(palette), ...) } else { ggplot2::scale_fill_gradientn( colours = grDevices::colorRampPalette(.cv_palettes[[palette]])(256), ... ) } }