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 ----------------------------------------------------------