feat: expand color system to 53 colors/10 palettes, add grid/slide themes, and README gallery
R-CMD-check / R CMD check (push) Successful in 3m41s
R-CMD-check / R CMD check (push) Successful in 3m41s
Replace the 16-color / 3-palette system with the full Civilytics design system: 53 named colors (full navy, ember, violet ramps plus teal, plum, moss, brass supporting hues) and 10 visualization palettes (qual, qual_warm, qual_cool, seq_ember, seq_navy, seq_violet, seq_paper_ink, div_navy_ember, div_violet_ember). Enrich theme_civilytics() with grid and paper_bg parameters, add theme_civilytics_slide() for presentations, and load all four brand fonts (Inter, Libre Franklin, Source Serif 4, JetBrains Mono). Add README.Rmd with rendered gallery showing all palettes, theme variants, grid options, dark/slide modes, facets, and scale usage.
This commit is contained in:
+33
-14
@@ -1,14 +1,33 @@
|
||||
#' @keywords internal
|
||||
#' @importFrom stats qbeta
|
||||
#' @importFrom stats qnorm
|
||||
"_PACKAGE"
|
||||
|
||||
## usethis namespace: start
|
||||
## usethis namespace: end
|
||||
NULL
|
||||
|
||||
# state.abb and state.name are lazy data from the datasets package (base R).
|
||||
# They cannot be imported via @importFrom — suppress the R CMD check NOTE here.
|
||||
utils::globalVariables(c("state.abb", "state.name"))
|
||||
|
||||
|
||||
#' @description
|
||||
#' Provides a complete ggplot2 brand theme system for Civilytics Consulting,
|
||||
#' including editorial light, dark, and slide-optimized themes; 10 curated
|
||||
#' color palettes; logo composition; and data-wrangling helpers for
|
||||
#' public-sector analysis.
|
||||
#'
|
||||
#' @section Themes:
|
||||
#' \itemize{
|
||||
#' \item [theme_civilytics()] -- editorial theme with warm paper background
|
||||
#' \item [theme_civilytics_dark()] -- navy background variant
|
||||
#' \item [theme_civilytics_slide()] -- transparent background, larger text
|
||||
#' }
|
||||
#'
|
||||
#' @section Color palettes:
|
||||
#' \itemize{
|
||||
#' \item [civilytics_colors] -- 53 named brand colors
|
||||
#' \item [civilytics_palettes] -- 10 visualization palettes
|
||||
#' \item [civilytics_palette()] -- extract colors by palette name
|
||||
#' \item [scale_color_civilytics()] / [scale_fill_civilytics()] -- ggplot2 scales
|
||||
#' }
|
||||
#'
|
||||
#' @keywords internal
|
||||
#' @importFrom stats qbeta
|
||||
#' @importFrom stats qnorm
|
||||
"_PACKAGE"
|
||||
|
||||
## usethis namespace: start
|
||||
## usethis namespace: end
|
||||
NULL
|
||||
|
||||
# state.abb and state.name are lazy data from the datasets package (base R).
|
||||
# They cannot be imported via @importFrom -- suppress the R CMD check NOTE here.
|
||||
utils::globalVariables(c("state.abb", "state.name"))
|
||||
|
||||
+216
-83
@@ -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),
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user