diff --git a/R/logo.R b/R/logo.R index c531204..5cf9ae7 100644 --- a/R/logo.R +++ b/R/logo.R @@ -46,17 +46,27 @@ get_png <- function(filename) { #' @param plot a ggplot2 grob #' @param logo a logo grob created by make_logo_grob() #' @param margin_param a numeric specifying what margin to add or subtract to align the logo +#' @param font_scale Numeric. Multiplicative scaling factor applied to text +#' sizes before composing the plot with the logo. Default `1.1` inflates +#' text by ~10 \% to compensate for the viewport shrinkage caused by +#' [gridExtra::arrangeGrob()]. Set to `1` to disable. #' #' @return a grob with a logo attached to it ready to plot #' @importFrom ggplot2 theme #' @importFrom gridExtra arrangeGrob #' @export -add_logo <- function(plot, logo, margin_param = NULL) { +add_logo <- function(plot, logo, margin_param = NULL, font_scale = 1.1) { + # Inflate text sizes to compensate for arrangeGrob viewport shrinkage. + # All theme text elements use rel() sizing, so scaling the root 'text' + # element cascades to titles, axis labels, legends, captions, and strips. + if (!is.null(font_scale) && font_scale != 1) { + base_size <- plot$theme$text$size %||% 14 + plot <- plot + ggplot2::theme( + text = ggplot2::element_text(size = base_size * font_scale) + ) + } + if(has_caption(plot)) { - # convert the caption size to a negative number and on the "pt" scale - # p1$theme$plot.caption$size * 1.1 - # Count the number of lines, which is this + 1 - # For each line, we can add a certain negative space to align the logo cap_lines <- measure_caption(plot) if (!is.null(margin_param)) { plot <- plot + theme(plot.margin = unit(c(7, 7, margin_param, 7), "pt")) @@ -107,13 +117,15 @@ has_caption <- function(gg) { any(names(gg$labels) == "caption") } -#' Add a logo to a ggplot2 object +#' Add a logo to multiple ggplot2 objects #' #' @param plot_list a list containing ggplot2 objects #' @param logo a grob containing the logo created with `make_logo_grob` #' @param nrow an integer, default = 1, for the number of rows to align the plots in #' @param widths an optional vector the same length as plot_list with the widths for each plot #' @param margin_param a number giving the adjustment up or down to help manually align logo and captions +#' @param font_scale Numeric. Multiplicative scaling factor applied to text +#' sizes before composing. Default `1.1`. See [add_logo()] for details. #' #' @return a grid object #' @note The resulting object needs to be drawn to the screen using grid.draw() @@ -131,7 +143,18 @@ has_caption <- function(gg) { #' grid.draw(plot_and_logo) #' dev.off() #' } -add_logo_ga <- function(plot_list, logo, nrow = 1, widths = NULL, margin_param = NULL) { +add_logo_ga <- function(plot_list, logo, nrow = 1, widths = NULL, + margin_param = NULL, font_scale = 1.1) { + # Inflate text sizes to compensate for viewport shrinkage + if (!is.null(font_scale) && font_scale != 1) { + plot_list <- lapply(plot_list, function(p) { + base_size <- p$theme$text$size %||% 14 + p + ggplot2::theme( + text = ggplot2::element_text(size = base_size * font_scale) + ) + }) + } + # Change position of logo depending on if plot has a caption if (!is.null(margin_param)) { margin <- theme(plot.margin = unit(c(7, 7, margin_param, 7), "pt")) @@ -224,6 +247,9 @@ make_logo_grob <- function(type = c("wordmark", "mark"), #' [make_logo_grob()]. #' @param margin_param Numeric or `NULL`. Manual margin adjustment passed to #' [add_logo()]. +#' @param font_scale Numeric. Inflate text sizes by this factor to compensate +#' for viewport shrinkage when composing with [gridExtra::arrangeGrob()]. +#' Default `1.1` (~10 \% inflation). Set to `1` to disable. #' #' @return A grob (from [gridExtra::arrangeGrob()]) ready to draw with #' [grid::grid.draw()]. @@ -246,12 +272,20 @@ make_logo_grob <- function(type = c("wordmark", "mark"), #' theme_civilytics_dark()) |> #' civilytics_logo(variant = "dark", type = "mark") |> #' grid.draw() +#' +#' # Disable font inflation +#' (ggplot(mpg, aes(displ, hwy)) + +#' geom_point() + +#' theme_civilytics()) |> +#' civilytics_logo(font_scale = 1) |> +#' grid.draw() #' } civilytics_logo <- function(plot, type = c("wordmark", "mark"), variant = c("light", "dark"), - margin_param = NULL) { + margin_param = NULL, + font_scale = 1.1) { logo <- make_logo_grob(type = type, variant = variant) - add_logo(plot, logo, margin_param = margin_param) + add_logo(plot, logo, margin_param = margin_param, font_scale = font_scale) } diff --git a/R/theme.R b/R/theme.R index ea5ca04..0b3dbeb 100644 --- a/R/theme.R +++ b/R/theme.R @@ -24,7 +24,8 @@ #' @param rel_tiny Numeric. Scale factor for tiny text relative to `font_size`. #' Default `11/14`. #' @param rel_large Numeric. Scale factor for large text (titles) relative to -#' `font_size`. Default `16/14`. +#' `font_size`. Default `20/14` (~1.43x), matching the Civilytics editorial +#' design system. #' @param ink Character. Hex code for foreground/text color. Defaults to #' [civilytics_colors]`["ink"]` (`#0E1A2B`). #' @param paper Character. Hex code for background color. Defaults to @@ -40,6 +41,26 @@ #' transparent background (useful for slides or overlay on colored #' surfaces). #' +#' @section Font size hierarchy: +#' All text sizes are derived from `font_size` using relative scale factors. +#' At the default `font_size = 14`: +#' +#' | Element | Scale factor | Default size | +#' |:--------|:-------------|:-------------| +#' | Plot title | `rel_large` (1.43x) | ~20 pt | +#' | Subtitle | 1.0x | 14 pt | +#' | Axis text (tick labels) | `rel_small` (0.86x) | ~12 pt | +#' | Axis titles | `rel_small` (0.86x) | ~12 pt | +#' | Legend text | `rel_small` (0.86x) | ~12 pt | +#' | Caption | `rel_tiny` (0.79x) | ~11 pt | +#' | Legend title | `rel_tiny` (0.79x) | ~11 pt | +#' | Strip text (facets) | `rel_small` (0.86x) | ~12 pt | +#' +#' To uniformly scale all text, change `font_size`. To adjust only the title +#' prominence, change `rel_large`. When using [civilytics_logo()] to add a +#' logo below the plot, pass `font_scale` to compensate for viewport +#' shrinkage. +#' #' @return A complete ggplot2 [ggplot2::theme()] object. #' @export #' @@ -62,6 +83,11 @@ #' ggplot(mpg, aes(displ, hwy)) + #' geom_point() + #' theme_civilytics(paper_bg = FALSE) +#' +#' # Larger text for poster or display +#' ggplot(mpg, aes(displ, hwy)) + +#' geom_point() + +#' theme_civilytics(font_size = 18) #' } theme_civilytics <- function( font_size = 14, @@ -70,7 +96,7 @@ theme_civilytics <- function( line_size = 0.5, rel_small = 12 / 14, rel_tiny = 11 / 14, - rel_large = 16 / 14, + rel_large = 20 / 14, ink = unname(civilytics_colors["ink"]), paper = unname(civilytics_colors["paper"]), accent = unname(civilytics_colors["ember_600"]), @@ -132,7 +158,7 @@ theme_civilytics <- function( axis.line.y = ggplot2::element_blank(), axis.text = ggplot2::element_text( color = ink_2, - size = small_size + size = ggplot2::rel(rel_small) ), axis.text.x = ggplot2::element_text( margin = ggplot2::margin(t = small_size / 4), @@ -249,7 +275,7 @@ theme_civilytics <- function( ), plot.title.position = "plot", plot.subtitle = ggplot2::element_text( - size = ggplot2::rel(rel_small), + size = ggplot2::rel(1), color = ink_2, hjust = 0, vjust = 1, @@ -307,7 +333,7 @@ theme_civilytics_dark <- function( line_size = 0.5, rel_small = 12 / 14, rel_tiny = 11 / 14, - rel_large = 16 / 14, + rel_large = 20 / 14, ink = unname(civilytics_colors["paper"]), paper = unname(civilytics_colors["navy_700"]), accent = unname(civilytics_colors["ember_400"]), @@ -359,7 +385,7 @@ theme_civilytics_slide <- function( line_size = 0.5, rel_small = 12 / 14, rel_tiny = 11 / 14, - rel_large = 16 / 14, + rel_large = 20 / 14, ink = unname(civilytics_colors["ink"]), paper = unname(civilytics_colors["paper"]), accent = unname(civilytics_colors["ember_600"]), diff --git a/README.Rmd b/README.Rmd index d12b942..e54068f 100644 --- a/README.Rmd +++ b/README.Rmd @@ -256,3 +256,30 @@ The package also includes helpers for public-sector data analysis: | `clopper_pearson()` / `agresti_coull_interval()` | Proportion confidence intervals | | `match_test()` / `trunc_match()` | Fuzzy join diagnostics | | `perturb_count()` / `random_round()` | Privacy-preserving data perturbation | + +## Maintaining brand assets + +Logo and brand mark files live in `inst/img/`. The package ships both +PNG (for ggplot2 raster composition) and SVG (for Quarto/HTML output) +variants: + +| File | Format | Used by | +|:-----|:-------|:--------| +| `civilytics-wordmark.png` / `.svg` | Full "Civilytics" lockup | `make_logo_grob("wordmark", "light")`, Quarto templates | +| `civilytics-wordmark-reverse.png` / `.svg` | Light-on-dark wordmark | `make_logo_grob("wordmark", "dark")`, dark slides | +| `civilytics-mark.png` / `.svg` | Compact C-pulse icon | `make_logo_grob("mark", "light")` | +| `civilytics-mark-reverse.svg` | Light-on-dark mark | `make_logo_grob("mark", "dark")` | +| `civilytics-pulse.svg` | Standalone waveform glyph | Quarto slide footer chrome | + +To update the logos, replace the files in `inst/img/` with new versions +using the same filenames. The PNG files must be raster images (the +ggplot2 logo functions read them via `png::readPNG()`). SVG files are +passed through as-is by Quarto and HTML templates. + +After replacing files, re-render the README gallery to update the +screenshots: + +```r +devtools::load_all() +rmarkdown::render("README.Rmd") +``` diff --git a/README.md b/README.md index dadb60a..17f1a3c 100644 --- a/README.md +++ b/README.md @@ -229,3 +229,30 @@ The package also includes helpers for public-sector data analysis: | `clopper_pearson()` / `agresti_coull_interval()` | Proportion confidence intervals | | `match_test()` / `trunc_match()` | Fuzzy join diagnostics | | `perturb_count()` / `random_round()` | Privacy-preserving data perturbation | + +## Maintaining brand assets + +Logo and brand mark files live in `inst/img/`. The package ships both +PNG (for ggplot2 raster composition) and SVG (for Quarto/HTML output) +variants: + +| File | Format | Used by | +|:---|:---|:---| +| `civilytics-wordmark.png` / `.svg` | Full “Civilytics” lockup | `make_logo_grob("wordmark", "light")`, Quarto templates | +| `civilytics-wordmark-reverse.png` / `.svg` | Light-on-dark wordmark | `make_logo_grob("wordmark", "dark")`, dark slides | +| `civilytics-mark.png` / `.svg` | Compact C-pulse icon | `make_logo_grob("mark", "light")` | +| `civilytics-mark-reverse.svg` | Light-on-dark mark | `make_logo_grob("mark", "dark")` | +| `civilytics-pulse.svg` | Standalone waveform glyph | Quarto slide footer chrome | + +To update the logos, replace the files in `inst/img/` with new versions +using the same filenames. The PNG files must be raster images (the +ggplot2 logo functions read them via `png::readPNG()`). SVG files are +passed through as-is by Quarto and HTML templates. + +After replacing files, re-render the README gallery to update the +screenshots: + +``` r +devtools::load_all() +rmarkdown::render("README.Rmd") +``` diff --git a/inst/img/civilytics-mark-reverse.svg b/inst/img/civilytics-mark-reverse.svg new file mode 100644 index 0000000..65709be --- /dev/null +++ b/inst/img/civilytics-mark-reverse.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/inst/img/civilytics-mark.svg b/inst/img/civilytics-mark.svg new file mode 100644 index 0000000..1d9eec4 --- /dev/null +++ b/inst/img/civilytics-mark.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/inst/img/civilytics-pulse.svg b/inst/img/civilytics-pulse.svg new file mode 100644 index 0000000..b379654 --- /dev/null +++ b/inst/img/civilytics-pulse.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/inst/img/civilytics-wordmark-reverse.svg b/inst/img/civilytics-wordmark-reverse.svg new file mode 100644 index 0000000..e96dc19 --- /dev/null +++ b/inst/img/civilytics-wordmark-reverse.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/inst/img/civilytics-wordmark.svg b/inst/img/civilytics-wordmark.svg new file mode 100644 index 0000000..df20775 --- /dev/null +++ b/inst/img/civilytics-wordmark.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/man/add_logo.Rd b/man/add_logo.Rd index b6cb744..ccdc4e5 100644 --- a/man/add_logo.Rd +++ b/man/add_logo.Rd @@ -4,7 +4,7 @@ \alias{add_logo} \title{Add a logo to a ggplot2 object} \usage{ -add_logo(plot, logo, margin_param = NULL) +add_logo(plot, logo, margin_param = NULL, font_scale = 1.1) } \arguments{ \item{plot}{a ggplot2 grob} @@ -12,6 +12,11 @@ add_logo(plot, logo, margin_param = NULL) \item{logo}{a logo grob created by make_logo_grob()} \item{margin_param}{a numeric specifying what margin to add or subtract to align the logo} + +\item{font_scale}{Numeric. Multiplicative scaling factor applied to text +sizes before composing the plot with the logo. Default `1.1` inflates +text by ~10 \% to compensate for the viewport shrinkage caused by +[gridExtra::arrangeGrob()]. Set to `1` to disable.} } \value{ a grob with a logo attached to it ready to plot diff --git a/man/add_logo_ga.Rd b/man/add_logo_ga.Rd index 4414486..c065575 100644 --- a/man/add_logo_ga.Rd +++ b/man/add_logo_ga.Rd @@ -2,9 +2,16 @@ % Please edit documentation in R/logo.R \name{add_logo_ga} \alias{add_logo_ga} -\title{Add a logo to a ggplot2 object} +\title{Add a logo to multiple ggplot2 objects} \usage{ -add_logo_ga(plot_list, logo, nrow = 1, widths = NULL, margin_param = NULL) +add_logo_ga( + plot_list, + logo, + nrow = 1, + widths = NULL, + margin_param = NULL, + font_scale = 1.1 +) } \arguments{ \item{plot_list}{a list containing ggplot2 objects} @@ -16,12 +23,15 @@ add_logo_ga(plot_list, logo, nrow = 1, widths = NULL, margin_param = NULL) \item{widths}{an optional vector the same length as plot_list with the widths for each plot} \item{margin_param}{a number giving the adjustment up or down to help manually align logo and captions} + +\item{font_scale}{Numeric. Multiplicative scaling factor applied to text +sizes before composing. Default `1.1`. See [add_logo()] for details.} } \value{ a grid object } \description{ -Add a logo to a ggplot2 object +Add a logo to multiple ggplot2 objects } \note{ The resulting object needs to be drawn to the screen using grid.draw() diff --git a/man/civilytics_logo.Rd b/man/civilytics_logo.Rd index 5ade49a..5339dd7 100644 --- a/man/civilytics_logo.Rd +++ b/man/civilytics_logo.Rd @@ -8,7 +8,8 @@ civilytics_logo( plot, type = c("wordmark", "mark"), variant = c("light", "dark"), - margin_param = NULL + margin_param = NULL, + font_scale = 1.1 ) } \arguments{ @@ -22,6 +23,10 @@ civilytics_logo( \item{margin_param}{Numeric or `NULL`. Manual margin adjustment passed to [add_logo()].} + +\item{font_scale}{Numeric. Inflate text sizes by this factor to compensate +for viewport shrinkage when composing with [gridExtra::arrangeGrob()]. +Default `1.1` (~10 \% inflation). Set to `1` to disable.} } \value{ A grob (from [gridExtra::arrangeGrob()]) ready to draw with @@ -59,5 +64,12 @@ library(ggplot2); library(grid) theme_civilytics_dark()) |> civilytics_logo(variant = "dark", type = "mark") |> grid.draw() + +# Disable font inflation +(ggplot(mpg, aes(displ, hwy)) + + geom_point() + + theme_civilytics()) |> + civilytics_logo(font_scale = 1) |> + grid.draw() } } diff --git a/man/figures/README-logo-mark-1.png b/man/figures/README-logo-mark-1.png index 2e4aaf3..b523593 100644 Binary files a/man/figures/README-logo-mark-1.png and b/man/figures/README-logo-mark-1.png differ diff --git a/man/figures/README-logo-multi-1.png b/man/figures/README-logo-multi-1.png index 60fe191..a99e581 100644 Binary files a/man/figures/README-logo-multi-1.png and b/man/figures/README-logo-multi-1.png differ diff --git a/man/figures/README-logo-wordmark-1.png b/man/figures/README-logo-wordmark-1.png index f70a849..76c272c 100644 Binary files a/man/figures/README-logo-wordmark-1.png and b/man/figures/README-logo-wordmark-1.png differ diff --git a/man/figures/README-palette-usage-1.png b/man/figures/README-palette-usage-1.png index ee81692..17e8c6d 100644 Binary files a/man/figures/README-palette-usage-1.png 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 index 9eb2a20..0b92476 100644 Binary files a/man/figures/README-palette-usage-2.png and b/man/figures/README-palette-usage-2.png differ diff --git a/man/figures/README-quickstart-1.png b/man/figures/README-quickstart-1.png index cdc8f47..f342c40 100644 Binary files a/man/figures/README-quickstart-1.png 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 index dad8680..c4c2abe 100644 Binary files a/man/figures/README-theme-dark-1.png 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 index 2ba738d..92c3157 100644 Binary files a/man/figures/README-theme-editorial-1.png 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 index d837ebd..944c90e 100644 Binary files a/man/figures/README-theme-facets-1.png 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 index 216391d..40731db 100644 Binary files a/man/figures/README-theme-grids-1.png 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 index 6070be1..a9e3d34 100644 Binary files a/man/figures/README-theme-slide-1.png and b/man/figures/README-theme-slide-1.png differ diff --git a/man/theme_civilytics.Rd b/man/theme_civilytics.Rd index 9d72847..c7dfeb4 100644 --- a/man/theme_civilytics.Rd +++ b/man/theme_civilytics.Rd @@ -11,7 +11,7 @@ theme_civilytics( line_size = 0.5, rel_small = 12/14, rel_tiny = 11/14, - rel_large = 16/14, + rel_large = 20/14, ink = unname(civilytics_colors["ink"]), paper = unname(civilytics_colors["paper"]), accent = unname(civilytics_colors["ember_600"]), @@ -38,7 +38,8 @@ Default `"Libre Franklin"` (loaded via showtext).} Default `11/14`.} \item{rel_large}{Numeric. Scale factor for large text (titles) relative to -`font_size`. Default `16/14`.} +`font_size`. Default `20/14` (~1.43x), matching the Civilytics editorial +design system.} \item{ink}{Character. Hex code for foreground/text color. Defaults to [civilytics_colors]`["ink"]` (`#0E1A2B`).} @@ -78,6 +79,28 @@ 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. } +\section{Font size hierarchy}{ + +All text sizes are derived from `font_size` using relative scale factors. +At the default `font_size = 14`: + +| Element | Scale factor | Default size | +|:--------|:-------------|:-------------| +| Plot title | `rel_large` (1.43x) | ~20 pt | +| Subtitle | 1.0x | 14 pt | +| Axis text (tick labels) | `rel_small` (0.86x) | ~12 pt | +| Axis titles | `rel_small` (0.86x) | ~12 pt | +| Legend text | `rel_small` (0.86x) | ~12 pt | +| Caption | `rel_tiny` (0.79x) | ~11 pt | +| Legend title | `rel_tiny` (0.79x) | ~11 pt | +| Strip text (facets) | `rel_small` (0.86x) | ~12 pt | + +To uniformly scale all text, change `font_size`. To adjust only the title +prominence, change `rel_large`. When using [civilytics_logo()] to add a +logo below the plot, pass `font_scale` to compensate for viewport +shrinkage. +} + \examples{ \dontrun{ library(ggplot2) @@ -97,5 +120,10 @@ ggplot(mpg, aes(displ, hwy, colour = class)) + ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics(paper_bg = FALSE) + +# Larger text for poster or display +ggplot(mpg, aes(displ, hwy)) + + geom_point() + + theme_civilytics(font_size = 18) } } diff --git a/man/theme_civilytics_dark.Rd b/man/theme_civilytics_dark.Rd index 0c05256..97dd6b8 100644 --- a/man/theme_civilytics_dark.Rd +++ b/man/theme_civilytics_dark.Rd @@ -11,7 +11,7 @@ theme_civilytics_dark( line_size = 0.5, rel_small = 12/14, rel_tiny = 11/14, - rel_large = 16/14, + rel_large = 20/14, ink = unname(civilytics_colors["paper"]), paper = unname(civilytics_colors["navy_700"]), accent = unname(civilytics_colors["ember_400"]), @@ -38,7 +38,8 @@ Default `"Libre Franklin"` (loaded via showtext).} Default `11/14`.} \item{rel_large}{Numeric. Scale factor for large text (titles) relative to -`font_size`. Default `16/14`.} +`font_size`. Default `20/14` (~1.43x), matching the Civilytics editorial +design system.} \item{ink}{Character. Hex code for foreground/text color. Defaults to [civilytics_colors]`["ink"]` (`#0E1A2B`).} diff --git a/man/theme_civilytics_slide.Rd b/man/theme_civilytics_slide.Rd index 494a849..4cff8c9 100644 --- a/man/theme_civilytics_slide.Rd +++ b/man/theme_civilytics_slide.Rd @@ -11,7 +11,7 @@ theme_civilytics_slide( line_size = 0.5, rel_small = 12/14, rel_tiny = 11/14, - rel_large = 16/14, + rel_large = 20/14, ink = unname(civilytics_colors["ink"]), paper = unname(civilytics_colors["paper"]), accent = unname(civilytics_colors["ember_600"]), @@ -38,7 +38,8 @@ Default `"Libre Franklin"` (loaded via showtext).} Default `11/14`.} \item{rel_large}{Numeric. Scale factor for large text (titles) relative to -`font_size`. Default `16/14`.} +`font_size`. Default `20/14` (~1.43x), matching the Civilytics editorial +design system.} \item{ink}{Character. Hex code for foreground/text color. Defaults to [civilytics_colors]`["ink"]` (`#0E1A2B`).}