diff --git a/DESCRIPTION b/DESCRIPTION
index 51f71fc..1d167fd 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -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
diff --git a/NAMESPACE b/NAMESPACE
index 409f60f..ba6b532 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -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)
diff --git a/R/civilytics-package.R b/R/civilytics-package.R
index 075b387..866eae7 100644
--- a/R/civilytics-package.R
+++ b/R/civilytics-package.R
@@ -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"))
diff --git a/R/colors.R b/R/colors.R
index 6a00222..5fce7b7 100644
--- a/R/colors.R
+++ b/R/colors.R
@@ -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),
...
)
}
diff --git a/R/fonts.R b/R/fonts.R
index 8af992a..e73ee80 100644
--- a/R/fonts.R
+++ b/R/fonts.R
@@ -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."
)
}
diff --git a/R/theme.R b/R/theme.R
index 2937fe3..ea5ca04 100644
--- a/R/theme.R
+++ b/R/theme.R
@@ -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)
+ )
+}
diff --git a/README.Rmd b/README.Rmd
new file mode 100644
index 0000000..e0175b1
--- /dev/null
+++ b/README.Rmd
@@ -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 |
diff --git a/README.md b/README.md
index 4a8ee96..9dec9e9 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,181 @@
-
-# civilytics
-
-
-
-
-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()
+```
+
+
+
+## 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
+
+
+
+### 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")
+```
+
+
+
+``` 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")
+```
+
+
+
+## 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()
+```
+
+
+
+### Grid options
+
+The `grid` parameter controls which major gridlines are drawn.
+
+
+
+### Dark
+
+Dark navy background with light text, suitable for presentations or
+dashboards on dark surfaces.
+
+``` r
+base_plot + theme_civilytics_dark()
+```
+
+
+
+### Slide
+
+Transparent background and larger base font (18 pt), sized for Reveal.js
+slides or PowerPoint exports.
+
+``` r
+base_plot + theme_civilytics_slide()
+```
+
+
+
+### 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")
+```
+
+
+
+## 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 |
diff --git a/man/civilytics-package.Rd b/man/civilytics-package.Rd
index 854fdd1..a132667 100644
--- a/man/civilytics-package.Rd
+++ b/man/civilytics-package.Rd
@@ -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{
diff --git a/man/civilytics_colors.Rd b/man/civilytics_colors.Rd
index 313911e..5cecf85 100644
--- a/man/civilytics_colors.Rd
+++ b/man/civilytics_colors.Rd
@@ -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}
diff --git a/man/civilytics_pal.Rd b/man/civilytics_pal.Rd
index 38bdb98..7afc649 100644
--- a/man/civilytics_pal.Rd
+++ b/man/civilytics_pal.Rd
@@ -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)
}
diff --git a/man/civilytics_palette.Rd b/man/civilytics_palette.Rd
new file mode 100644
index 0000000..d382a90
--- /dev/null
+++ b/man/civilytics_palette.Rd
@@ -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)
+}
diff --git a/man/civilytics_palettes.Rd b/man/civilytics_palettes.Rd
new file mode 100644
index 0000000..fc3f678
--- /dev/null
+++ b/man/civilytics_palettes.Rd
@@ -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}
diff --git a/man/figures/README-palette-gallery-1.png b/man/figures/README-palette-gallery-1.png
new file mode 100644
index 0000000..f63a316
Binary files /dev/null and b/man/figures/README-palette-gallery-1.png differ
diff --git a/man/figures/README-palette-usage-1.png b/man/figures/README-palette-usage-1.png
new file mode 100644
index 0000000..ee81692
Binary files /dev/null and b/man/figures/README-palette-usage-1.png differ
diff --git a/man/figures/README-palette-usage-2.png b/man/figures/README-palette-usage-2.png
new file mode 100644
index 0000000..9eb2a20
Binary files /dev/null and b/man/figures/README-palette-usage-2.png differ
diff --git a/man/figures/README-pressure-1.png b/man/figures/README-pressure-1.png
deleted file mode 100644
index c092055..0000000
Binary files a/man/figures/README-pressure-1.png and /dev/null differ
diff --git a/man/figures/README-quickstart-1.png b/man/figures/README-quickstart-1.png
new file mode 100644
index 0000000..cdc8f47
Binary files /dev/null and b/man/figures/README-quickstart-1.png differ
diff --git a/man/figures/README-theme-dark-1.png b/man/figures/README-theme-dark-1.png
new file mode 100644
index 0000000..dad8680
Binary files /dev/null and b/man/figures/README-theme-dark-1.png differ
diff --git a/man/figures/README-theme-editorial-1.png b/man/figures/README-theme-editorial-1.png
new file mode 100644
index 0000000..2ba738d
Binary files /dev/null and b/man/figures/README-theme-editorial-1.png differ
diff --git a/man/figures/README-theme-facets-1.png b/man/figures/README-theme-facets-1.png
new file mode 100644
index 0000000..d837ebd
Binary files /dev/null and b/man/figures/README-theme-facets-1.png differ
diff --git a/man/figures/README-theme-grids-1.png b/man/figures/README-theme-grids-1.png
new file mode 100644
index 0000000..216391d
Binary files /dev/null and b/man/figures/README-theme-grids-1.png differ
diff --git a/man/figures/README-theme-slide-1.png b/man/figures/README-theme-slide-1.png
new file mode 100644
index 0000000..6070be1
Binary files /dev/null and b/man/figures/README-theme-slide-1.png differ
diff --git a/man/scale_color_civilytics.Rd b/man/scale_color_civilytics.Rd
index f54ab8c..ee9580e 100644
--- a/man/scale_color_civilytics.Rd
+++ b/man/scale_color_civilytics.Rd
@@ -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)
}
diff --git a/man/scale_fill_civilytics.Rd b/man/scale_fill_civilytics.Rd
index 78487f2..33ca9ac 100644
--- a/man/scale_fill_civilytics.Rd
+++ b/man/scale_fill_civilytics.Rd
@@ -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)
}
diff --git a/man/theme_civilytics.Rd b/man/theme_civilytics.Rd
index 7fbe383..9d72847 100644
--- a/man/theme_civilytics.Rd
+++ b/man/theme_civilytics.Rd
@@ -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)
}
}
diff --git a/man/theme_civilytics_dark.Rd b/man/theme_civilytics_dark.Rd
index 9964c31..0c05256 100644
--- a/man/theme_civilytics_dark.Rd
+++ b/man/theme_civilytics_dark.Rd
@@ -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() +
diff --git a/man/theme_civilytics_slide.Rd b/man/theme_civilytics_slide.Rd
new file mode 100644
index 0000000..494a849
--- /dev/null
+++ b/man/theme_civilytics_slide.Rd
@@ -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()
+}
+}
diff --git a/tests/testthat/test_theme.R b/tests/testthat/test_theme.R
index e7beae7..4c197c7 100644
--- a/tests/testthat/test_theme.R
+++ b/tests/testthat/test_theme.R
@@ -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 ----------------------------------------------------------