feat: add brand color system, showtext fonts, and dark theme
R-CMD-check / R CMD check (push) Failing after 1m39s
R-CMD-check / R CMD check (push) Failing after 1m39s
- R/colors.R: civilytics_colors named vector (from --cv-* CSS vars on civilytics.com), three palettes (main/sequential/diverging), scale_color_civilytics() and scale_fill_civilytics() for ggplot2 - R/fonts.R: auto-loads Inter + Libre Franklin from Google Fonts via showtext in .onLoad(); exports civilytics_load_fonts() for manual retry - R/theme.R: wires ggplot2 4.0 ink/paper/accent params into theme_grey() base, adds title_family + strip_color params; new theme_civilytics_dark() using navy_dark background and warm off-white text - R/logo.R: make_logo_grob(variant) accepts "light"/"dark" to select the white logo for dark-background compositions - DESCRIPTION: adds showtext, sysfonts; pins ggplot2 >= 4.0.0 - tests/testthat/test_theme.R: full test suite for colors, palettes, scales, both theme variants, and logo grob variants
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
#' 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),
|
||||
...
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user