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
+6 -4
View File
@@ -1,13 +1,15 @@
Package: civilytics
Type: Package
Title: Utilities Functions for Civilytics
Title: Brand Themes, Color Palettes, and Utility Functions for Civilytics
Version: 0.2.0
Authors@R:
person("Jared", "E. Knowles", email = "jared@civilytics.com",
role = c("aut", "cre"))
Description: House R functions for Civilytics Consulting LLC. This package
implements a variety of useful functions for creating and branding analyses
produced by Civilytics Consulting LLC.
Description: Provides a complete ggplot2 brand theme system for Civilytics
Consulting LLC, including editorial light, dark, and slide-optimized themes;
10 curated color palettes for qualitative, sequential, and diverging data;
logo composition utilities; and data-wrangling helpers for public-sector
analysis.
License: LGPL (>= 3)
URL: https://gitea.civilytics.org/Civilytics/civilyticsR
BugReports: https://gitea.civilytics.org/Civilytics/civilyticsR/issues
+3
View File
@@ -7,6 +7,8 @@ export(civilytics_colors)
export(civilytics_load_fonts)
export(civilytics_logo)
export(civilytics_pal)
export(civilytics_palette)
export(civilytics_palettes)
export(clopper_pearson)
export(countCleanr)
export(countDots)
@@ -42,6 +44,7 @@ export(simpleCap)
export(star_subs)
export(theme_civilytics)
export(theme_civilytics_dark)
export(theme_civilytics_slide)
export(trim_max)
export(waldInterval)
export(z_gap_test)
+33 -14
View File
@@ -1,14 +1,33 @@
#' @keywords internal
#' @importFrom stats qbeta
#' @importFrom stats qnorm
"_PACKAGE"
## usethis namespace: start
## usethis namespace: end
NULL
# state.abb and state.name are lazy data from the datasets package (base R).
# They cannot be imported via @importFrom — suppress the R CMD check NOTE here.
utils::globalVariables(c("state.abb", "state.name"))
#' @description
#' Provides a complete ggplot2 brand theme system for Civilytics Consulting,
#' including editorial light, dark, and slide-optimized themes; 10 curated
#' color palettes; logo composition; and data-wrangling helpers for
#' public-sector analysis.
#'
#' @section Themes:
#' \itemize{
#' \item [theme_civilytics()] -- editorial theme with warm paper background
#' \item [theme_civilytics_dark()] -- navy background variant
#' \item [theme_civilytics_slide()] -- transparent background, larger text
#' }
#'
#' @section Color palettes:
#' \itemize{
#' \item [civilytics_colors] -- 53 named brand colors
#' \item [civilytics_palettes] -- 10 visualization palettes
#' \item [civilytics_palette()] -- extract colors by palette name
#' \item [scale_color_civilytics()] / [scale_fill_civilytics()] -- ggplot2 scales
#' }
#'
#' @keywords internal
#' @importFrom stats qbeta
#' @importFrom stats qnorm
"_PACKAGE"
## usethis namespace: start
## usethis namespace: end
NULL
# state.abb and state.name are lazy data from the datasets package (base R).
# They cannot be imported via @importFrom -- suppress the R CMD check NOTE here.
utils::globalVariables(c("state.abb", "state.name"))
+216 -83
View File
@@ -1,106 +1,229 @@
#' Civilytics brand colors
#'
#' A named character vector of all Civilytics brand colors, matching the CSS
#' custom properties defined on www.civilytics.com (`--cv-*` variables).
#' custom properties in the Civilytics design system (`--cv-*` variables).
#' Includes full ramps for navy, ember, and violet, plus supporting hues
#' (teal, plum, moss, brass) and semantic status colors.
#'
#' @format A named character vector of hex color codes.
#' @export
#'
#' @examples
#' civilytics_colors["accent"]
#' civilytics_colors[c("ink", "paper")]
#' civilytics_colors["ink"]
#' civilytics_colors[c("navy_600", "ember_600", "teal_600")]
civilytics_colors <- c(
# Paper (backgrounds) — warm off-white scale
# Neutrals — warm paper -> civic ink
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"
# Navy — civic authority (primary brand blue)
navy_900 = "#0E1A2B", navy_800 = "#132339", navy_700 = "#1A2E4A",
navy_600 = "#22406A", navy_500 = "#2E5590", navy_400 = "#4A74B0",
navy_300 = "#7A9BCA", navy_200 = "#B3C6E0", navy_100 = "#DDE6F2",
navy_50 = "#EEF3FA",
# Ember — warm orange accent
ember_900 = "#451A00", ember_800 = "#6B2B00", ember_700 = "#923D00",
ember_600 = "#C25311", ember_500 = "#DB6C25", ember_400 = "#EA8A49",
ember_300 = "#F2A976", ember_200 = "#F8C8A3", ember_100 = "#FBE0C6",
ember_50 = "#FDF1E4",
# Violet — extended supporting
violet_900 = "#19102E", violet_800 = "#2A1B4D", violet_700 = "#3D2A6B",
violet_600 = "#5C3A8A", violet_500 = "#7556A8", violet_400 = "#9A7EC2",
violet_300 = "#BDA8DA", violet_200 = "#DCD0EC", violet_100 = "#F1ECF8",
# Supporting — muted editorial hues for data viz
teal_600 = "#1F6F70", teal_300 = "#7CB3B3", teal_100 = "#D3E6E6",
plum_600 = "#6B3A5E", plum_300 = "#B392A7", plum_100 = "#E7DAE1",
moss_600 = "#4A6B2F", moss_300 = "#9CB47D", moss_100 = "#DEE8CF",
brass_600 = "#B8751C", brass_100 = "#F9E6C8",
# Semantic status colors
success = "#4A6B2F",
warning = "#9A5F18",
danger = "#A6271D",
info = "#2E5590"
)
# 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"
)])
#' Civilytics visualization palettes
#'
#' Named list of curated color palettes for data visualization. Qualitative
#' palettes use distinct hues for categorical data; sequential palettes ramp
#' through a single hue for ordered data; diverging palettes fan out from a
#' neutral midpoint.
#'
#' @format A named list of character vectors of hex color codes.
#'
#' @section Qualitative (categorical data):
#' \describe{
#' \item{`qual`}{7 distinct hues: navy, ember, plum, violet, red, teal, ink-3}
#' \item{`qual_warm`}{Warm-leaning: ember, red, magenta, plum, violet}
#' \item{`qual_cool`}{Cool-leaning: navy, violet, plum, teal}
#' }
#'
#' @section Sequential (ordered/continuous data):
#' \describe{
#' \item{`seq_ember`}{Light to dark ember (9 stops)}
#' \item{`seq_navy`}{Light to dark navy (9 stops)}
#' \item{`seq_violet`}{Light to dark violet (9 stops)}
#' \item{`seq_paper_ink`}{Paper through ember/plum to ink (8 stops, good for heatmaps)}
#' }
#'
#' @section Diverging (data with a meaningful midpoint):
#' \describe{
#' \item{`div_navy_ember`}{Navy <-> paper <-> ember (9 stops)}
#' \item{`div_violet_ember`}{Violet <-> paper <-> ember (9 stops)}
#' }
#'
#' @export
#'
#' @examples
#' names(civilytics_palettes)
#' civilytics_palettes[["qual"]]
civilytics_palettes <- list(
# -- Qualitative --
qual = c(
"#22406A",
"#C25311",
"#6B3A5E",
"#3D2A6B",
"#A6271D",
"#1F6F70",
"#5A6A82"
),
qual_warm = c(
"#C25311",
"#A6271D",
"#923D00",
"#B8366B",
"#6B3A5E",
"#DB6C25",
"#3D2A6B"
),
qual_cool = c(
"#22406A",
"#3D2A6B",
"#6B3A5E",
"#1F6F70",
"#2E5590",
"#7556A8",
"#5A6A82"
),
# -- Sequential --
seq_ember = c(
"#FDF1E4", "#FBE0C6", "#F8C8A3", "#F2A976",
"#EA8A49", "#DB6C25", "#C25311", "#923D00", "#451A00"
),
seq_navy = c(
"#EEF3FA", "#DDE6F2", "#B3C6E0", "#7A9BCA",
"#4A74B0", "#2E5590", "#22406A", "#1A2E4A", "#0E1A2B"
),
seq_violet = c(
"#F1ECF8", "#DCD0EC", "#BDA8DA", "#9A7EC2",
"#7556A8", "#5C3A8A", "#3D2A6B", "#2A1B4D", "#19102E"
),
seq_paper_ink = c(
"#FAF7F2", "#F8C8A3", "#EA8A49", "#C25311",
"#A6271D", "#6B3A5E", "#3D2A6B", "#0E1A2B"
),
# -- Diverging --
div_navy_ember = c(
"#0E1A2B", "#22406A", "#4A74B0", "#B3C6E0",
"#FAF7F2",
"#F8C8A3", "#EA8A49", "#C25311", "#6B2B00"
),
div_violet_ember = c(
"#19102E", "#3D2A6B", "#7556A8", "#BDA8DA",
"#FAF7F2",
"#F8C8A3", "#EA8A49", "#C25311", "#6B2B00"
)
)
# 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
#' Get colors from a Civilytics 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).
#' For qualitative palettes, colors beyond the palette length recycle with a
#' warning. For sequential and diverging palettes, colors are interpolated
#' via [grDevices::colorRampPalette()].
#'
#' @param name Character. Palette name: `"main"`, `"sequential"`, or
#' `"diverging"`. Defaults to `"main"`.
#' @param name Character. Palette name. See `names(civilytics_palettes)`.
#' @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()].
#' all defined stops.
#' @param reverse Logical. Reverse the palette order. Default `FALSE`.
#'
#' @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_palette() # all 7 qualitative colors
#' civilytics_palette("seq_navy", n = 5) # 5-stop navy ramp
#' civilytics_palette("div_navy_ember", n = 11, reverse = TRUE)
civilytics_palette <- function(name = "qual", n = NULL, reverse = FALSE) {
pal <- civilytics_palettes[[name]]
if (is.null(pal)) {
stop(
"'", name, "' is not a valid palette. Choose from: ",
paste(names(civilytics_palettes), collapse = ", "),
call. = FALSE
)
}
if (reverse) pal <- rev(pal)
if (is.null(n)) return(pal)
if (startsWith(name, "qual")) {
if (n > length(pal)) {
warning(
"Requested ", n, " colors from '", name, "' palette (max ",
length(pal), "); recycling.",
call. = FALSE
)
pal <- rep_len(pal, n)
}
return(pal[seq_len(n)])
}
grDevices::colorRampPalette(pal)(n)
}
#' Civilytics palette function (closure)
#'
#' Returns a closure `function(n)` suitable for passing to
#' [ggplot2::discrete_scale()] or similar scale constructors.
#'
#' @param name Character. Palette name. See `names(civilytics_palettes)`.
#' @param reverse Logical. Reverse the palette order. Default `FALSE`.
#'
#' @return A function that takes integer `n` and returns `n` hex color codes.
#' @export
#'
#' @examples
#' pal_fn <- civilytics_pal("qual")
#' pal_fn(4)
civilytics_pal <- function(name = "qual", reverse = FALSE) {
function(n) civilytics_palette(name, n = n, reverse = reverse)
}
#' 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.
#' Applies a Civilytics brand palette to the `colour` aesthetic.
#'
#' @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.
#' @param palette Character. Palette name. Defaults to `"qual"`.
#' @param discrete Logical. `TRUE` (default) for categorical data; `FALSE`
#' for a continuous gradient via [ggplot2::scale_color_gradientn()].
#' @param reverse Logical. Reverse the palette order. Default `FALSE`.
#' @param ... Additional arguments passed to the ggplot2 scale function.
#'
#' @return A ggplot2 scale object.
#' @export
@@ -113,30 +236,34 @@ civilytics_pal <- function(name = "main", n = NULL) {
#'
#' ggplot(mpg, aes(displ, hwy, colour = cty)) +
#' geom_point() +
#' scale_color_civilytics("sequential", discrete = FALSE)
scale_color_civilytics <- function(palette = "main", discrete = TRUE, ...) {
#' scale_color_civilytics("seq_navy", discrete = FALSE)
scale_color_civilytics <- function(palette = "qual", discrete = TRUE,
reverse = FALSE, ...) {
if (discrete) {
ggplot2::discrete_scale("colour", palette = .cv_pal_fun(palette), ...)
ggplot2::discrete_scale(
"colour",
palette = civilytics_pal(palette, reverse = reverse),
...
)
} else {
pal <- civilytics_palette(palette, reverse = reverse)
ggplot2::scale_color_gradientn(
colours = grDevices::colorRampPalette(.cv_palettes[[palette]])(256),
colours = grDevices::colorRampPalette(pal)(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.
#' Applies a Civilytics brand palette to the `fill` aesthetic.
#'
#' @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.
#' @param palette Character. Palette name. Defaults to `"qual"`.
#' @param discrete Logical. `TRUE` (default) for categorical data; `FALSE`
#' for a continuous gradient via [ggplot2::scale_fill_gradientn()].
#' @param reverse Logical. Reverse the palette order. Default `FALSE`.
#' @param ... Additional arguments passed to the ggplot2 scale function.
#'
#' @return A ggplot2 scale object.
#' @export
@@ -149,13 +276,19 @@ scale_color_civilytics <- function(palette = "main", discrete = TRUE, ...) {
#'
#' ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
#' geom_tile() +
#' scale_fill_civilytics("sequential", discrete = FALSE)
scale_fill_civilytics <- function(palette = "main", discrete = TRUE, ...) {
#' scale_fill_civilytics("seq_ember", discrete = FALSE)
scale_fill_civilytics <- function(palette = "qual", discrete = TRUE,
reverse = FALSE, ...) {
if (discrete) {
ggplot2::discrete_scale("fill", palette = .cv_pal_fun(palette), ...)
ggplot2::discrete_scale(
"fill",
palette = civilytics_pal(palette, reverse = reverse),
...
)
} else {
pal <- civilytics_palette(palette, reverse = reverse)
ggplot2::scale_fill_gradientn(
colours = grDevices::colorRampPalette(.cv_palettes[[palette]])(256),
colours = grDevices::colorRampPalette(pal)(256),
...
)
}
+7 -3
View File
@@ -2,7 +2,8 @@
# These match the --cv-font-* CSS custom properties on www.civilytics.com.
CV_FONT_DISPLAY <- "Libre Franklin" # headings / display text
CV_FONT_SANS <- "Inter" # axis text, legends, UI elements
CV_FONT_SERIF <- "Source Serif 4" # body prose (not used in default theme)
CV_FONT_SERIF <- "Source Serif 4" # body prose / editorial long-form
CV_FONT_MONO <- "JetBrains Mono" # code, data tables, numeric callouts
# Internal flag so civilytics_load_fonts() is idempotent within a session.
.cv_fonts_loaded <- FALSE
@@ -25,6 +26,8 @@ CV_FONT_SERIF <- "Source Serif 4" # body prose (not used in default theme)
civilytics_load_fonts <- function() {
sysfonts::font_add_google("Inter", family = "Inter")
sysfonts::font_add_google("Libre Franklin", family = "Libre Franklin")
sysfonts::font_add_google("Source Serif 4", family = "Source Serif 4")
sysfonts::font_add_google("JetBrains Mono", family = "JetBrains Mono")
showtext::showtext_auto()
.cv_fonts_loaded <<- TRUE
invisible(NULL)
@@ -43,8 +46,9 @@ civilytics_load_fonts <- function() {
.onAttach <- function(libname, pkgname) {
if (isTRUE(getOption(".civilytics_fonts_failed"))) {
packageStartupMessage(
"[civilytics] Brand fonts (Inter, Libre Franklin) could not be loaded ",
"from Google Fonts. Charts will fall back to system fonts. ",
"[civilytics] Brand fonts (Inter, Libre Franklin, Source Serif 4, ",
"JetBrains Mono) could not be loaded from Google Fonts. ",
"Charts will fall back to system fonts. ",
"Call civilytics_load_fonts() once you have an internet connection."
)
}
+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)
)
}
+213
View File
@@ -0,0 +1,213 @@
---
output: github_document
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
fig.retina = 2,
dpi = 150,
out.width = "100%"
)
```
# civilytics
Brand themes, color palettes, and utility functions for
[Civilytics Consulting](https://www.civilytics.com). The package provides a
complete ggplot2 theme system drawn from the Civilytics design system ---
warm paper backgrounds, civic-navy ink, and editorial typography --- along
with 10 curated color palettes, logo composition helpers, and data-wrangling
utilities for public-sector analysis.
## Installation
Install from the Civilytics Gitea server:
```r
# install.packages("remotes")
remotes::install_gitea(
"Civilytics/civilyticsR",
gitea_url = "https://gitea.civilytics.org"
)
```
## Quick start
```{r quickstart, fig.height = 4, fig.width = 7, message = FALSE}
library(civilytics)
library(ggplot2)
ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point(size = 2.5) +
scale_color_civilytics() +
labs(
title = "Fuel economy by engine displacement",
subtitle = "Highway MPG vs. engine size for 234 vehicles",
caption = "Source: EPA fuel economy data (ggplot2::mpg)",
x = "Engine displacement (litres)",
y = "Highway MPG"
) +
theme_civilytics()
```
## Color palettes
The package ships 53 named brand colors in `civilytics_colors` and 10
curated palettes in `civilytics_palettes`. Use `civilytics_palette()` to
retrieve colors by name, or pass palettes directly to the ggplot2 scales.
### Palette gallery
```{r palette-gallery, echo = FALSE, fig.height = 9, fig.width = 7}
show_palette <- function(name, colors) {
n <- length(colors)
df <- data.frame(
x = seq_len(n),
fill = factor(seq_len(n), levels = seq_len(n))
)
ggplot(df, aes(x, y = 1, fill = fill)) +
geom_tile(width = 0.9, height = 0.9, show.legend = FALSE) +
scale_fill_manual(values = colors) +
scale_x_continuous(expand = expansion(add = 0.5)) +
labs(title = name) +
theme_void() +
theme(
plot.title = element_text(
family = "Libre Franklin", face = "bold", size = 11,
hjust = 0, margin = margin(b = 2)
),
plot.margin = margin(4, 4, 4, 4)
)
}
plots <- mapply(
show_palette,
names(civilytics_palettes),
civilytics_palettes,
SIMPLIFY = FALSE
)
gridExtra::grid.arrange(grobs = plots, ncol = 1)
```
### Using palettes
```{r palette-usage, fig.height = 3.5, fig.width = 7}
# Discrete fill with the qualitative palette
ggplot(mpg, aes(class, fill = class)) +
geom_bar(show.legend = FALSE) +
scale_fill_civilytics() +
labs(title = "Vehicle counts by class", x = NULL, y = NULL) +
theme_civilytics(grid = "y")
# Continuous fill with a sequential palette
ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
geom_tile() +
scale_fill_civilytics("seq_ember", discrete = FALSE) +
labs(title = "Old Faithful eruption density") +
theme_civilytics(grid = "none")
```
## Themes
Three theme variants cover the most common output contexts. All share the
same typographic structure and accept `grid` and `paper_bg` parameters.
### Editorial (default)
The default theme uses a warm paper background with horizontal gridlines ---
an editorial, Pew-style layout.
```{r theme-editorial, fig.height = 4, fig.width = 7}
base_plot <- ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(colour = factor(cyl)), size = 2) +
scale_color_civilytics() +
labs(
title = "Engine size vs. highway fuel economy",
subtitle = "Colored by number of cylinders",
caption = "Source: ggplot2::mpg",
colour = "Cylinders",
x = "Displacement (L)", y = "Highway MPG"
)
base_plot + theme_civilytics()
```
### Grid options
The `grid` parameter controls which major gridlines are drawn.
```{r theme-grids, echo = FALSE, fig.height = 6.5, fig.width = 7}
grid_opts <- c("y", "x", "both", "none")
grid_plots <- lapply(grid_opts, function(g) {
base_plot +
theme_civilytics(grid = g) +
labs(title = paste0("grid = \"", g, "\""), subtitle = NULL, caption = NULL)
})
gridExtra::grid.arrange(grobs = grid_plots, ncol = 2)
```
### Dark
Dark navy background with light text, suitable for presentations or
dashboards on dark surfaces.
```{r theme-dark, fig.height = 4, fig.width = 7}
base_plot + theme_civilytics_dark()
```
### Slide
Transparent background and larger base font (18 pt), sized for Reveal.js
slides or PowerPoint exports.
```{r theme-slide, fig.height = 4, fig.width = 7}
base_plot + theme_civilytics_slide()
```
### Facets
Facet strips use the `paper_2` tint, with the title font.
```{r theme-facets, fig.height = 5, fig.width = 7}
ggplot(mpg, aes(displ, hwy)) +
geom_point(colour = civilytics_colors["navy_600"], size = 1.5) +
facet_wrap(~class, ncol = 4) +
labs(
title = "Highway MPG by vehicle class",
x = "Displacement (L)", y = "Highway MPG"
) +
theme_civilytics(grid = "y")
```
## Logo utilities
Add the Civilytics logo to any ggplot using the pipe-friendly
`civilytics_logo()` or the lower-level `add_logo()` / `make_logo_grob()`.
```r
library(civilytics)
library(ggplot2)
(ggplot(mpg, aes(displ, hwy)) +
geom_point() +
theme_civilytics()) |>
civilytics_logo()
```
## Other utilities
The package also includes helpers for public-sector data analysis:
| Function | Purpose |
|:---------|:--------|
| `pretty_count()` / `pretty_per()` | Format numbers and percentages |
| `grade_level_to_num()` | Convert grade labels (KG, 01--12) to numeric |
| `race_short_names()` | Standardize NCES race/ethnicity categories |
| `get_fips()` / `get_stabbr()` | State FIPS code lookups |
| `clopper_pearson()` / `agresti_coull_interval()` | Proportion confidence intervals |
| `match_test()` / `trunc_match()` | Fuzzy join diagnostics |
| `perturb_count()` / `random_round()` | Privacy-preserving data perturbation |
+181 -25
View File
@@ -1,25 +1,181 @@
# civilytics
<!-- badges: start -->
<!-- badges: end -->
The goal of civilytics is to ...
## Installation
You can install the released version of civilytics from [CRAN](https://CRAN.R-project.org) with:
``` r
install.packages("civilytics")
```
## Example
This is a basic example which shows you how to solve a common problem:
``` r
library(civilytics)
## basic example code
```
# civilytics
Brand themes, color palettes, and utility functions for [Civilytics
Consulting](https://www.civilytics.com). The package provides a complete
ggplot2 theme system drawn from the Civilytics design system — warm
paper backgrounds, civic-navy ink, and editorial typography — along with
10 curated color palettes, logo composition helpers, and data-wrangling
utilities for public-sector analysis.
## Installation
Install from the Civilytics Gitea server:
``` r
# install.packages("remotes")
remotes::install_gitea(
"Civilytics/civilyticsR",
gitea_url = "https://gitea.civilytics.org"
)
```
## Quick start
``` r
library(civilytics)
library(ggplot2)
ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point(size = 2.5) +
scale_color_civilytics() +
labs(
title = "Fuel economy by engine displacement",
subtitle = "Highway MPG vs. engine size for 234 vehicles",
caption = "Source: EPA fuel economy data (ggplot2::mpg)",
x = "Engine displacement (litres)",
y = "Highway MPG"
) +
theme_civilytics()
```
<img src="man/figures/README-quickstart-1.png" alt="" width="100%" />
## Color palettes
The package ships 53 named brand colors in `civilytics_colors` and 10
curated palettes in `civilytics_palettes`. Use `civilytics_palette()` to
retrieve colors by name, or pass palettes directly to the ggplot2
scales.
### Palette gallery
<img src="man/figures/README-palette-gallery-1.png" alt="" width="100%" />
### Using palettes
``` r
# Discrete fill with the qualitative palette
ggplot(mpg, aes(class, fill = class)) +
geom_bar(show.legend = FALSE) +
scale_fill_civilytics() +
labs(title = "Vehicle counts by class", x = NULL, y = NULL) +
theme_civilytics(grid = "y")
```
<img src="man/figures/README-palette-usage-1.png" alt="" width="100%" />
``` r
# Continuous fill with a sequential palette
ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
geom_tile() +
scale_fill_civilytics("seq_ember", discrete = FALSE) +
labs(title = "Old Faithful eruption density") +
theme_civilytics(grid = "none")
```
<img src="man/figures/README-palette-usage-2.png" alt="" width="100%" />
## Themes
Three theme variants cover the most common output contexts. All share
the same typographic structure and accept `grid` and `paper_bg`
parameters.
### Editorial (default)
The default theme uses a warm paper background with horizontal gridlines
— an editorial, Pew-style layout.
``` r
base_plot <- ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(colour = factor(cyl)), size = 2) +
scale_color_civilytics() +
labs(
title = "Engine size vs. highway fuel economy",
subtitle = "Colored by number of cylinders",
caption = "Source: ggplot2::mpg",
colour = "Cylinders",
x = "Displacement (L)", y = "Highway MPG"
)
base_plot + theme_civilytics()
```
<img src="man/figures/README-theme-editorial-1.png" alt="" width="100%" />
### Grid options
The `grid` parameter controls which major gridlines are drawn.
<img src="man/figures/README-theme-grids-1.png" alt="" width="100%" />
### Dark
Dark navy background with light text, suitable for presentations or
dashboards on dark surfaces.
``` r
base_plot + theme_civilytics_dark()
```
<img src="man/figures/README-theme-dark-1.png" alt="" width="100%" />
### Slide
Transparent background and larger base font (18 pt), sized for Reveal.js
slides or PowerPoint exports.
``` r
base_plot + theme_civilytics_slide()
```
<img src="man/figures/README-theme-slide-1.png" alt="" width="100%" />
### Facets
Facet strips use the `paper_2` tint, with the title font.
``` r
ggplot(mpg, aes(displ, hwy)) +
geom_point(colour = civilytics_colors["navy_600"], size = 1.5) +
facet_wrap(~class, ncol = 4) +
labs(
title = "Highway MPG by vehicle class",
x = "Displacement (L)", y = "Highway MPG"
) +
theme_civilytics(grid = "y")
```
<img src="man/figures/README-theme-facets-1.png" alt="" width="100%" />
## Logo utilities
Add the Civilytics logo to any ggplot using the pipe-friendly
`civilytics_logo()` or the lower-level `add_logo()` /
`make_logo_grob()`.
``` r
library(civilytics)
library(ggplot2)
(ggplot(mpg, aes(displ, hwy)) +
geom_point() +
theme_civilytics()) |>
civilytics_logo()
```
## Other utilities
The package also includes helpers for public-sector data analysis:
| Function | Purpose |
|:---|:---|
| `pretty_count()` / `pretty_per()` | Format numbers and percentages |
| `grade_level_to_num()` | Convert grade labels (KG, 01–12) to numeric |
| `race_short_names()` | Standardize NCES race/ethnicity categories |
| `get_fips()` / `get_stabbr()` | State FIPS code lookups |
| `clopper_pearson()` / `agresti_coull_interval()` | Proportion confidence intervals |
| `match_test()` / `trunc_match()` | Fuzzy join diagnostics |
| `perturb_count()` / `random_round()` | Privacy-preserving data perturbation |
+24 -2
View File
@@ -4,10 +4,32 @@
\name{civilytics-package}
\alias{civilytics}
\alias{civilytics-package}
\title{civilytics: Utilities Functions for Civilytics}
\title{civilytics: Brand Themes, Color Palettes, and Utility Functions for Civilytics}
\description{
House R functions for Civilytics Consulting LLC. This package implements a variety of useful functions for creating and branding analyses produced by Civilytics Consulting LLC.
Provides a complete ggplot2 brand theme system for Civilytics Consulting,
including editorial light, dark, and slide-optimized themes; 10 curated
color palettes; logo composition; and data-wrangling helpers for
public-sector analysis.
}
\section{Themes}{
\itemize{
\item [theme_civilytics()] -- editorial theme with warm paper background
\item [theme_civilytics_dark()] -- navy background variant
\item [theme_civilytics_slide()] -- transparent background, larger text
}
}
\section{Color palettes}{
\itemize{
\item [civilytics_colors] -- 53 named brand colors
\item [civilytics_palettes] -- 10 visualization palettes
\item [civilytics_palette()] -- extract colors by palette name
\item [scale_color_civilytics()] / [scale_fill_civilytics()] -- ggplot2 scales
}
}
\seealso{
Useful links:
\itemize{
+5 -3
View File
@@ -12,10 +12,12 @@ civilytics_colors
}
\description{
A named character vector of all Civilytics brand colors, matching the CSS
custom properties defined on www.civilytics.com (`--cv-*` variables).
custom properties in the Civilytics design system (`--cv-*` variables).
Includes full ramps for navy, ember, and violet, plus supporting hues
(teal, plum, moss, brass) and semantic status colors.
}
\examples{
civilytics_colors["accent"]
civilytics_colors[c("ink", "paper")]
civilytics_colors["ink"]
civilytics_colors[c("navy_600", "ember_600", "teal_600")]
}
\keyword{datasets}
+9 -14
View File
@@ -2,28 +2,23 @@
% Please edit documentation in R/colors.R
\name{civilytics_pal}
\alias{civilytics_pal}
\title{Civilytics brand color palette}
\title{Civilytics palette function (closure)}
\usage{
civilytics_pal(name = "main", n = NULL)
civilytics_pal(name = "qual", reverse = FALSE)
}
\arguments{
\item{name}{Character. Palette name: `"main"`, `"sequential"`, or
`"diverging"`. Defaults to `"main"`.}
\item{name}{Character. Palette name. See `names(civilytics_palettes)`.}
\item{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()].}
\item{reverse}{Logical. Reverse the palette order. Default `FALSE`.}
}
\value{
A character vector of hex color codes.
A function that takes integer `n` and returns `n` hex color codes.
}
\description{
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).
Returns a closure `function(n)` suitable for passing to
[ggplot2::discrete_scale()] or similar scale constructors.
}
\examples{
civilytics_pal() # all 6 qualitative colors
civilytics_pal("sequential", n = 3)
civilytics_pal("diverging", n = 5)
pal_fn <- civilytics_pal("qual")
pal_fn(4)
}
+30
View File
@@ -0,0 +1,30 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/colors.R
\name{civilytics_palette}
\alias{civilytics_palette}
\title{Get colors from a Civilytics palette}
\usage{
civilytics_palette(name = "qual", n = NULL, reverse = FALSE)
}
\arguments{
\item{name}{Character. Palette name. See `names(civilytics_palettes)`.}
\item{n}{Integer or `NULL`. Number of colors to return. If `NULL`, returns
all defined stops.}
\item{reverse}{Logical. Reverse the palette order. Default `FALSE`.}
}
\value{
A character vector of hex color codes.
}
\description{
Returns a character vector of hex codes from a named Civilytics palette.
For qualitative palettes, colors beyond the palette length recycle with a
warning. For sequential and diverging palettes, colors are interpolated
via [grDevices::colorRampPalette()].
}
\examples{
civilytics_palette() # all 7 qualitative colors
civilytics_palette("seq_navy", n = 5) # 5-stop navy ramp
civilytics_palette("div_navy_ember", n = 11, reverse = TRUE)
}
+50
View File
@@ -0,0 +1,50 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/colors.R
\docType{data}
\name{civilytics_palettes}
\alias{civilytics_palettes}
\title{Civilytics visualization palettes}
\format{
A named list of character vectors of hex color codes.
}
\usage{
civilytics_palettes
}
\description{
Named list of curated color palettes for data visualization. Qualitative
palettes use distinct hues for categorical data; sequential palettes ramp
through a single hue for ordered data; diverging palettes fan out from a
neutral midpoint.
}
\section{Qualitative (categorical data)}{
\describe{
\item{`qual`}{7 distinct hues: navy, ember, plum, violet, red, teal, ink-3}
\item{`qual_warm`}{Warm-leaning: ember, red, magenta, plum, violet}
\item{`qual_cool`}{Cool-leaning: navy, violet, plum, teal}
}
}
\section{Sequential (ordered/continuous data)}{
\describe{
\item{`seq_ember`}{Light to dark ember (9 stops)}
\item{`seq_navy`}{Light to dark navy (9 stops)}
\item{`seq_violet`}{Light to dark violet (9 stops)}
\item{`seq_paper_ink`}{Paper through ember/plum to ink (8 stops, good for heatmaps)}
}
}
\section{Diverging (data with a meaningful midpoint)}{
\describe{
\item{`div_navy_ember`}{Navy <-> paper <-> ember (9 stops)}
\item{`div_violet_ember`}{Violet <-> paper <-> ember (9 stops)}
}
}
\examples{
names(civilytics_palettes)
civilytics_palettes[["qual"]]
}
\keyword{datasets}
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+9 -11
View File
@@ -4,25 +4,23 @@
\alias{scale_color_civilytics}
\title{Civilytics color scale for ggplot2}
\usage{
scale_color_civilytics(palette = "main", discrete = TRUE, ...)
scale_color_civilytics(palette = "qual", discrete = TRUE, reverse = FALSE, ...)
}
\arguments{
\item{palette}{Character. Palette name passed to [civilytics_pal()].
Defaults to `"main"`.}
\item{palette}{Character. Palette name. Defaults to `"qual"`.}
\item{discrete}{Logical. `TRUE` (default) for a discrete scale; `FALSE` for
a continuous gradient via [ggplot2::scale_color_gradientn()].}
\item{discrete}{Logical. `TRUE` (default) for categorical data; `FALSE`
for a continuous gradient via [ggplot2::scale_color_gradientn()].}
\item{...}{Additional arguments passed to the underlying ggplot2 scale
function.}
\item{reverse}{Logical. Reverse the palette order. Default `FALSE`.}
\item{...}{Additional arguments passed to the ggplot2 scale function.}
}
\value{
A ggplot2 scale object.
}
\description{
Applies a Civilytics brand palette to the `colour` aesthetic. Use
`discrete = TRUE` for categorical variables and `discrete = FALSE` for
continuous gradients.
Applies a Civilytics brand palette to the `colour` aesthetic.
}
\examples{
library(ggplot2)
@@ -32,5 +30,5 @@ ggplot(mpg, aes(displ, hwy, colour = class)) +
ggplot(mpg, aes(displ, hwy, colour = cty)) +
geom_point() +
scale_color_civilytics("sequential", discrete = FALSE)
scale_color_civilytics("seq_navy", discrete = FALSE)
}
+9 -11
View File
@@ -4,25 +4,23 @@
\alias{scale_fill_civilytics}
\title{Civilytics fill scale for ggplot2}
\usage{
scale_fill_civilytics(palette = "main", discrete = TRUE, ...)
scale_fill_civilytics(palette = "qual", discrete = TRUE, reverse = FALSE, ...)
}
\arguments{
\item{palette}{Character. Palette name passed to [civilytics_pal()].
Defaults to `"main"`.}
\item{palette}{Character. Palette name. Defaults to `"qual"`.}
\item{discrete}{Logical. `TRUE` (default) for a discrete scale; `FALSE` for
a continuous gradient via [ggplot2::scale_fill_gradientn()].}
\item{discrete}{Logical. `TRUE` (default) for categorical data; `FALSE`
for a continuous gradient via [ggplot2::scale_fill_gradientn()].}
\item{...}{Additional arguments passed to the underlying ggplot2 scale
function.}
\item{reverse}{Logical. Reverse the palette order. Default `FALSE`.}
\item{...}{Additional arguments passed to the ggplot2 scale function.}
}
\value{
A ggplot2 scale object.
}
\description{
Applies a Civilytics brand palette to the `fill` aesthetic. Use
`discrete = TRUE` for categorical variables and `discrete = FALSE` for
continuous gradients.
Applies a Civilytics brand palette to the `fill` aesthetic.
}
\examples{
library(ggplot2)
@@ -32,5 +30,5 @@ ggplot(mpg, aes(class, fill = class)) +
ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
geom_tile() +
scale_fill_civilytics("sequential", discrete = FALSE)
scale_fill_civilytics("seq_ember", discrete = FALSE)
}
+27 -4
View File
@@ -14,8 +14,10 @@ theme_civilytics(
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
)
}
\arguments{
@@ -45,10 +47,18 @@ Default `11/14`.}
[civilytics_colors]`["paper"]` (`#FAF7F2`).}
\item{accent}{Character. Hex code for accent/highlight color. Defaults to
[civilytics_colors]`["accent"]` (`#C25311`).}
[civilytics_colors]`["ember_600"]` (`#C25311`).}
\item{strip_color}{Character. Hex code for facet strip background. Defaults
to [civilytics_colors]`["paper_2"]` (`#F2EDE4`).}
\item{grid}{Character. Which major gridlines to draw: `"y"` (default,
horizontal only), `"x"` (vertical only), `"both"`, or `"none"`.}
\item{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).}
}
\value{
A complete ggplot2 [ggplot2::theme()] object.
@@ -59,6 +69,11 @@ Civilytics brand color palette and typography. Requires ggplot2 >= 4.0.0
for the `ink`, `paper`, and `accent` base-theme parameters.
}
\details{
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.
@@ -66,13 +81,21 @@ automatically via [showtext] when the package is attached. Call
\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)
}
}
+18 -14
View File
@@ -13,9 +13,11 @@ theme_civilytics_dark(
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
)
}
\arguments{
@@ -45,31 +47,33 @@ Default `11/14`.}
[civilytics_colors]`["paper"]` (`#FAF7F2`).}
\item{accent}{Character. Hex code for accent/highlight color. Defaults to
[civilytics_colors]`["accent"]` (`#C25311`).}
[civilytics_colors]`["ember_600"]` (`#C25311`).}
\item{strip_color}{Character. Hex code for facet strip background. Defaults
to [civilytics_colors]`["paper_2"]` (`#F2EDE4`).}
\item{grid}{Character. Which major gridlines to draw: `"y"` (default,
horizontal only), `"x"` (vertical only), `"both"`, or `"none"`.}
\item{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).}
}
\value{
A complete ggplot2 [ggplot2::theme()] object.
}
\description{
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.
}
\details{
All parameters from [theme_civilytics()] are accepted and override the dark
defaults.
Pair with `make_logo_grob(variant = "dark")` for the white logo.
}
\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() +
+79
View File
@@ -0,0 +1,79 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/theme.R
\name{theme_civilytics_slide}
\alias{theme_civilytics_slide}
\title{Slide-friendly Civilytics ggplot2 theme}
\usage{
theme_civilytics_slide(
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
)
}
\arguments{
\item{font_size}{Numeric. Base font size in points. Default `14`.}
\item{font_family}{Character. Font family for body/axis text. Default
`"Inter"` (loaded via showtext).}
\item{title_family}{Character. Font family for plot titles and strip labels.
Default `"Libre Franklin"` (loaded via showtext).}
\item{line_size}{Numeric. Base line width. Default `0.5`.}
\item{rel_small}{Numeric. Scale factor for small text relative to
`font_size`. Default `12/14`.}
\item{rel_tiny}{Numeric. Scale factor for tiny text relative to `font_size`.
Default `11/14`.}
\item{rel_large}{Numeric. Scale factor for large text (titles) relative to
`font_size`. Default `16/14`.}
\item{ink}{Character. Hex code for foreground/text color. Defaults to
[civilytics_colors]`["ink"]` (`#0E1A2B`).}
\item{paper}{Character. Hex code for background color. Defaults to
[civilytics_colors]`["paper"]` (`#FAF7F2`).}
\item{accent}{Character. Hex code for accent/highlight color. Defaults to
[civilytics_colors]`["ember_600"]` (`#C25311`).}
\item{strip_color}{Character. Hex code for facet strip background. Defaults
to [civilytics_colors]`["paper_2"]` (`#F2EDE4`).}
\item{grid}{Character. Which major gridlines to draw: `"y"` (default,
horizontal only), `"x"` (vertical only), `"both"`, or `"none"`.}
\item{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).}
}
\value{
A complete ggplot2 [ggplot2::theme()] object.
}
\description{
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.
}
\examples{
\dontrun{
library(ggplot2)
ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point() +
scale_color_civilytics() +
theme_civilytics_slide()
}
}
+122 -27
View File
@@ -9,46 +9,67 @@ test_that("civilytics_colors is a named character vector of hex codes", {
})
test_that("civilytics_colors contains expected brand keys", {
expected <- c("paper", "ink", "accent", "navy", "navy_dark",
"accent_dark", "paper_2", "rule")
expected <- c("paper", "ink", "navy_600", "ember_600", "teal_600",
"plum_600", "moss_600", "paper_2", "rule")
expect_true(all(expected %in% names(civilytics_colors)))
})
test_that("civilytics_colors values match brand specification", {
expect_equal(unname(civilytics_colors["paper"]), "#FAF7F2")
expect_equal(unname(civilytics_colors["ink"]), "#0E1A2B")
expect_equal(unname(civilytics_colors["accent"]), "#C25311")
expect_equal(unname(civilytics_colors["navy_dark"]), "#1A2E4A")
expect_equal(unname(civilytics_colors["accent_dark"]),"#E07840")
expect_equal(unname(civilytics_colors["ink"]), "#0E1A2B")
expect_equal(unname(civilytics_colors["ember_600"]), "#C25311")
expect_equal(unname(civilytics_colors["navy_700"]), "#1A2E4A")
expect_equal(unname(civilytics_colors["ember_400"]), "#EA8A49")
})
# --- civilytics_pal ----------------------------------------------------------
# --- civilytics_palettes / civilytics_palette --------------------------------
test_that("civilytics_pal returns character vector of hex codes", {
result <- civilytics_pal()
test_that("civilytics_palettes contains all expected palettes", {
expected <- c("qual", "qual_warm", "qual_cool",
"seq_ember", "seq_navy", "seq_violet", "seq_paper_ink",
"div_navy_ember", "div_violet_ember")
expect_true(all(expected %in% names(civilytics_palettes)))
})
test_that("civilytics_palette returns character vector of hex codes", {
result <- civilytics_palette()
expect_type(result, "character")
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", result)))
})
test_that("civilytics_pal respects n argument", {
expect_length(civilytics_pal("main", n = 3), 3)
expect_length(civilytics_pal("sequential", n = 5), 5)
test_that("civilytics_palette respects n argument", {
expect_length(civilytics_palette("qual", n = 3), 3)
expect_length(civilytics_palette("seq_navy", n = 5), 5)
})
test_that("civilytics_pal interpolates when n exceeds palette size", {
result <- civilytics_pal("main", n = 10)
expect_length(result, 10)
test_that("civilytics_palette interpolates sequential palettes", {
result <- civilytics_palette("seq_ember", n = 20)
expect_length(result, 20)
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", result)))
})
test_that("civilytics_pal errors on unknown palette name", {
expect_error(civilytics_pal("nope"), "'nope' is not a valid Civilytics palette")
test_that("civilytics_palette warns when recycling qualitative", {
expect_warning(civilytics_palette("qual", n = 10), "recycling")
})
test_that("civilytics_pal supports all three named palettes", {
expect_no_error(civilytics_pal("main"))
expect_no_error(civilytics_pal("sequential"))
expect_no_error(civilytics_pal("diverging"))
test_that("civilytics_palette reverse works", {
fwd <- civilytics_palette("seq_navy")
rev <- civilytics_palette("seq_navy", reverse = TRUE)
expect_equal(fwd, rev(rev))
})
test_that("civilytics_palette errors on unknown palette name", {
expect_error(civilytics_palette("nope"), "'nope' is not a valid palette")
})
# --- civilytics_pal (closure) ------------------------------------------------
test_that("civilytics_pal returns a function", {
f <- civilytics_pal()
expect_type(f, "closure")
result <- f(4)
expect_length(result, 4)
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", result)))
})
# --- scale_color/fill_civilytics ---------------------------------------------
@@ -59,7 +80,7 @@ test_that("scale_color_civilytics returns a Scale object (discrete)", {
})
test_that("scale_color_civilytics returns a Scale object (continuous)", {
sc <- scale_color_civilytics("sequential", discrete = FALSE)
sc <- scale_color_civilytics("seq_navy", discrete = FALSE)
expect_s3_class(sc, "Scale")
})
@@ -69,7 +90,12 @@ test_that("scale_fill_civilytics returns a Scale object (discrete)", {
})
test_that("scale_fill_civilytics returns a Scale object (continuous)", {
sc <- scale_fill_civilytics("sequential", discrete = FALSE)
sc <- scale_fill_civilytics("seq_ember", discrete = FALSE)
expect_s3_class(sc, "Scale")
})
test_that("scale_color_civilytics reverse parameter works", {
sc <- scale_color_civilytics("qual", reverse = TRUE)
expect_s3_class(sc, "Scale")
})
@@ -128,6 +154,42 @@ test_that("theme_civilytics font_size parameter scales text elements", {
expect_gt(th_big$text$size, th_sml$text$size)
})
test_that("theme_civilytics grid parameter controls gridlines", {
th_y <- theme_civilytics(grid = "y")
th_x <- theme_civilytics(grid = "x")
th_both <- theme_civilytics(grid = "both")
th_none <- theme_civilytics(grid = "none")
# grid="y" shows y gridlines, blanks x
expect_s3_class(th_y$panel.grid.major.y, "element_line")
expect_s3_class(th_y$panel.grid.major.x, "element_blank")
# grid="x" shows x gridlines, blanks y
expect_s3_class(th_x$panel.grid.major.x, "element_line")
expect_s3_class(th_x$panel.grid.major.y, "element_blank")
# grid="both" shows both
expect_s3_class(th_both$panel.grid.major.x, "element_line")
expect_s3_class(th_both$panel.grid.major.y, "element_line")
# grid="none" blanks both
expect_s3_class(th_none$panel.grid.major.x, "element_blank")
expect_s3_class(th_none$panel.grid.major.y, "element_blank")
})
test_that("theme_civilytics paper_bg=FALSE gives transparent background", {
th <- theme_civilytics(paper_bg = FALSE)
expect_true(is.na(th$plot.background$fill))
expect_true(is.na(th$panel.background$fill))
})
test_that("theme_civilytics has plot-aligned title and caption", {
th <- theme_civilytics()
expect_equal(th$plot.title.position, "plot")
expect_equal(th$plot.caption.position, "plot")
})
# --- theme_civilytics_dark ---------------------------------------------------
test_that("theme_civilytics_dark returns a complete ggplot2 theme", {
@@ -148,14 +210,47 @@ test_that("theme_civilytics_dark uses paper color as ink (light text on dark bg)
expect_equal(th$text$colour, unname(civilytics_colors["paper"]))
})
test_that("theme_civilytics_dark uses navy_dark as background", {
test_that("theme_civilytics_dark uses navy_700 as background", {
th <- theme_civilytics_dark()
expect_equal(th$plot.background$fill, unname(civilytics_colors["navy_dark"]))
expect_equal(th$plot.background$fill, unname(civilytics_colors["navy_700"]))
})
test_that("theme_civilytics_dark uses navy for strip background", {
test_that("theme_civilytics_dark uses navy_600 for strip background", {
th <- theme_civilytics_dark()
expect_equal(th$strip.background$fill, unname(civilytics_colors["navy"]))
expect_equal(th$strip.background$fill, unname(civilytics_colors["navy_600"]))
})
test_that("theme_civilytics_dark inherits grid parameter", {
th <- theme_civilytics_dark(grid = "both")
expect_s3_class(th$panel.grid.major.x, "element_line")
expect_s3_class(th$panel.grid.major.y, "element_line")
})
# --- theme_civilytics_slide --------------------------------------------------
test_that("theme_civilytics_slide returns a complete ggplot2 theme", {
th <- theme_civilytics_slide()
expect_s3_class(th, "theme")
expect_true(attr(th, "complete"))
})
test_that("theme_civilytics_slide has transparent background by default", {
th <- theme_civilytics_slide()
expect_true(is.na(th$plot.background$fill))
})
test_that("theme_civilytics_slide uses larger base font", {
th_slide <- theme_civilytics_slide()
th_base <- theme_civilytics()
expect_gt(th_slide$text$size, th_base$text$size)
})
test_that("theme_civilytics_slide applies to a ggplot without error", {
p <- ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point() +
scale_color_civilytics() +
theme_civilytics_slide()
expect_no_error(ggplot_build(p))
})
# --- make_logo_grob ----------------------------------------------------------