feat: expand color system to 53 colors/10 palettes, add grid/slide themes, and README gallery
R-CMD-check / R CMD check (push) Successful in 3m41s

Replace the 16-color / 3-palette system with the full Civilytics design
system: 53 named colors (full navy, ember, violet ramps plus teal, plum,
moss, brass supporting hues) and 10 visualization palettes (qual,
qual_warm, qual_cool, seq_ember, seq_navy, seq_violet, seq_paper_ink,
div_navy_ember, div_violet_ember).

Enrich theme_civilytics() with grid and paper_bg parameters, add
theme_civilytics_slide() for presentations, and load all four brand fonts
(Inter, Libre Franklin, Source Serif 4, JetBrains Mono).

Add README.Rmd with rendered gallery showing all palettes, theme
variants, grid options, dark/slide modes, facets, and scale usage.
This commit is contained in:
2026-05-19 10:35:32 -06:00
parent 099fff1d1d
commit 515bae8685
29 changed files with 1209 additions and 273 deletions
+168 -58
View File
@@ -4,6 +4,11 @@
#' Civilytics brand color palette and typography. Requires ggplot2 >= 4.0.0
#' for the `ink`, `paper`, and `accent` base-theme parameters.
#'
#' Produces an editorial, Pew-style layout: visible x-axis line to ground
#' the data, light horizontal gridlines for reference, no panel border or
#' y-axis line. Plot title and caption are left-aligned to the full plot
#' region.
#'
#' Brand fonts (Inter for UI text, Libre Franklin for titles) are loaded
#' automatically via [showtext] when the package is attached. Call
#' [civilytics_load_fonts()] to reload them if needed.
@@ -25,9 +30,15 @@
#' @param paper Character. Hex code for background color. Defaults to
#' [civilytics_colors]`["paper"]` (`#FAF7F2`).
#' @param accent Character. Hex code for accent/highlight color. Defaults to
#' [civilytics_colors]`["accent"]` (`#C25311`).
#' [civilytics_colors]`["ember_600"]` (`#C25311`).
#' @param strip_color Character. Hex code for facet strip background. Defaults
#' to [civilytics_colors]`["paper_2"]` (`#F2EDE4`).
#' @param grid Character. Which major gridlines to draw: `"y"` (default,
#' horizontal only), `"x"` (vertical only), `"both"`, or `"none"`.
#' @param paper_bg Logical. If `TRUE` (default), fill the plot and panel
#' backgrounds with the warm `paper` color. Set to `FALSE` for a
#' transparent background (useful for slides or overlay on colored
#' surfaces).
#'
#' @return A complete ggplot2 [ggplot2::theme()] object.
#' @export
@@ -35,14 +46,22 @@
#' @examples
#' \dontrun{
#' library(ggplot2)
#'
#' # Default editorial theme
#' ggplot(mpg, aes(displ, hwy)) +
#' geom_point() +
#' theme_civilytics()
#'
#' # With both gridlines and brand colors
#' ggplot(mpg, aes(displ, hwy, colour = class)) +
#' geom_point() +
#' scale_color_civilytics() +
#' theme_civilytics()
#' theme_civilytics(grid = "both")
#'
#' # Transparent background for embedding
#' ggplot(mpg, aes(displ, hwy)) +
#' geom_point() +
#' theme_civilytics(paper_bg = FALSE)
#' }
theme_civilytics <- function(
font_size = 14,
@@ -54,11 +73,22 @@ theme_civilytics <- function(
rel_large = 16 / 14,
ink = unname(civilytics_colors["ink"]),
paper = unname(civilytics_colors["paper"]),
accent = unname(civilytics_colors["accent"]),
strip_color = unname(civilytics_colors["paper_2"])) {
accent = unname(civilytics_colors["ember_600"]),
strip_color = unname(civilytics_colors["paper_2"]),
grid = c("y", "x", "both", "none"),
paper_bg = TRUE) {
grid <- match.arg(grid)
half_line <- font_size / 2
small_size <- rel_small * font_size
rule_color <- unname(civilytics_colors["rule"])
ink_2 <- unname(civilytics_colors["ink_2"])
ink_3 <- unname(civilytics_colors["ink_3"])
bg_color <- if (isTRUE(paper_bg)) paper else NA
# Grid line elements
grid_line <- ggplot2::element_line(color = rule_color, linewidth = 0.35)
no_line <- ggplot2::element_blank()
ggplot2::theme_grey(
base_size = font_size,
@@ -92,16 +122,16 @@ theme_civilytics <- function(
margin = ggplot2::margin(),
debug = FALSE
),
# Axes
axis.line = ggplot2::element_line(
# -- Axes --
axis.line = ggplot2::element_blank(),
axis.line.x = ggplot2::element_line(
color = ink,
linewidth = line_size,
linewidth = 0.6,
lineend = "square"
),
axis.line.x = NULL,
axis.line.y = NULL,
axis.line.y = ggplot2::element_blank(),
axis.text = ggplot2::element_text(
color = ink,
color = ink_2,
size = small_size
),
axis.text.x = ggplot2::element_text(
@@ -121,66 +151,80 @@ theme_civilytics <- function(
hjust = 0
),
axis.ticks = ggplot2::element_line(
color = ink,
linewidth = line_size
color = ink_3,
linewidth = 0.4
),
axis.ticks.length = ggplot2::unit(half_line / 2, "pt"),
axis.ticks.length = ggplot2::unit(4, "pt"),
axis.title.x = ggplot2::element_text(
margin = ggplot2::margin(t = half_line / 2),
size = ggplot2::rel(rel_small),
color = ink_3,
margin = ggplot2::margin(t = 10),
vjust = 1
),
axis.title.x.top = ggplot2::element_text(
size = ggplot2::rel(rel_small),
color = ink_3,
margin = ggplot2::margin(b = half_line / 2),
vjust = 0
),
axis.title.y = ggplot2::element_text(
size = ggplot2::rel(rel_small),
color = ink_3,
angle = 90,
margin = ggplot2::margin(r = half_line / 2),
margin = ggplot2::margin(r = 10),
vjust = 1
),
axis.title.y.right = ggplot2::element_text(
size = ggplot2::rel(rel_small),
color = ink_3,
angle = -90,
margin = ggplot2::margin(l = half_line / 2),
vjust = 0
),
# Legend
# -- Legend --
legend.background = ggplot2::element_blank(),
legend.spacing = ggplot2::unit(font_size, "pt"),
legend.spacing.x = NULL,
legend.spacing.y = NULL,
legend.margin = ggplot2::margin(0, 0, 0, 0),
legend.margin = ggplot2::margin(0, 0, 4, 0),
legend.key = ggplot2::element_blank(),
legend.key.size = ggplot2::unit(1.1 * font_size, "pt"),
legend.key.size = ggplot2::unit(12, "pt"),
legend.key.height = NULL,
legend.key.width = NULL,
legend.text = ggplot2::element_text(size = ggplot2::rel(rel_small)),
legend.title = ggplot2::element_text(hjust = 0),
legend.position = "right",
legend.text = ggplot2::element_text(
size = ggplot2::rel(rel_small),
color = ink_2
),
legend.title = ggplot2::element_text(
hjust = 0,
face = "bold",
size = ggplot2::rel(rel_tiny),
color = ink_3
),
legend.position = "top",
legend.direction = NULL,
legend.justification = c("left", "center"),
legend.box = NULL,
legend.box.margin = ggplot2::margin(0, 0, 0, 0),
legend.box.background = ggplot2::element_blank(),
legend.box.spacing = ggplot2::unit(font_size, "pt"),
# Panel
panel.background = ggplot2::element_blank(),
# -- Panel --
panel.background = ggplot2::element_rect(fill = bg_color, color = NA),
panel.border = ggplot2::element_blank(),
panel.grid = ggplot2::element_blank(),
panel.grid.major = NULL,
panel.grid.minor = NULL,
panel.grid.major.x = NULL,
panel.grid.major.y = NULL,
panel.grid.minor.x = NULL,
panel.grid.minor.y = NULL,
panel.spacing = ggplot2::unit(half_line, "pt"),
panel.grid.minor = ggplot2::element_blank(),
panel.grid.major.x = if (grid %in% c("x", "both")) grid_line else no_line,
panel.grid.major.y = if (grid %in% c("y", "both")) grid_line else no_line,
panel.spacing = ggplot2::unit(16, "pt"),
panel.spacing.x = NULL,
panel.spacing.y = NULL,
panel.ontop = FALSE,
# Facet strips — use brand paper_2 tint (overridden for dark variant)
strip.background = ggplot2::element_rect(fill = strip_color),
# -- Facet strips --
strip.background = ggplot2::element_rect(fill = strip_color, color = NA),
strip.text = ggplot2::element_text(
family = title_family,
family = font_family,
face = "bold",
size = ggplot2::rel(rel_small),
color = ink,
margin = ggplot2::margin(
half_line / 2, half_line / 2,
half_line / 2, half_line / 2
@@ -193,49 +237,55 @@ theme_civilytics <- function(
strip.placement.y = NULL,
strip.switch.pad.grid = ggplot2::unit(half_line / 2, "pt"),
strip.switch.pad.wrap = ggplot2::unit(half_line / 2, "pt"),
# Plot-level
plot.background = ggplot2::element_rect(fill = paper, color = NA),
# -- Plot-level --
plot.background = ggplot2::element_rect(fill = bg_color, color = NA),
plot.title = ggplot2::element_text(
family = title_family,
face = "bold",
size = ggplot2::rel(rel_large),
hjust = 0,
vjust = 1,
margin = ggplot2::margin(b = half_line)
margin = ggplot2::margin(b = 4)
),
plot.title.position = "plot",
plot.subtitle = ggplot2::element_text(
size = ggplot2::rel(rel_small),
hjust = 0,
vjust = 1,
margin = ggplot2::margin(b = half_line)
size = ggplot2::rel(rel_small),
color = ink_2,
hjust = 0,
vjust = 1,
lineheight = 1.3,
margin = ggplot2::margin(b = 14)
),
plot.caption = ggplot2::element_text(
size = ggplot2::rel(rel_tiny),
color = ink_3,
hjust = 0,
vjust = 1,
lineheight = 1,
margin = ggplot2::margin(t = half_line)
lineheight = 1.3,
margin = ggplot2::margin(t = 14)
),
plot.caption.position = "plot",
plot.tag = ggplot2::element_text(
face = "bold",
color = accent,
size = ggplot2::rel(rel_tiny),
hjust = 0,
vjust = 0.7
),
plot.tag.position = c(0, 1),
plot.margin = ggplot2::margin(half_line, half_line, half_line, half_line),
plot.margin = ggplot2::margin(16, 18, 16, 16),
complete = TRUE
)
}
#' Dark variant of the Civilytics ggplot2 theme
#'
#' A convenience wrapper around [theme_civilytics()] with dark-background
#' defaults: navy (`#1A2E4A`) background, warm off-white (`#FAF7F2`) text, and
#' a lighter orange accent (`#E07840`) suitable for dark backgrounds. Pair with
#' `make_logo_grob(variant = "dark")` to use the white logo.
#' Convenience wrapper around [theme_civilytics()] with dark-background
#' defaults: navy (`#1A2E4A`) paper, warm off-white (`#FAF7F2`) ink, and
#' a lighter ember accent (`#E07840`). Facet strips use primary navy.
#'
#' All parameters from [theme_civilytics()] are accepted and override the dark
#' defaults.
#' Pair with `make_logo_grob(variant = "dark")` for the white logo.
#'
#' @inheritParams theme_civilytics
#'
@@ -245,10 +295,6 @@ theme_civilytics <- function(
#' @examples
#' \dontrun{
#' library(ggplot2)
#' ggplot(mpg, aes(displ, hwy)) +
#' geom_point() +
#' theme_civilytics_dark()
#'
#' ggplot(mpg, aes(displ, hwy, colour = class)) +
#' geom_point() +
#' scale_color_civilytics() +
@@ -263,9 +309,11 @@ theme_civilytics_dark <- function(
rel_tiny = 11 / 14,
rel_large = 16 / 14,
ink = unname(civilytics_colors["paper"]),
paper = unname(civilytics_colors["navy_dark"]),
accent = unname(civilytics_colors["accent_dark"]),
strip_color = unname(civilytics_colors["navy"])) {
paper = unname(civilytics_colors["navy_700"]),
accent = unname(civilytics_colors["ember_400"]),
strip_color = unname(civilytics_colors["navy_600"]),
grid = c("y", "x", "both", "none"),
paper_bg = TRUE) {
theme_civilytics(
font_size = font_size,
@@ -278,6 +326,68 @@ theme_civilytics_dark <- function(
ink = ink,
paper = paper,
accent = accent,
strip_color = strip_color
strip_color = strip_color,
grid = grid,
paper_bg = paper_bg
)
}
#' Slide-friendly Civilytics ggplot2 theme
#'
#' Variant of [theme_civilytics()] sized for Reveal.js slides or PowerPoint
#' exports: larger base font (18pt), transparent background, wider margins,
#' and a heavier x-axis line. Gridlines default to horizontal only.
#'
#' @inheritParams theme_civilytics
#'
#' @return A complete ggplot2 [ggplot2::theme()] object.
#' @export
#'
#' @examples
#' \dontrun{
#' library(ggplot2)
#' ggplot(mpg, aes(displ, hwy, colour = class)) +
#' geom_point() +
#' scale_color_civilytics() +
#' theme_civilytics_slide()
#' }
theme_civilytics_slide <- function(
font_size = 18,
font_family = CV_FONT_SANS,
title_family = CV_FONT_DISPLAY,
line_size = 0.5,
rel_small = 12 / 14,
rel_tiny = 11 / 14,
rel_large = 16 / 14,
ink = unname(civilytics_colors["ink"]),
paper = unname(civilytics_colors["paper"]),
accent = unname(civilytics_colors["ember_600"]),
strip_color = unname(civilytics_colors["paper_2"]),
grid = c("y", "x", "both", "none"),
paper_bg = FALSE) {
theme_civilytics(
font_size = font_size,
font_family = font_family,
title_family = title_family,
line_size = line_size,
rel_small = rel_small,
rel_tiny = rel_tiny,
rel_large = rel_large,
ink = ink,
paper = paper,
accent = accent,
strip_color = strip_color,
grid = grid,
paper_bg = paper_bg
) +
ggplot2::theme(
axis.line.x = ggplot2::element_line(
color = ink,
linewidth = 0.8,
lineend = "square"
),
plot.margin = ggplot2::margin(24, 24, 24, 24)
)
}