--- title: "Notre Dame Color Palettes" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Notre Dame Color Palettes} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.align = "center", fig.width = 7, fig.height = 4.5, dpi = 96, out.width = "100%" ) library(NDPalette) # This vignette is a ggplot2 demonstration. If ggplot2 is not installed, # the figures are skipped rather than raising an error. has_ggplot2 <- requireNamespace("ggplot2", quietly = TRUE) if (has_ggplot2) { library(ggplot2) # Pair the brand colors with a light built-in theme throughout (see the # "Pairing with a light theme" section below). theme_set(theme_minimal(base_size = 12)) } knitr::opts_chunk$set(eval = has_ggplot2) ``` `NDPalette` provides color palettes that approximate the University of Notre Dame brand colors, together with `ggplot2` color and fill scales. These colors are well suited to statistical visualization and psychometric analysis: the white-safe ordering keeps groups legible in plots of data, and the near-white-to-navy ramps suit continuous quantities such as correlations and factor loadings. The colors come back as plain hexadecimal strings, so they work anywhere that accepts a color: a `ggplot2` scale, a base graphics call, a `plotly` chart, a table, or a figure rendered outside R. It opens with a quick start for every workflow (`ggplot2`, base R graphics, R Markdown, and Shiny), previews the palettes, shows each color with its name, walks through coloring one group up to ten, gives a gallery of common chart types in both `ggplot2` and base R, shows how to mix in a color from outside the palette, and closes with a set of statistical and psychometric figures. It is an independent project, not affiliated with or endorsed by the University. ```{r load, eval = TRUE} library(NDPalette) ``` # Quick start: every workflow, the easy way The point of `NDPalette` is that you should not have to choose colors by hand. In each workflow you add **one** thing and `NDPalette` applies the colors for you. Everything after this section is only for when you want more control: a set number of colors, a specific brand color by name, or mixing in a color from outside the palette. **ggplot2** --- add a scale: ```{r qs-ggplot, fig.height = 3.3} ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2) + scale_color_nd() # one line; the colors are chosen for you ``` **Base R graphics** --- set the palette once, then color by a grouping factor: ```{r qs-base, eval = TRUE, fig.height = 3.6} palette(nd_palette(8)) # base R now draws in Notre Dame colors plot(Petal.Length ~ Sepal.Length, data = iris, col = Species, pch = 19) palette("default") # restore base R's palette when done ``` **R Markdown** --- theme a whole HTML report from one line in the YAML header: ```yaml output: NDPalette::html_nd_document # navy headings, brand callouts, soft page ``` **Shiny** --- color the page chrome from the palette with a `bslib` theme (the *Theming a Shiny app with NDPalette* vignette walks through a full app): ```r bslib::bs_theme(primary = nd_color("navy"), secondary = nd_color("bright_gold")) ``` # The default Notre Dame palette The default `"nd"` palette leads with the six Notre Dame brand colors that read clearly on a white background, then extends through seven former Notre Dame brand colors, thirteen anchors in all. `show_palette()` previews the whole set (it defaults to the full palette): ```{r full, eval = TRUE, fig.height = 1.8, fig.width = 9} show_palette() ``` Every color also has a readable name. The card below lists the thirteen data colors with their names and hexadecimal values, so a color can be matched to its label at a glance: ```{r named-card, fig.height = 5, fig.width = 6.5} pal <- nd_palette() # the thirteen data colors named <- nd_colors[match(pal, nd_colors$hex), ] # matched to their catalog names named$pos <- rev(seq_len(nrow(named))) ggplot(named, aes(x = 0, y = pos)) + geom_tile(aes(fill = hex), width = 0.5, height = 0.82) + geom_text(aes(x = 0.32, label = paste0(name, " (", hex, ")")), hjust = 0, size = 3.1) + scale_fill_identity() + coord_cartesian(xlim = c(-0.3, 4), clip = "off") + labs(x = NULL, y = NULL) + theme_void() ``` `show_palette()` can also print those names on the swatches themselves, in place of the hex values, by passing a character vector to `labels`: ```{r named-swatch, eval = TRUE, fig.height = 1.8, fig.width = 9} show_palette(nd_palette(), labels = named$name) ``` The raw anchors are exported as `nd_palettes` for tools and for users who want the hexadecimal values directly: ```{r anchors, eval = TRUE} nd_palettes$nd ``` `nd_palette(n)` returns the first `n` of these. Because the colors are taken in a fixed order, asking for five groups always yields the same five colors, and those five are the first five of any larger request: ```{r small-requests, eval = TRUE} nd_palette(2) nd_palette(5) ``` # From one group to ten As the number of groups grows, the palette adds one color at a time. The staircase below shows exactly which colors `nd_palette(n)` returns for `n` running from one to ten: each row is one value of `n`, read left to right. ```{r ladder, fig.height = 5} ladder <- do.call(rbind, lapply(1:10, function(k) { data.frame(n = k, position = seq_len(k), hex = nd_palette(k)) })) ladder$n <- factor(ladder$n, levels = 10:1) ggplot(ladder, aes(position, n, fill = hex)) + geom_tile(color = "white", linewidth = 1.2) + scale_fill_identity() + scale_x_continuous(breaks = 1:10, position = "top") + coord_equal() + labs(x = "color position", y = "number of groups (n)") + theme(panel.grid = element_blank()) ``` The same progression, drawn as `ggplot2` bar charts colored with `scale_fill_nd()`. Each panel is a plot of `n` groups, so the panels show the palette at work for one through ten categories: ```{r facets, fig.height = 4.5} bars <- do.call(rbind, lapply(1:10, function(k) { data.frame(n = k, position = seq_len(k)) })) bars$value <- 2 + sin(bars$position) bars$group <- factor(bars$position, levels = 1:10) bars$panel <- factor(paste0("n = ", bars$n), levels = paste0("n = ", 1:10)) ggplot(bars, aes(group, value, fill = group)) + geom_col(width = 0.85) + facet_wrap(~ panel, nrow = 2, scales = "free_x") + scale_fill_nd() + labs(x = NULL, y = NULL) + theme(legend.position = "none", axis.text = element_blank(), panel.grid = element_blank()) ``` The first four colors (ND Blue, Bright Gold, Green, Bright Blue) are the most strongly separated, so two-, three-, and four-group plots read cleanly. Past six groups the palette draws on the former Notre Dame colors, which introduce teal, maroon, and purple to keep neighboring categories distinct. Two of those, maroon and purple, are dark, so for a plot with many small or thin marks (a fine line chart, a dense scatter) it is worth checking that adjacent groups stay legible, and reaching for the colorblind-friendly `"nd_cvd"` palette (below) when separation matters more than leading with the brand colors. # The scales in practice `scale_color_nd()` and `scale_fill_nd()` drop into a `ggplot2` plot and map the grouping levels to the Notre Dame colors automatically. `scale_colour_nd()` is the British-spelling alias. Two and three groups, on the color aesthetic. The data are `iris` --- sepal and petal measurements of 150 flowers from three species: ```{r iris, fig.height = 4.5} ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2.5, alpha = 0.9) + scale_color_nd() + labs(title = "Three groups", x = "sepal length", y = "petal length", color = "species") ``` Three groups, on the fill aesthetic --- the count of the 32 `mtcars` cars by number of cylinders: ```{r mtcars, fig.height = 4.5} ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) + geom_bar(width = 0.7) + scale_fill_nd() + labs(title = "Three groups", x = "cylinders", y = "count", fill = "cylinders") ``` Five groups, five well-separated colors --- the count of the roughly 54,000 `diamonds` by cut quality: ```{r diamonds, fig.height = 4.5} ggplot(diamonds, aes(cut, fill = cut)) + geom_bar() + scale_fill_nd() + labs(title = "Five groups", x = "cut", y = "count") + theme(legend.position = "none") ``` Six groups --- `InsectSprays` counts insects on field plots treated with each of six insecticides (A--F): ```{r sprays, fig.height = 4.5} ggplot(InsectSprays, aes(spray, count, fill = spray)) + geom_boxplot() + scale_fill_nd() + labs(title = "Six groups", x = "spray", y = "count") + theme(legend.position = "none") ``` To reverse the color order, pass `reverse = TRUE` to the scale: ```{r reverse, fig.height = 4.5} ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) + geom_bar(width = 0.7) + scale_fill_nd(reverse = TRUE) + labs(x = "cylinders", y = "count", fill = "cylinders") + theme(legend.position = "none") ``` # A gallery of plot types The same scales drop into the chart types people reach for most often. Every example below uses a built-in R dataset, so each one runs without any extra data. A line chart with one series per group suits five groups. `Orange` records the trunk circumference of five orange trees as they age: ```{r lines, fig.height = 4.5} ggplot(Orange, aes(age, circumference, color = Tree)) + geom_line(linewidth = 1) + geom_point(size = 2) + scale_color_nd() + labs(title = "Five series, as lines", x = "age (days)", y = "trunk circumference (mm)", color = "tree") ``` Grouped (dodged) bars color a second factor with `scale_fill_nd()`. `warpbreaks` gives the number of warp breaks on a loom by wool type (A or B) and tension (low, medium, high): ```{r grouped-bars, fig.height = 4.5} ggplot(warpbreaks, aes(tension, breaks, fill = wool)) + geom_bar(stat = "summary", fun = "mean", position = "dodge", width = 0.7) + scale_fill_nd() + labs(title = "Grouped bars", x = "tension", y = "mean breaks", fill = "wool") ``` Overlapping densities put the palette on a filled area, where a little transparency keeps the groups readable. `ToothGrowth` is guinea-pig tooth length at three doses of vitamin C: ```{r density, fig.height = 4.5} ggplot(ToothGrowth, aes(len, fill = factor(dose))) + geom_density(alpha = 0.6) + scale_fill_nd() + labs(title = "Overlapping densities", x = "tooth length", y = "density", fill = "dose") ``` A scatter plot with a fitted line per group pairs `geom_smooth()` with `scale_color_nd()`. `ChickWeight` follows the weight of chicks on four diets over their first weeks: ```{r fitted-lines, fig.height = 4.5} ggplot(ChickWeight, aes(Time, weight, color = Diet)) + geom_point(size = 1.6, alpha = 0.5) + geom_smooth(method = "lm", formula = y ~ x, se = FALSE) + scale_color_nd() + labs(title = "Fitted lines by group", x = "time (days)", y = "weight (g)", color = "diet") ``` A stacked bar normalized to proportions (`position = "fill"`) shows how a second factor divides each category, a common way to read composition. Here the passenger class composition of the `Titanic` differs sharply by sex (the crew was almost entirely male): ```{r stacked-fill, fig.height = 4.5} titanic <- aggregate(Freq ~ Sex + Class, data = as.data.frame(Titanic), FUN = sum) ggplot(titanic, aes(Sex, Freq, fill = Class)) + geom_col(position = "fill") + scale_fill_nd() + labs(title = "Stacked proportions", x = NULL, y = "proportion", fill = "passenger class") ``` A lollipop chart --- a tidy alternative to bars --- colors the stems and heads from the palette with `scale_color_nd()`. Here it shows the mean final weight of chicks on each of the four `ChickWeight` diets: ```{r lollipop, fig.height = 4.5} chick <- aggregate(weight ~ Diet, data = ChickWeight, FUN = mean) ggplot(chick, aes(Diet, weight, color = Diet)) + geom_segment(aes(xend = Diet, yend = 0), linewidth = 1.5) + geom_point(size = 6) + scale_color_nd() + labs(title = "A lollipop chart", x = "diet", y = "mean weight (g)") + theme(legend.position = "none") ``` A bar chart in polar coordinates makes a radial chart, painted straight from the palette --- again the count of `diamonds` by cut quality: ```{r polar, fig.height = 4.6, fig.width = 5} ggplot(diamonds, aes(cut, fill = cut)) + geom_bar(width = 1, color = "white") + scale_fill_nd() + coord_polar() + labs(title = "A radial bar chart", x = NULL, y = NULL, fill = "cut") + theme_minimal(base_size = 12) ``` # Base R graphics The palettes are plain hex strings, so they drop into base graphics too. The easiest route is to set R's active palette once with `palette(nd_palette(n))`; after that, base plots that color by a factor or integer use Notre Dame colors automatically, with no per-call color work. ```{r base-palette, eval = TRUE, fig.height = 4} palette(nd_palette(6)) # base R now draws in Notre Dame colors boxplot(count ~ spray, data = InsectSprays, col = 1:6, border = "grey30", main = "Six groups, base R", xlab = "spray", ylab = "count") palette("default") # restore when done ``` Or pass `nd_palette()` straight to a `col` argument, without touching the global palette: ```{r base-barplot, eval = TRUE, fig.height = 4} barplot(table(mtcars$cyl), col = nd_palette(3), border = NA, main = "Three groups, base R", xlab = "cylinders", ylab = "count") ``` A sequential ramp shades a heatmap or surface; `image()` is the base R counterpart of a filled `ggplot2` raster. A ramp straight from a pale tint to navy passes through a washed-out blue-gray in the middle, so the ramp here runs light sky blue through Bright Blue to navy, keeping the midtones a clear blue. `volcano` is a grid of elevations on Maungawhau, a volcano in Auckland: ```{r base-image, eval = TRUE, fig.height = 4.2, fig.width = 5} ramp <- grDevices::colorRampPalette(c(nd_tints[["light_sky_blue"]], nd_color("bright_blue"), nd_color("navy")))(20) image(volcano, col = ramp, axes = FALSE, main = "Maungawhau elevation") ``` A soft tint works as a base R page background through `par(bg = nd_informal_tints[["soft_white"]])`, the base counterpart of setting `plot.background` in `ggplot2`; `par(bg = ...)` takes one color for the whole page. The grid below compares the options on one plot of `mtcars` (fuel economy versus weight for 32 cars from a 1974 issue of *Motor Trend*, one point per car): the soft white, soft yellow, and warm soft-yellow tints as the background, with navy or bright blue points. To use a tint for a real page, pass it to `par(bg = ...)`; to change the point color, change the `nd_color(...)` argument. ```{r base-bg, eval = TRUE, fig.height = 6.5, fig.width = 8} panel <- function(bg, col, lab) { plot(mpg ~ wt, data = mtcars, type = "n", main = lab, xlab = "weight (1000 lbs)", ylab = "mpg") rect(par("usr")[1], par("usr")[3], par("usr")[2], par("usr")[4], col = bg, border = NA) # the tint, shown per panel points(mtcars$wt, mtcars$mpg, pch = 19, col = col) box() } op <- par(mfrow = c(2, 2), mar = c(4, 4, 2, 1)) panel(nd_informal_tints[["soft_white"]], nd_color("navy"), "soft white + navy") panel(nd_informal_tints[["soft_yellow"]], nd_color("navy"), "soft yellow + navy") panel(nd_informal_tints[["soft_yellow_warm"]], nd_color("navy"), "soft yellow, warm (gold) + navy") panel(nd_informal_tints[["soft_white"]], nd_color("bright_blue"), "soft white + bright blue") par(op) ``` # Statistical and psychometric visualization The brand colors are built for plots of data, and they fit the figures that recur in statistical and psychometric work. The examples that follow are a sequential and a diverging ramp on two correlation matrices, a two-group fill for factor loadings, and a several-color scale for item characteristic curves. A correlation heatmap puts the near-white-to-navy ramp on a matrix of item intercorrelations. Here six items measure a single construct, so the off-diagonal correlations cluster around a common value: ```{r corr-heatmap, fig.height = 4.6, fig.width = 5.4} set.seed(113) f <- rnorm(250) # the latent construct load <- 0.65 # common loading items <- sapply(1:6, function(j) load * f + rnorm(250, sd = sqrt(1 - load^2))) colnames(items) <- paste0("i", 1:6) R <- cor(items) dfR <- as.data.frame(as.table(R)) names(dfR) <- c("row", "col", "r") dfR$txt <- ifelse(dfR$r > 0.5, "white", nd_color("navy")) heat <- grDevices::colorRampPalette( c(nd_tints[["light_sky_blue"]], nd_palette(1)))(100) ggplot(dfR, aes(col, row, fill = r)) + geom_tile(color = "white") + geom_text(aes(label = sprintf("%.2f", r), color = txt), size = 3) + scale_fill_gradientn(colors = heat, limits = c(0, 1)) + scale_color_identity() + coord_equal() + labs(title = "Item intercorrelations", x = NULL, y = NULL, fill = "r") ``` Because the off-diagonal correlations here are all similar (around 0.42), the tiles read as nearly one color. When correlations are all alike, no color scale will show much difference among them; the scale does more visible work when the values have a wider range, including negative ones, as in the next example. A second matrix, the correlations among several `mtcars` variables (fuel economy, cylinders, displacement, horsepower, rear-axle ratio, and weight), has a wide range, including strong negative correlations --- fuel economy falls as weight, cylinders, and displacement rise. A *diverging* ramp anchored at the two Notre Dame primaries (navy for negative, a light tint at zero, gold for positive) shows those differences: ```{r corr-diverging, fig.height = 4.8, fig.width = 5.8} vars <- c("mpg", "cyl", "disp", "hp", "drat", "wt") Rm <- cor(mtcars[, vars]) dfm <- as.data.frame(as.table(Rm)); names(dfm) <- c("row", "col", "r") dfm$txt <- ifelse(dfm$r < -0.5, "white", nd_color("navy")) diverging <- grDevices::colorRampPalette( c(nd_color("navy"), nd_tints[["light_warm_white"]], nd_color("bright_gold")))(100) ggplot(dfm, aes(col, row, fill = r)) + geom_tile(color = "white") + geom_text(aes(label = sprintf("%.2f", r), color = txt), size = 3) + scale_fill_gradientn(colors = diverging, limits = c(-1, 1)) + scale_color_identity() + coord_equal() + labs(title = "mtcars correlations (diverging ramp)", x = NULL, y = NULL, fill = "r") ``` Standardized factor loadings are a natural two-group fill: one bar per item for each of two factors, colored ND Blue and Bright Gold. The first four items load on the first factor, the last four on the second: ```{r factor-loadings, fig.height = 4.4, fig.width = 7} loadings <- rbind( data.frame(item = paste0("Item ", 1:8), factor = "Verbal", loading = c(.74, .69, .78, .81, .22, .15, .09, .25)), data.frame(item = paste0("Item ", 1:8), factor = "Quantitative", loading = c(.18, .12, .24, .07, .71, .79, .66, .80))) loadings$item <- factor(loadings$item, levels = paste0("Item ", 8:1)) ggplot(loadings, aes(loading, item, fill = factor)) + geom_col(position = "dodge", width = 0.7) + scale_fill_nd() + labs(title = "Standardized factor loadings", x = "loading", y = NULL, fill = "factor") ``` Item characteristic curves from a two-parameter logistic model give each item one of the well-separated brand colors. Five items, five colors: ```{r item-curves, fig.height = 4.4, fig.width = 7} theta <- seq(-4, 4, length.out = 200) pars <- data.frame(item = paste0("Item ", 1:5), a = c(1.2, 0.8, 1.6, 1.0, 1.9), # discriminations b = c(-1.5, -0.6, 0.1, 0.8, 1.7)) # difficulties icc <- do.call(rbind, lapply(seq_len(nrow(pars)), function(i) { data.frame(theta = theta, item = pars$item[i], p = plogis(pars$a[i] * (theta - pars$b[i]))) })) ggplot(icc, aes(theta, p, color = item)) + geom_line(linewidth = 1) + scale_color_nd() + labs(title = "Item characteristic curves (2PL)", x = expression(theta), y = "probability of a correct response", color = "item") ``` # Finding a color by name and role The palettes above cover most plotting needs, but sometimes you want one specific brand color, or you want to see the whole set at once with a note on what each color is. `nd_colors` is that catalog: one row per color, labeled with its brand (University or Athletics) and its role (primary, secondary, former, tint, or neutral). ```{r colors-table, eval = TRUE} nd_colors ``` `nd_color()` pulls hex values out of that table by name or by role, which is easier than going through a palette name when you only want a color or two: ```{r color-by-name, eval = TRUE} nd_color("navy", "green") # two colors by name nd_color(role = "former") # a whole role group ``` Because the result is a plain character vector of hexadecimal strings, it drops straight into a manual scale, a base graphics call, or anywhere else a color is expected: ```{r color-manual, fig.height = 4.5} ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2.5, alpha = 0.9) + scale_color_manual(values = nd_color("navy", "bright_gold", "green")) + labs(title = "Picked by name", x = "sepal length", y = "petal length", color = "species") ``` The table also carries the Notre Dame Athletics colors for reference. Athletics is a separate brand system: it shares ND Blue with the University, pairs it with a warmer Standard Dome Gold and a metallic gold that has no sanctioned digital value (so its hex is `NA`), and uses an Irish Green a single Pantone step from the University green. These Athletics colors are reference only and never enter `nd_palette()` or the scales. ```{r colors-athletics, eval = TRUE} nd_colors[nd_colors$brand == "athletics", c("name", "hex", "pms")] ``` # Mixing in a color from outside the palette Sometimes one category should be a specific color that is not a Notre Dame color at all --- a rival's color, another institution's, or a flag. Because `nd_palette()` and `nd_color()` return plain hex strings, you can append your own color and hand the combined vector to `scale_color_manual()` (in `ggplot2`) or to a `col` argument (in base R): keep the Notre Dame colors for the brand-relevant groups and let the outside color stand apart. The three below are **not** Notre Dame colors; they are defined here only for the example: ```{r non-nd-defs, eval = TRUE} st_louis_blues <- "#2c5196" # St. Louis Blues blue irish_flag <- "#009900" # Irish flag green dark_goldenrod <- "#b8860b" # darkgoldenrod ``` In `ggplot2`, build the value vector from `nd_color()` plus the outside color and pass it to `scale_color_manual()`: ```{r non-nd-ggplot, fig.height = 4.2} values <- c("setosa" = nd_color("navy"), "versicolor" = nd_color("bright_gold"), "virginica" = st_louis_blues) # the non-ND color ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2.5) + scale_color_manual(values = values) + labs(title = "Two Notre Dame colors plus one from outside the palette", x = "sepal length", y = "petal length", color = "species") ``` The same idea in base R --- the first groups take Notre Dame colors and the last takes the outside color: ```{r non-nd-base, eval = TRUE, fig.height = 4} cols <- c(nd_palette(2), irish_flag) # navy, bright gold, Irish green barplot(c(8, 6, 9), col = cols, border = NA, names.arg = c("A", "B", "C"), main = "Notre Dame palette plus one outside color") ``` # Other palettes ## Former Notre Dame colors The seven colors past the six leading brand colors are former Notre Dame colors: a historical, legacy set no longer in the current brand guide. In the former brand taxonomy six were *secondary* colors and purple was the lone *tertiary* color. They are approximate, legacy colors, included here only to extend the categorical palette past six, and are not part of the current Notre Dame brand. A color such as purple appearing in this set should not be read as a current, primary, or signature Notre Dame color. Because a key like `maroon` does not reveal its tier, the `description` column of `nd_colors` gives each one's tier and hue (teal, for instance, is a former secondary color): ```{r former-desc, eval = TRUE} nd_colors[nd_colors$role == "former", c("name", "hex", "description")] ``` They round out the default palette beyond six, and they are also available on their own: ```{r former-swatch, eval = TRUE, fig.height = 1.6, fig.width = 8} nd_palette(palette = "former") show_palette(nd_palette(palette = "former")) ``` Selectable palettes other than `"nd"` are not wired into the `scale_*_nd()` functions; reach them through `scale_fill_manual()` or `scale_color_manual()`: ```{r former-plot, fig.height = 4.5} ggplot(InsectSprays, aes(spray, count, fill = spray)) + geom_boxplot() + scale_fill_manual(values = nd_palette(palette = "former")) + labs(title = "Former Notre Dame palette", x = "spray", y = "count") + theme(legend.position = "none") ``` ## Colorblind-friendly Notre Dame colors When categorical separation or colorblind safety matters more than leading with the brand colors, the `"nd_cvd"` palette reorders the *actual* Notre Dame colors (current and former) so that they stay distinguishable under the common forms of color vision deficiency, rather than borrowing a palette from another source. The order was derived by a greedy search that, at each step, adds the Notre Dame color that keeps the worst-case minimum CIE-Lab Delta-E as large as possible across normal vision and simulated deuteranopia, protanopia, and tritanopia. The ten anchors stay distinguishable for two through ten categories and lead with ND Blue and Bright Gold, the Notre Dame pair furthest apart under simulation. ```{r cvd-swatch, eval = TRUE, fig.height = 1.6, fig.width = 8} show_palette(nd_palette(palette = "nd_cvd")) ``` The `scale_*_nd()` scales take it through their `palette` argument, so it drops into a plot the same way as the default: ```{r cvd-plot, fig.height = 4.5} ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2.5) + scale_color_nd(palette = "nd_cvd") + labs(title = "Colorblind-friendly Notre Dame colors", x = "sepal length", y = "petal length", color = "species") ``` To see why the ordering is safe, the rows below show the first eight `"nd_cvd"` colors as they appear to normal vision and as simulated for the three dichromacies with the `colorspace` package. The colors stay separated across all four rows: ```{r cvd-sim, eval = requireNamespace("colorspace", quietly = TRUE), fig.height = 3.2, fig.width = 8} cvd8 <- nd_palette(8, palette = "nd_cvd") rows <- list("normal vision" = cvd8, deuteranopia = colorspace::deutan(cvd8), protanopia = colorspace::protan(cvd8), tritanopia = colorspace::tritan(cvd8)) op <- par(mfrow = c(4, 1), mar = c(0.3, 6.5, 0.3, 0.3)) for (nm in names(rows)) { plot(NA, xlim = c(0, 8), ylim = c(0, 1), axes = FALSE, xlab = "", ylab = "", xaxs = "i", yaxs = "i") rect(0:7, 0, 1:8, 1, col = rows[[nm]], border = "white") mtext(nm, side = 2, las = 1, line = 0.5, cex = 0.85) } par(op) ``` # Brand tints, backgrounds, and sequential ramps Four Notre Dame brand tints are too light to read as data on a white background, so `nd_palette()` never returns them. They remain useful away from the data layer and are exported as `nd_tints`: ```{r tints, eval = TRUE} nd_tints ``` A tint makes a panel background behind a single brand color: ```{r tint-bg, fig.height = 4.5} ggplot(mtcars, aes(wt, mpg)) + geom_point(color = nd_palette(1), size = 2.5) + labs(title = "A tint as a panel background", x = "weight", y = "mpg") + theme(panel.background = element_rect(fill = nd_tints[["light_sky_blue"]], color = NA)) ``` Warm White (`#efe9d9`) is the warm counterpart: a cream that works as a full-page background behind dark brand colors. Set it on both `panel.background` and `plot.background`: ```{r warm-white-bg, fig.height = 4.5} ggplot(mtcars, aes(wt, mpg)) + geom_point(color = nd_palette(1), size = 2.5) + labs(title = "Warm White as a full background", x = "weight", y = "mpg") + theme( panel.background = element_rect(fill = nd_tints[["warm_white"]], color = NA), plot.background = element_rect(fill = nd_tints[["warm_white"]], color = NA) ) ``` Six informal soft backgrounds are also provided, as `nd_informal_tints`, running from a very light warm white to a warmer soft yellow. The two `faint_*` tints sit just off pure white (CIE L\* about 99) for the lightest touch, where you want only a hint of warmth rather than plain white. These are **not** Notre Dame brand colors; they are alternatives to a white background for HTML reports, vignettes, and Shiny apps, offered alongside the official Warm Whites: ```{r informal-tints, eval = TRUE} nd_informal_tints ``` ```{r informal-swatch, eval = TRUE, fig.height = 1.5, fig.width = 9} show_palette(nd_informal_tints, labels = names(nd_informal_tints), border = "grey80") ``` Set one on both `panel.background` and `plot.background` for a soft page behind a plot --- here the new, even softer `soft_white`: ```{r soft-bg, fig.height = 4.5} ggplot(mtcars, aes(wt, mpg)) + geom_point(color = nd_palette(1), size = 2.5) + labs(title = "An informal soft-white background", x = "weight", y = "mpg") + theme( panel.background = element_rect(fill = nd_informal_tints[["soft_white"]], color = NA), plot.background = element_rect(fill = nd_informal_tints[["soft_white"]], color = NA) ) ``` A tint also makes a natural light end for a sequential ramp running to a dark brand color, for a continuous (rather than categorical) fill. Putting Bright Blue between the pale tint and navy keeps the midtones a clear blue instead of a washed-out gray: ```{r ramp, eval = TRUE, fig.height = 1.6, fig.width = 8} ramp <- grDevices::colorRampPalette( c(nd_tints[["light_sky_blue"]], nd_color("bright_blue"), nd_color("navy")))(7) show_palette(ramp) ``` Applied to `faithfuld`, a smoothed density of Old Faithful eruptions over eruption length and waiting time: ```{r ramp-plot, fig.height = 4.5} ggplot(faithfuld, aes(waiting, eruptions, fill = density)) + geom_raster() + scale_fill_gradientn(colors = ramp) + labs(title = "Old Faithful eruption density", x = "waiting", y = "eruptions") ``` For a **diverging** quantity --- a correlation that runs from negative to positive, say --- a ramp anchored at the two Notre Dame primaries (navy and gold) with a light tint in the middle covers both directions: ```{r diverging-ramp, eval = TRUE, fig.height = 1.6, fig.width = 9} diverging <- grDevices::colorRampPalette( c(nd_color("navy"), nd_tints[["light_warm_white"]], nd_color("bright_gold")))(11) show_palette(diverging) ``` The *Statistical and psychometric visualization* section above puts this ramp on a correlation matrix. # Theming R Markdown reports The package's stylesheet themes an HTML report in the same colors its figures use, so the prose and the plots match. The simplest route is the `html_nd_document` output format, which wires the stylesheet in for you: ```yaml output: NDPalette::html_nd_document ``` To add it to an existing `html_document` instead, point the `css` field at the shipped stylesheet: ```yaml output: html_document: css: !expr NDPalette::nd_css_path() ``` Either way you get navy headings, a bright-gold accent rule, navy table headers, brand-tinted callout boxes (`.note`, `.important`, `.tip`), and the soft Warm White page background; `nd_css()` also returns the stylesheet as a string if you would rather inline it or edit a copy. The same stylesheet drops into Shiny via `shiny::includeCSS(nd_css_path())`, and the *Theming a Shiny app with NDPalette* vignette walks through a complete app. By default the stylesheet uses a system sans-serif font and loads nothing from the network. To pull two open-source fonts close in feel to the brand typefaces --- Montserrat for body text, Zilla Slab for headings --- generate the stylesheet with `web_fonts = TRUE`, which adds a Google Fonts import: ```yaml output: html_document: css: !expr NDPalette::nd_css(tempfile(fileext = ".css"), web_fonts = TRUE) ``` These are independent open-source typefaces, not the licensed Notre Dame brand fonts. # Pairing with a light theme `NDPalette` is a color system, not a full visual theme: it does not ship a `theme_nd()`. The brand colors pair best with a light built-in `ggplot2` theme, which keeps the figure background white or near-white so the colors carry the meaning. This vignette sets `theme_minimal()` once at the top with `theme_set(theme_minimal(base_size = 12))`, and every figure above inherits it. `theme_light()` and `theme_bw()` are good light alternatives: ```{r theme-light, fig.height = 4.5} ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2.5) + scale_color_nd() + labs(x = "sepal length", y = "petal length", color = "species") + theme_light(base_size = 12) ``` # A note on fonts Notre Dame's brand typefaces are licensed, so `NDPalette` ships colors only, not fonts. You can pair the colors with free, openly licensed fonts that have a similar feel through `ggplot2`'s `base_family` argument. Montserrat is a geometric sans close to the brand sans, and Zilla Slab or Roboto Slab is a slab serif close to the brand display face. These are independent open-source typefaces, not the Notre Dame fonts. For an HTML report, `nd_css(web_fonts = TRUE)` pulls the same two open-source fonts from Google Fonts and applies them to the page (see *Theming R Markdown reports* above). The recipe below uses the `showtext` and `sysfonts` packages to pull two free fonts from Google Fonts and render them. It is shown for reference and is not run when this vignette is built (it would require those two packages and a network connection). ```{r fonts, eval = FALSE} # install.packages(c("showtext", "sysfonts")) library(showtext) sysfonts::font_add_google("Montserrat", "nd_sans") # geometric sans sysfonts::font_add_google("Zilla Slab", "nd_slab") # slab serif showtext_auto() ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(size = 2.5) + scale_color_nd() + labs(title = "Notre Dame colors with a free, ND-evoking font", x = "sepal length", y = "petal length", color = "species") + theme_minimal(base_family = "nd_sans") + theme(plot.title = element_text(family = "nd_slab", face = "bold")) ``` If you already have a free font installed on your system, the `systemfonts` package can register it by name without a download, and the same `base_family` argument then applies it. # Related color tools `NDPalette` is a small, single-purpose package: one institution's colors, curated and ordered for statistical visualization and psychometric analysis. For general color-palette machinery (palette classes, format conversion, interpolation, and a large catalog of palettes from across R), see Emil Hvitfeldt's [palettes](https://cran.r-project.org/package=palettes) package and the companion [r-color-palettes](https://github.com/EmilHvitfeldt/r-color-palettes) gallery. The `NDPalette` anchors are exported as `nd_palettes` precisely so they can be registered with general tools such as [paletteer](https://emilhvitfeldt.github.io/paletteer/) when that broader machinery is what you need.