From ed94aeca264d8847c3d638eb1452bfe49263250f Mon Sep 17 00:00:00 2001 From: Trevor L Davis Date: Tue, 9 Jun 2026 11:35:54 -0700 Subject: [PATCH 1/3] feat: New `mix_col()` utility for subtractive color mixing via Munsell space * Uses `aqp::col2Munsell()`, `aqp::mixMunsell()`, and `aqp::parseMunsell()` to simulate pigment mixing. * Also refactors `assert_suggested()` to accept a `fn` argument (in addition to `pattern`). Co-Authored-By: Claude Sonnet 4.6 --- .Rbuildignore | 1 + .gitignore | 1 + DESCRIPTION | 3 +- NAMESPACE | 2 ++ NEWS.md | 4 +++ R/mean_col.R | 24 --------------- R/utils-color.R | 58 +++++++++++++++++++++++++++++++++++++ R/utils-misc.R | 12 ++++---- _pkgdown.yml | 1 + man/mean_col.Rd | 5 +++- man/mix_col.Rd | 38 ++++++++++++++++++++++++ tests/testthat/test_utils.R | 15 ++++++++-- 12 files changed, 131 insertions(+), 33 deletions(-) delete mode 100644 R/mean_col.R create mode 100644 R/utils-color.R create mode 100644 man/mix_col.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 68aeee9..9a55673 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,6 +7,7 @@ ^README.Rmd$ ^README.html$ ^Rakefile$ +^Rplots.pdf$ ^pkgdown$ ^_pkgdown.yml$ ^data-raw$ diff --git a/.gitignore b/.gitignore index aadc262..1313f1c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ codecov.yml data-raw/*.png vignettes/*.R vignettes/*.html +Rplots.pdf diff --git a/DESCRIPTION b/DESCRIPTION index b1a7ae3..c3c40c8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: gridpattern Type: Package Title: 'grid' Pattern Grobs -Version: 1.4.0-1 +Version: 1.4.0-2 Authors@R: c( person("Trevor L.", "Davis", role=c("aut", "cre"), email="trevor.l.davis@gmail.com", comment = c(ORCID = "0000-0001-6341-4639")), @@ -26,6 +26,7 @@ Imports: utils Suggests: ambient, + aqp, aRtsy, ggplot2 (>= 3.5.0), gtable, diff --git a/NAMESPACE b/NAMESPACE index 595f346..745de9d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -35,7 +35,9 @@ export(grid.pattern_wave) export(grid.pattern_weave) export(guess_has_R4.1_features) export(mean_col) +export(mix_col) export(names_aRtsy) +export(names_hatch) export(names_hex) export(names_magick) export(names_magick_intensity) diff --git a/NEWS.md b/NEWS.md index f234029..e82b85f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,10 @@ gridpattern v1.4.0 (development) New Features ------------ +* `mix_col()` is a new utility function that mixes colors via Munsell color + space using `aqp::mixMunsell()`, simulating subtractive (pigment) mixing. + Requires the suggested package `{aqp}`. + * New "line" pattern with corresponding `grid.pattern_line()`. Unlike the "stripe" pattern which fills bands with solid colour, "line" draws stroked lines using the device's native line rendering, diff --git a/R/mean_col.R b/R/mean_col.R deleted file mode 100644 index b2a4e59..0000000 --- a/R/mean_col.R +++ /dev/null @@ -1,24 +0,0 @@ -#' Compute average color -#' -#' `mean_col()` computes an average color. -#' -#' We currently compute an average color -#' by using the quadratic mean of the colors' RGBA values. -#' -#' @param ... Colors to average -#' @return A color string of 9 characters: `"#"` followed by the -#' red, blue, green, and alpha values in hexadecimal. -#' @examples -#' mean_col("black", "white") -#' mean_col(c("black", "white")) -#' mean_col("red", "blue") -#' @export -mean_col <- function(...) { - cols <- unlist(list(...)) - m <- grDevices::col2rgb(cols, alpha = TRUE) / 255.0 - # quadratic mean suggested at https://stackoverflow.com/a/29576746 - v <- apply(m, 1, quadratic_mean) - grDevices::rgb(v[1], v[2], v[3], v[4]) -} - -quadratic_mean <- function(x) sqrt(mean(x^2)) diff --git a/R/utils-color.R b/R/utils-color.R new file mode 100644 index 0000000..cd57486 --- /dev/null +++ b/R/utils-color.R @@ -0,0 +1,58 @@ +#' Compute average color +#' +#' `mean_col()` computes an average color. +#' +#' We currently compute an average color +#' by using the quadratic mean of the colors' RGBA values. +#' +#' @param ... Colors to average +#' @return A color string of 9 characters: `"#"` followed by the +#' red, blue, green, and alpha values in hexadecimal. +#' @examples +#' mean_col("black", "white") +#' mean_col(c("black", "white")) +#' mean_col("red", "blue") +#' @seealso [mix_col()] for subtractive (pigment) mixing via Munsell color space. +#' @export +mean_col <- function(...) { + cols <- unlist(list(...)) + m <- grDevices::col2rgb(cols, alpha = TRUE) / 255.0 + # quadratic mean suggested at https://stackoverflow.com/a/29576746 + v <- apply(m, 1, quadratic_mean) + grDevices::rgb(v[1], v[2], v[3], v[4]) +} + +quadratic_mean <- function(x) sqrt(mean(x^2)) + +#' Mix colors via Munsell color space +#' +#' `mix_col()` simulates subtractive (pigment) color mixing by converting +#' input colors to Munsell notation via [aqp::col2Munsell()], mixing them with +#' [aqp::mixMunsell()], and converting the result back to an R color string +#' via [aqp::parseMunsell()]. +#' +#' @param ... Colors to mix. Can be individual color strings or character vectors; +#' all are combined into a single vector. +#' @param w A numeric vector of weights or proportions the same length as the combined color vector. +#' Defaults to equal weights. +#' @param mixingMethod Mixing method passed to [aqp::mixMunsell()]. +#' @return A single R color string in hex notation. +#' @examples +#' if (requireNamespace("aqp", quietly = TRUE)) { +#' mix_col("red", "blue") +#' mix_col(c("red", "blue")) +#' } +#' if (requireNamespace("aqp", quietly = TRUE)) { +#' mix_col("red", "yellow", "blue", w = c(2, 1, 1)) +#' } +#' @seealso [mean_col()] for a simpler quadratic-mean RGB approach (no extra packages required). +#' @export +mix_col <- function(..., w = NULL, mixingMethod = "adaptive") { + assert_suggested("aqp", fn = "mix_col") + cols <- unlist(list(...)) + w <- w %||% rep.int(1, length(cols)) + df <- aqp::col2Munsell(cols) + s <- aqp::formatMunsell(df$hue, df$value, df$chroma) + mixed <- suppressMessages(aqp::mixMunsell(s, w = w, mixingMethod = mixingMethod)) + aqp::parseMunsell(mixed$munsell[1L]) +} diff --git a/R/utils-misc.R b/R/utils-misc.R index a66ae88..c0a9d54 100644 --- a/R/utils-misc.R +++ b/R/utils-misc.R @@ -1,10 +1,12 @@ -assert_suggested <- function(package, pattern) { +assert_suggested <- function(package, pattern = NULL, fn = NULL) { if (!requireNamespace(package, quietly = TRUE)) { + context <- if (!is.null(pattern)) { + glue('in order to use the "{pattern}" pattern.') + } else { + glue("in order to use `{fn}()`.") + } abort(c( - glue( - "The suggested package {{{package}}} must be installed ", - 'in order to use the "{pattern}" pattern.' - ), + glue("The suggested package {{{package}}} must be installed {context}"), i = glue('Install with the command `install.packages("{package}")`') )) } diff --git a/_pkgdown.yml b/_pkgdown.yml index a9f3936..3ec5eae 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -25,6 +25,7 @@ reference: - clippingPathGrob - guess_has_R4.1_features - mean_col + - mix_col - reset_image_cache - star_scale - update_alpha diff --git a/man/mean_col.Rd b/man/mean_col.Rd index 6d00e8b..23644bb 100644 --- a/man/mean_col.Rd +++ b/man/mean_col.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/mean_col.R +% Please edit documentation in R/utils-color.R \name{mean_col} \alias{mean_col} \title{Compute average color} @@ -25,3 +25,6 @@ by using the quadratic mean of the colors' RGBA values. mean_col(c("black", "white")) mean_col("red", "blue") } +\seealso{ +\code{\link[=mix_col]{mix_col()}} for subtractive (pigment) mixing via Munsell color space. +} diff --git a/man/mix_col.Rd b/man/mix_col.Rd new file mode 100644 index 0000000..7a6ab1b --- /dev/null +++ b/man/mix_col.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-color.R +\name{mix_col} +\alias{mix_col} +\title{Mix colors via Munsell color space} +\usage{ +mix_col(..., w = NULL, mixingMethod = "adaptive") +} +\arguments{ +\item{...}{Colors to mix. Can be individual color strings or character vectors; +all are combined into a single vector.} + +\item{w}{A numeric vector of weights or proportions the same length as the combined color vector. +Defaults to equal weights.} + +\item{mixingMethod}{Mixing method passed to \code{\link[aqp:mixMunsell]{aqp::mixMunsell()}}.} +} +\value{ +A single R color string in hex notation. +} +\description{ +\code{mix_col()} simulates subtractive (pigment) color mixing by converting +input colors to Munsell notation via \code{\link[aqp:col2Munsell]{aqp::col2Munsell()}}, mixing them with +\code{\link[aqp:mixMunsell]{aqp::mixMunsell()}}, and converting the result back to an R color string +via \code{\link[aqp:parseMunsell]{aqp::parseMunsell()}}. +} +\examples{ +if (requireNamespace("aqp", quietly = TRUE)) { + mix_col("red", "blue") + mix_col(c("red", "blue")) +} +if (requireNamespace("aqp", quietly = TRUE)) { + mix_col("red", "yellow", "blue", w = c(2, 1, 1)) +} +} +\seealso{ +\code{\link[=mean_col]{mean_col()}} for a simpler quadratic-mean RGB approach (no extra packages required). +} diff --git a/tests/testthat/test_utils.R b/tests/testthat/test_utils.R index b1bfce8..e614f56 100644 --- a/tests/testthat/test_utils.R +++ b/tests/testthat/test_utils.R @@ -81,9 +81,20 @@ test_that("`assert_patterns_unique()` works as expected", { ) }) +test_that("`mix_col()` works as expected", { + skip_if_not_installed("aqp") + expect_equal(mix_col(c("red", "blue")), "#C2008FFF") + expect_equal(mix_col(c("yellow", "green")), "#ACFF00FF") + expect_equal(mix_col(c("red", "yellow", "blue"), w = c(2, 1, 1)), "#F46666FF") +}) + test_that("`assert_suggested()` works as expected", { expect_error( - assert_suggested("doesnotexist", "blueberry"), - "The suggested package \\{doesnotexist\\} must be installed" + assert_suggested("doesnotexist", pattern = "blueberry"), + "The suggested package \\{doesnotexist\\} must be installed in order to use the \"blueberry\" pattern" + ) + expect_error( + assert_suggested("doesnotexist", fn = "blueberry"), + "The suggested package \\{doesnotexist\\} must be installed in order to use `blueberry\\(\\)`" ) }) From 57c6bcc6b13c300e90c8c7ae0aa70883ca8bcaf9 Mon Sep 17 00:00:00 2001 From: Trevor L Davis Date: Tue, 9 Jun 2026 13:33:06 -0700 Subject: [PATCH 2/3] fix: Stagger support and bug fixes in 'line' pattern - Add `stagger` parameter to `grid.pattern_line()`: shifts alternate lines by half the dash period so dashes interleave (heraldic convention). Co-Authored-By: Claude Sonnet 4.6 --- R/pattern-both-line.R | 144 +++++++++++++++++++++++++++++++++------ R/utils-params.R | 1 + man/grid.pattern_line.Rd | 12 ++-- 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/R/pattern-both-line.R b/R/pattern-both-line.R index ee57f65..5a5054a 100644 --- a/R/pattern-both-line.R +++ b/R/pattern-both-line.R @@ -9,19 +9,17 @@ #' @inheritParams grid.pattern_circle #' @inheritParams alphaMaskGrob #' @param lineend Line end style, one of `"round"` (default), `"butt"`, or `"square"`. +#' @param stagger If `TRUE`, alternate lines are shifted by half the dash period so that +#' dashes of adjacent lines interleave. +#' Computed from `linetype` and `linewidth` per `?par`. Default `FALSE`. #' @return A grid grob object invisibly. If `draw` is `TRUE` then also draws to the graphic device as a side effect. #' @examples #' x_hex <- 0.5 + 0.5 * cos(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6)) #' y_hex <- 0.5 + 0.5 * sin(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6)) #' if (capabilities("png") || guess_has_R4.1_features("masks")) { #' grid::grid.newpage() -#' grid.pattern_line(x_hex, y_hex, colour = "black", angle = 0, spacing = 0.1) -#' } -#' -#' if (capabilities("png") || guess_has_R4.1_features("masks")) { -#' grid::grid.newpage() #' grid.pattern_line(x_hex, y_hex, colour = "black", linetype = "dotdash", -#' angle = 45, spacing = 0.1) +#' angle = 45, spacing = 0.1, stagger = TRUE) #' } #' #' # more intricate dashed lines are possible with hex strings @@ -47,6 +45,7 @@ grid.pattern_line <- function( linetype = gp$lty %||% 1, linewidth = size %||% gp$lwd %||% 1, size = NULL, + stagger = FALSE, use_R4.1_masks = getOption( "ggpattern_use_R4.1_masks", getOption("ggpattern_use_R4.1_features") @@ -77,6 +76,7 @@ grid.pattern_line <- function( linetype = linetype, linewidth = linewidth, lineend = lineend, + stagger = stagger, use_R4.1_masks = use_R4.1_masks, png_device = png_device, res = res, @@ -88,11 +88,80 @@ grid.pattern_line <- function( ) } -create_pattern_line <- function(params, boundary_df, aspect_ratio, legend = FALSE) { - default.units <- "bigpts" - boundary_df <- convert_polygon_df_units(boundary_df, default.units) - params <- convert_params_units(params, default.units) - vpm <- get_vp_measurements(default.units) +# blank or solid lines return NULL +lty_pattern_str <- function(lty) { + if (is.numeric(lty)) { + lty <- c("blank", "solid", "dashed", "dotted", "dotdash", "longdash", "twodash")[lty + 1L] + } + switch( + lty, + blank = , + solid = NULL, + dashed = "44", + dotted = "13", + dotdash = "1343", + longdash = "73", + twodash = "2262", + lty + ) +} + +# blank or solid lines return 0L +lty_period_sum <- function(lty) { + pat <- lty_pattern_str(lty) + if (is.null(pat)) { + return(0L) + } + sum(strtoi(strsplit(pat, "")[[1L]], base = 16L)) +} + +# Expand one row of a staggered pattern into explicit solid on-segments. +# phase_offset_lwd: phase in lwd units (may be fractional, e.g. 5.5 for dotdash half-period). +# Returns list(x0, x1, y) — equal-length vectors. +expand_lty_row <- function(lty, lwd, x_min, x_max, y_row, phase_offset_lwd) { + pat <- lty_pattern_str(lty) + if (is.null(pat)) { + if (lty_period_sum(lty) == 0L) { + return(list(x0 = x_min, x1 = x_max, y = y_row)) + } + return(list(x0 = numeric(0L), x1 = numeric(0L), y = numeric(0L))) + } + units_lwd <- strtoi(strsplit(pat, "")[[1L]], base = 16L) + n <- length(units_lwd) + period_lwd <- sum(units_lwd) + phase <- phase_offset_lwd %% period_lwd + cumul_lwd <- c(0, cumsum(units_lwd)) + seg_idx <- findInterval(phase, cumul_lwd[-length(cumul_lwd)]) + offset_in_seg <- phase - cumul_lwd[seg_idx] + x0s <- numeric(0L) + x1s <- numeric(0L) + x_cur <- x_min + first <- TRUE + i <- seg_idx + while (x_cur < x_max) { + rem_lwd <- if (first) { + first <- FALSE + units_lwd[i] - offset_in_seg + } else { + units_lwd[i] + } + if (rem_lwd > 0) { + x_end <- min(x_cur + rem_lwd * lwd, x_max) + if (i %% 2L == 1L) { + x0s <- c(x0s, x_cur) + x1s <- c(x1s, x_end) + } + x_cur <- x_end + } + i <- i %% n + 1L + } + list(x0 = x0s, x1 = x1s, y = rep(y_row, length(x0s))) +} + +# Build the segments grob for one set of lines (angle/lty/stagger already set in params). +# params must already be unit-converted (via convert_params_units). +# vpm must be from get_vp_measurements(default.units). +create_line_maskee <- function(params, vpm, default.units = "bigpts") { grid_xy <- get_xy_grid(params, vpm) col <- update_alpha(params$pattern_colour, params$pattern_alpha) @@ -103,17 +172,50 @@ create_pattern_line <- function(params, boundary_df, aspect_ratio, legend = FALS x0 <- rep(grid_xy$x_min, length(grid_xy$y)) x1 <- rep(grid_xy$x_max, length(grid_xy$y)) - xy0 <- rotate_xy(x0, grid_xy$y, params$pattern_angle, vpm$x, vpm$y) - xy1 <- rotate_xy(x1, grid_xy$y, params$pattern_angle, vpm$x, vpm$y) + seg_y <- grid_xy$y - maskee <- segmentsGrob( - xy0$x, - xy0$y, - xy1$x, - xy1$y, - default.units = default.units, - gp = gp - ) + # Stagger alternating lines by half the dash period so that marks of + # adjacent lines interleave (heraldic convention). Explicit solid + # sub-segments are used so the phase is device-independent (relying on a + # segment start outside the viewport breaks dash phase on some devices). + if (isTRUE(params$pattern_stagger)) { + half_period_lwd <- lty_period_sum(lty) / 2 + if (half_period_lwd > 0) { + segs <- lapply(seq_along(grid_xy$y), function(i) { + phase <- if (i %% 2L == 0L) half_period_lwd else 0 + expand_lty_row(lty, lwd, grid_xy$x_min, grid_xy$x_max, grid_xy$y[i], phase) + }) + x0_list <- lapply(segs, `[[`, "x0") + x0 <- unlist(x0_list, use.names = FALSE) + x1 <- unlist(lapply(segs, `[[`, "x1"), use.names = FALSE) + seg_y <- unlist(lapply(segs, `[[`, "y"), use.names = FALSE) + # Expand per-line gp values to per-sub-segment so recycling + # matches the non-stagger path (one gp value per line, not per dash). + n_sub <- lengths(x0_list) + line_idx <- rep(seq_along(grid_xy$y), n_sub) + recycle_to_lines <- function(v) v[(line_idx - 1L) %% length(v) + 1L] + gp <- gpar( + col = recycle_to_lines(col), + lwd = recycle_to_lines(lwd), + lineend = recycle_to_lines(lineend), + lty = "solid" + ) + } + } + + xy0 <- rotate_xy(x0, seg_y, params$pattern_angle, vpm$x, vpm$y) + xy1 <- rotate_xy(x1, seg_y, params$pattern_angle, vpm$x, vpm$y) + + segmentsGrob(xy0$x, xy0$y, xy1$x, xy1$y, default.units = default.units, gp = gp) +} + +create_pattern_line <- function(params, boundary_df, aspect_ratio, legend = FALSE) { + default.units <- "bigpts" + boundary_df <- convert_polygon_df_units(boundary_df, default.units) + params <- convert_params_units(params, default.units) + vpm <- get_vp_measurements(default.units) + + maskee <- create_line_maskee(params, vpm, default.units) masker <- convert_polygon_df_to_polygon_grob( boundary_df, default.units = default.units, diff --git a/R/utils-params.R b/R/utils-params.R index 4d00459..81b5900 100644 --- a/R/utils-params.R +++ b/R/utils-params.R @@ -45,6 +45,7 @@ get_params <- function(..., pattern = "none", prefix = "pattern_", gp = gpar()) l$pattern_type <- default_pattern_type(pattern) } l$pattern_units <- l$pattern_units %||% "snpc" + l$pattern_stagger <- l$pattern_stagger %||% FALSE l$pattern_xoffset <- l$pattern_xoffset %||% 0 l$pattern_yoffset <- l$pattern_yoffset %||% 0 diff --git a/man/grid.pattern_line.Rd b/man/grid.pattern_line.Rd index dac39b0..07d395b 100644 --- a/man/grid.pattern_line.Rd +++ b/man/grid.pattern_line.Rd @@ -20,6 +20,7 @@ grid.pattern_line( linetype = gp$lty \%||\% 1, linewidth = size \%||\% gp$lwd \%||\% 1, size = NULL, + stagger = FALSE, use_R4.1_masks = getOption("ggpattern_use_R4.1_masks", getOption("ggpattern_use_R4.1_features")), png_device = NULL, @@ -63,6 +64,10 @@ All locations within the same \code{id} belong to the same boundary.} \item{size}{For backwards compatibility can be used to set \code{linewidth}.} +\item{stagger}{If \code{TRUE}, alternate lines are shifted by half the dash period so that +dashes of adjacent lines interleave. +Computed from \code{linetype} and \code{linewidth} per \code{?par}. Default \code{FALSE}.} + \item{use_R4.1_masks}{If \code{TRUE} use the grid mask feature introduced in R v4.1.0. If \code{FALSE} do a \code{rasterGrob} approximation. If \code{NULL} try to guess an appropriate choice. @@ -104,15 +109,10 @@ enabling all of R's built-in \code{linetype} values (including \code{"dotdash"}, \examples{ x_hex <- 0.5 + 0.5 * cos(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6)) y_hex <- 0.5 + 0.5 * sin(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6)) -if (capabilities("png") || guess_has_R4.1_features("masks")) { - grid::grid.newpage() - grid.pattern_line(x_hex, y_hex, colour = "black", angle = 0, spacing = 0.1) -} - if (capabilities("png") || guess_has_R4.1_features("masks")) { grid::grid.newpage() grid.pattern_line(x_hex, y_hex, colour = "black", linetype = "dotdash", - angle = 45, spacing = 0.1) + angle = 45, spacing = 0.1, stagger = TRUE) } # more intricate dashed lines are possible with hex strings From 28522ca8fbe9ffbaf2772cd9ea1bbcba512276ac Mon Sep 17 00:00:00 2001 From: "Trevor L. Davis" Date: Fri, 12 Jun 2026 08:45:57 -0700 Subject: [PATCH 3/3] feat: New 'hatch' pattern with `grid.pattern_hatch()`, `names_hatch()`, and heraldic hatching vignette * Adds a new heraldic color hatching pattern supporting four subtypes: - combinatorial - fox-davies - goodman - unicode Co-Authored-By: Claude Sonnet 4.6 --- DESCRIPTION | 1 + NAMESPACE | 1 + NEWS.md | 8 +- R/grid-pattern.R | 4 + R/pattern-both-hatch.R | 639 ++++++++++++++++++++++ R/utils-params.R | 1 + README.Rmd | 1 + README.md | 1 + man/figures/README-piecepackr-1.png | Bin 58523 -> 58269 bytes man/grid.pattern.Rd | 2 + man/grid.pattern_hatch.Rd | 146 +++++ tests/figs/array/hatch_azure.png | Bin 0 -> 535 bytes tests/figs/array/hatch_cendree.png | Bin 0 -> 1042 bytes tests/figs/array/hatch_copper.png | Bin 0 -> 877 bytes tests/figs/array/hatch_gules.png | Bin 0 -> 715 bytes tests/figs/array/hatch_olive.png | Bin 0 -> 821 bytes tests/figs/array/hatch_or.png | Bin 0 -> 552 bytes tests/figs/array/hatch_proper.png | Bin 0 -> 2063 bytes tests/figs/array/hatch_sable.png | Bin 0 -> 883 bytes tests/figs/array/hatch_sanguine.png | Bin 0 -> 2572 bytes tests/figs/array/hatch_steel.png | Bin 0 -> 799 bytes tests/figs/array/hatch_unicode_grey.png | Bin 0 -> 976 bytes tests/figs/array/hatch_unicode_orange.png | Bin 0 -> 1523 bytes tests/figs/array/line_stagger.png | Bin 0 -> 948 bytes tests/testthat/test_array.R | 85 +++ tests/testthat/test_utils.R | 103 ++++ vignettes/hatching.Rmd | 553 +++++++++++++++++++ 27 files changed, 1542 insertions(+), 3 deletions(-) create mode 100644 R/pattern-both-hatch.R create mode 100644 man/grid.pattern_hatch.Rd create mode 100644 tests/figs/array/hatch_azure.png create mode 100644 tests/figs/array/hatch_cendree.png create mode 100644 tests/figs/array/hatch_copper.png create mode 100644 tests/figs/array/hatch_gules.png create mode 100644 tests/figs/array/hatch_olive.png create mode 100644 tests/figs/array/hatch_or.png create mode 100644 tests/figs/array/hatch_proper.png create mode 100644 tests/figs/array/hatch_sable.png create mode 100644 tests/figs/array/hatch_sanguine.png create mode 100644 tests/figs/array/hatch_steel.png create mode 100644 tests/figs/array/hatch_unicode_grey.png create mode 100644 tests/figs/array/hatch_unicode_orange.png create mode 100644 tests/figs/array/line_stagger.png create mode 100644 vignettes/hatching.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index c3c40c8..eeac94b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,6 +37,7 @@ Suggests: scales, svglite (>= 2.1.0), testthat, + Unicode, vdiffr (>= 1.0.6) VignetteBuilder: knitr, rmarkdown Config/roxygen2/version: 8.0.0 diff --git a/NAMESPACE b/NAMESPACE index 745de9d..20f163e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,6 +19,7 @@ export(grid.pattern_circle) export(grid.pattern_crosshatch) export(grid.pattern_fill) export(grid.pattern_gradient) +export(grid.pattern_hatch) export(grid.pattern_image) export(grid.pattern_line) export(grid.pattern_magick) diff --git a/NEWS.md b/NEWS.md index e82b85f..cccb174 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,9 +4,7 @@ gridpattern v1.4.0 (development) New Features ------------ -* `mix_col()` is a new utility function that mixes colors via Munsell color - space using `aqp::mixMunsell()`, simulating subtractive (pigment) mixing. - Requires the suggested package `{aqp}`. +* New "hatch" pattern with corresponding `grid.pattern_hatch()` and `names_hatch()` (#97). * New "line" pattern with corresponding `grid.pattern_line()`. Unlike the "stripe" pattern which fills bands with solid colour, @@ -14,6 +12,10 @@ New Features enabling all of R's built-in `linetype` values (including `"dotdash"`, `"twodash"`, and custom line types specified as hex strings per `?par`). +* `mix_col()` is a new utility function that mixes colors via Munsell color + space using `aqp::mixMunsell()`. + Requires the suggested package `{aqp}`. + Bug fixes and minor improvements -------------------------------- diff --git a/R/grid-pattern.R b/R/grid-pattern.R index ae360f6..d65df02 100644 --- a/R/grid-pattern.R +++ b/R/grid-pattern.R @@ -17,6 +17,8 @@ #' See [grid.pattern_crosshatch()] for more information.} #' \item{gradient}{Gradient array/geometry patterns. #' See [grid.pattern_gradient()] for more information.} +#' \item{hatch}{Heraldic hatching patterns. +#' See [grid.pattern_hatch()] for more information.} #' \item{image}{Image array patterns. #' See [grid.pattern_image()] for more information.} #' \item{line}{Line geometry patterns. @@ -158,6 +160,7 @@ names_pattern <- c( "crosshatch", "fill", "gradient", + "hatch", "image", "line", "magick", @@ -251,6 +254,7 @@ get_pattern_fn <- function(pattern) { crosshatch = create_pattern_crosshatch_via_sf, fill = create_pattern_fill, gradient = create_pattern_gradient, + hatch = create_pattern_hatch, line = create_pattern_line, none = create_pattern_none, pch = create_pattern_pch, diff --git a/R/pattern-both-hatch.R b/R/pattern-both-hatch.R new file mode 100644 index 0000000..bd86556 --- /dev/null +++ b/R/pattern-both-hatch.R @@ -0,0 +1,639 @@ +#' Heraldic color hatching patterned grobs +#' +#' `grid.pattern_hatch()` draws a heraldic color hatching patterns onto the graphic device. +#' `names_hatch()` returns a character vector of supported `type` values. +#' +#' @inheritParams grid.pattern_line +#' @param type A tincture or color name. `names_hatch()` lists supported values. +#' Both traditional tincture names (e.g. `"gules"`) and modern color equivalents +#' (e.g. `"red"`) are accepted. Matching is case-insensitive and ignores hyphens +#' and spaces. +#' @param subtype A string with one of +#' +#' * `"combinatorial"` (default): an extension of the seven standard Petra Sancta hatchings with systematically derived mixed-color hatchings. +#' * `"fox-davies"`: the hatchings in Fox-Davies' *A Complete Guide to Heraldry*. +#' * `"goodman"`: the hatchings in David Goodman's *Heraldic Tincture* reference. +#' * `"unicode"`: the hatchings used in Unicode character charts. +#' +#' The string is case-insensitive and hyphens and spaces are ignored. +#' @return A grid grob object invisibly. If `draw` is `TRUE` then also draws to the graphic device as a side effect. +#' @examples +#' x_hex <- 0.5 + 0.5 * cos(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6)) +#' y_hex <- 0.5 + 0.5 * sin(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6)) +#' if (capabilities("png") || guess_has_R4.1_features("masks")) { +#' grid::grid.newpage() +#' grid.pattern_hatch(x_hex, y_hex, type = "azure", colour = "blue") +#' } +#' if (capabilities("png") || guess_has_R4.1_features("masks")) { +#' grid::grid.newpage() +#' grid.pattern_hatch(x_hex, y_hex, type = "cendree", colour = "grey", spacing = 0.1) +#' } +#' print(names_hatch()) +#' print(names_hatch("fox-davies")) +#' print(names_hatch("goodman")) +#' print(names_hatch("unicode")) +#' @seealso [grid.pattern_line()] for single-direction lines, +#' [grid.pattern_crosshatch()] for perpendicular lines. +#' `vignette("hatching", package = "gridpattern")` for a visual overview of all subtypes. +#' for Fox-Davies' +#' _A Complete Guide to Heraldry_. +#' for +#' David Goodman's Heraldic Tincture reference. +#' @export +grid.pattern_hatch <- function( + x = c(0, 0, 1, 1), + y = c(1, 0, 0, 1), + id = 1L, + ..., + type = "gules", + subtype = "combinatorial", + colour = gp$col %||% "grey20", + spacing = 0.05, + xoffset = 0, + yoffset = 0, + units = "snpc", + alpha = gp$alpha %||% NA_real_, + lineend = gp$lineend %||% "round", + linewidth = size %||% gp$lwd %||% 1, + size = NULL, + use_R4.1_masks = getOption( + "ggpattern_use_R4.1_masks", + getOption("ggpattern_use_R4.1_features") + ), + png_device = NULL, + res = getOption("ggpattern_res", 72), + default.units = "npc", + name = NULL, + gp = gpar(), + draw = TRUE, + vp = NULL +) { + if (missing(colour) && hasName(l <- list(...), "color")) { + colour <- l$color + } + grid.pattern( + "hatch", + x, + y, + id, + colour = colour, + type = type, + subtype = subtype, + spacing = spacing, + xoffset = xoffset, + yoffset = yoffset, + units = units, + alpha = alpha, + lineend = lineend, + linewidth = linewidth, + use_R4.1_masks = use_R4.1_masks, + png_device = png_device, + res = res, + default.units = default.units, + name = name, + gp = gp, + draw = draw, + vp = vp + ) +} + +#' @rdname grid.pattern_hatch +#' @param accent If `TRUE`, return tincture names using their traditional +#' accented spellings where applicable (e.g. `"tenn\u00e9"`, `"brun\u00e2tre"`). +#' Defaults to `FALSE`. +#' @export +names_hatch <- function( + subtype = c("combinatorial", "fox-davies", "goodman", "unicode"), + accent = FALSE +) { + subtype <- tolower(subtype) + subtype <- match.arg(subtype) + nms <- if (subtype == "fox-davies") { + sort(names(HATCH_FOX_DAVIES)) + } else if (subtype == "unicode") { + sort(unname(HATCH_COLORS[names(HATCH_UNICODE)])) + } else if (subtype == "goodman") { + sort(names(HATCH_GOODMAN)) + } else { + sort(unname(HATCH_COLORS[names(HATCH_COMBINATORIAL)])) + } + if (accent) { + accented <- HATCH_ACCENTS[nms] + nms[!is.na(accented)] <- accented[!is.na(accented)] + } + nms +} + +# Accented spellings for tincture names that have them. +HATCH_ACCENTS <- c( + `bleu celeste` = "bleu c\u00e9leste", + cendree = "cendr\u00e9e", + tenne = "tenn\u00e9", + brunatre = "brun\u00e2tre" +) + +# Internal: hatching specs per subtype. +# Each entry is one of: +# list(special = "plain") -- no pattern (argent) +# list(special = "solid") -- solid colour fill (unicode sable) +# list(angles, linetypes) -- one or two sets of stroked lines + +# Combinatorial Petra Sancta: systematically extends the seven standard tinctures. +# Three derivation rules: +# white + color → dashed lines at that color's angle (lightened tints) +# yellow + color → dotdash lines at that color's angle (Munsell YR and GY secondaries) +# color + color → crossing solid lines at both angles (Munsell BG, PB, RP secondaries + others) +HATCH_COMBINATORIAL <- list( + # === Base seven tinctures (standard Petra Sancta) === + argent = list(special = "plain"), + or = list(special = "circle"), + azure = list(angles = 0, linetypes = "solid"), + gules = list(angles = 90, linetypes = "solid"), + sable = list(angles = c(0, 90), linetypes = c("solid", "solid")), + vert = list(angles = 135, linetypes = "solid"), + purpure = list(angles = 45, linetypes = "solid"), + # === White + color: dashed lines === + `bleu celeste` = list(angles = 0, linetypes = "dashed"), # argent + azure + carnation = list(angles = 90, linetypes = "dashed"), # argent + gules + cendree = list(special = "cendree"), # argent + sable + mint = list(angles = 135, linetypes = "dashed"), # argent + vert (new) + lavender = list(angles = 45, linetypes = "dashed"), # argent + purpure (new) + # === Yellow + color: dotdash lines === + orange = list(angles = 90, linetypes = "dotdash"), # or + gules (YR) + lime = list(angles = 135, linetypes = "dotdash"), # or + vert (GY) + olive = list(special = "olive"), # or + sable: alternating circles and plus signs + rose = list(angles = 45, linetypes = "dotdash"), # or + purpure (YP) + # === Color × color: crossing lines === + brunatre = list( + angles = c(90, 0, 135), + linetypes = c("solid", "solid", "solid"), + spacings = c(1, 1, 1 / sqrt(2)) + ), # gules × azure × vert + eisenfarbe = list(angles = c(45, 135), linetypes = c("solid", "solid")), # purpure × vert + sanguine = list(angles = c(90, 45), linetypes = c("solid", "solid")), # gules × purpure (RP) + tenne = list(angles = c(90, 135), linetypes = c("solid", "solid")), # gules × vert + teal = list(angles = c(0, 135), linetypes = c("solid", "solid")), # azure × vert (BG, new) + violet = list(angles = c(0, 45), linetypes = c("solid", "solid")) # azure × purpure (PB, new) +) + +# Fox-Davies is the standard Petra Sancta tinctures plus the German heraldry extensions, +# all of which are defined in HATCH_COMBINATORIAL. +HATCH_FOX_DAVIES <- c( + HATCH_COMBINATORIAL[c( + "argent", + "or", + "azure", + "gules", + "sable", + "vert", + "purpure", + "eisenfarbe", + "sanguine", + "tenne", + "brunatre", + "carnation", + "cendree", + "orange", + "bleu celeste" + )], + list(proper = list(special = "proper")) +) + +# Goodman system (david.goodman.graphics v2.0 2024-02-14). +# Shares most specs with fox-davies; key differences: +# murrey = eisenfarbe spec (45° + 135°) — not fox-davies sanguine +# sanguine = teal spec (0° + 135°) — different from fox-davies sanguine (90° + 45°) +# rose = carnation spec (90° dashed) — rose and carnation share one hatching +# Rare metals (steel, copper, bronze, lead) are unique to this system. +HATCH_GOODMAN <- c( + HATCH_COMBINATORIAL[c( + "argent", + "or", + "azure", + "gules", + "sable", + "vert", + "purpure", + "cendree", + "brunatre", + "bleu celeste", + "carnation", + "orange", + "tenne" + )], + list( + murrey = HATCH_COMBINATORIAL[["eisenfarbe"]], # 45° + 135° (dark red-purple) + sanguine = HATCH_COMBINATORIAL[["teal"]], # 0° + 135° (blood red) + rose = HATCH_COMBINATORIAL[["carnation"]], # 90° dashed (rose/carnation share hatching) + steel = list(special = "steel"), # + signs in square grid + copper = list(special = "c_hex"), # c in hex grid + bronze = list(special = "c_hex"), # c in hex grid + lead = list(special = "c_hex") # c in hex grid + ) +) + +HATCH_UNICODE <- c( + HATCH_COMBINATORIAL[c("argent", "or", "azure", "gules", "vert", "purpure", "tenne")], + list( + `bleu celeste` = list(angles = 0, linetypes = "dotdash"), + carnation = HATCH_COMBINATORIAL[["rose"]], # unicode "pink" heart uses 45° dotdash + cendree = list(special = "checker"), + orange = list(angles = c(0, 45), linetypes = c("solid", "solid")), + sable = list(special = "solid") + ) +) + +# Modern color equivalents for tincture names. +# For the unicode subtype, values match the canonical Unicode emoji color names. +HATCH_COLORS <- c( + `bleu celeste` = "light blue", + argent = "white", + azure = "blue", + bronze = "bronze", + brunatre = "umbre", + carnation = "pink", + cendree = "grey", + copper = "copper", + eisenfarbe = "slate", + gules = "red", + lavender = "lavender", + lead = "lead grey", + lime = "lime green", + mint = "mint green", + murrey = "mulberry", + olive = "olive", + or = "yellow", + orange = "orange", + proper = "color of nature", + purpure = "purple", + rose = "rose", + sable = "black", + sanguine = "magenta", + steel = "steel grey", + teal = "teal", + tenne = "brown", + vert = "green", + violet = "violet" +) + + +# Map from normalized user input to canonical tincture name. +# Normalization: tolower() + remove hyphens and spaces. +HATCH_NAME_MAP <- c( + argent = "argent", + white = "argent", + silver = "argent", + azure = "azure", + blue = "azure", + cobalt = "azure", + bleuceleste = "bleu celeste", + "bleuc\u00e9leste" = "bleu celeste", + skyblue = "bleu celeste", + lightblue = "bleu celeste", + celeste = "bleu celeste", + "c\u00e9leste" = "bleu celeste", + ciel = "bleu celeste", + bronze = "bronze", + brunatre = "brunatre", + "brun\u00e2tre" = "brunatre", + darkbrown = "brunatre", + umbre = "brunatre", + umber = "brunatre", + carnation = "carnation", + flesh = "carnation", + pink = "carnation", + cendree = "cendree", + "cendr\u00e9e" = "cendree", + grey = "cendree", + gray = "cendree", + ash = "cendree", + ashgray = "cendree", + ashgrey = "cendree", + copper = "copper", + slate = "eisenfarbe", + irongrey = "eisenfarbe", + irongray = "eisenfarbe", + iron = "eisenfarbe", + eisenfarbe = "eisenfarbe", + gules = "gules", + red = "gules", + crimson = "gules", + lavender = "lavender", + lilac = "lavender", + lead = "lead", + leadgrey = "lead", + leadgray = "lead", + lime = "lime", + chartreuse = "lime", + limegreen = "lime", + yellowgreen = "lime", + mint = "mint", + mintgreen = "mint", + seafoam = "mint", + murrey = "murrey", + mulberry = "murrey", + olive = "olive", + or = "or", + yellow = "or", + gold = "or", + orange = "orange", + colorofnature = "proper", + proper = "proper", + natural = "proper", + purpure = "purpure", + purple = "purpure", + rose = "rose", + sable = "sable", + black = "sable", + sanguine = "sanguine", + bloodred = "sanguine", + magenta = "sanguine", + steel = "steel", + steelgrey = "steel", + steelgray = "steel", + teal = "teal", + cyan = "teal", + aqua = "teal", + aquamarine = "teal", + turquoise = "teal", + bluegreen = "teal", + tenne = "tenne", + "tenn\u00e9" = "tenne", + tawny = "tenne", + brown = "tenne", + vert = "vert", + green = "vert", + violet = "violet", + bluepurple = "violet", + violetblue = "violet" +) + +create_pattern_hatch <- function(params, boundary_df, aspect_ratio, legend = FALSE) { + type_norm <- gsub("[- ]", "", tolower(params$pattern_type)) + subtype_norm <- gsub("[- ]", "", tolower(params$pattern_subtype %||% "combinatorial")) + is_unicode <- subtype_norm == "unicode" + is_fox_davies <- subtype_norm == "foxdavies" + is_goodman <- subtype_norm == "goodman" + canonical <- HATCH_NAME_MAP[type_norm] + if (is.na(canonical)) { + stop("Unknown hatching type '", params$pattern_type, "'.") + } + + spec <- if (is_unicode) { + HATCH_UNICODE[[canonical]] + } else if (is_fox_davies) { + HATCH_FOX_DAVIES[[canonical]] + } else if (is_goodman) { + HATCH_GOODMAN[[canonical]] + } else { + HATCH_COMBINATORIAL[[canonical]] + } + + if (is.null(spec)) { + stop( + "Hatching type '", + params$pattern_type, + "' is not supported ", + "by the '", + params$pattern_subtype %||% "combinatorial", + "' subtype." + ) + } + + if (!is.null(spec$special)) { + if (spec$special == "plain") { + return(grid::nullGrob()) + } + if (spec$special == "solid") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + default.units <- "bigpts" + bd <- convert_polygon_df_units(boundary_df, default.units) + return(convert_polygon_df_to_polygon_grob( + bd, + default.units = default.units, + gp = gpar(fill = col, col = NA) + )) + } + if (spec$special == "circle") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + spacing_bigpts <- convertX( + unit(params$pattern_spacing, params$pattern_units), + "bigpts", + valueOnly = TRUE + ) + density <- params$pattern_linewidth * .pt / spacing_bigpts + return(patternGrob( + "circle", + x = boundary_df$x, + y = boundary_df$y, + id = boundary_df$id, + colour = NA, + fill = col, + spacing = params$pattern_spacing, + density = density, + grid = "hex_circle" + )) + } + if (spec$special == "olive") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + spacing_bigpts <- convertX( + unit(params$pattern_spacing, params$pattern_units), + "bigpts", + valueOnly = TRUE + ) + lwd_bigpts <- params$pattern_linewidth * .pt + density_dot <- lwd_bigpts / spacing_bigpts * 0.6 + density_plus <- min(lwd_bigpts / spacing_bigpts * 2, 0.9) + return(patternGrob( + "pch", + x = boundary_df$x, + y = boundary_df$y, + id = boundary_df$id, + colour = col, + fill = col, + shape = c(16, 3), + spacing = params$pattern_spacing, + density = c(density_dot, density_plus), + angle = 0, + type = "diagonal", + subtype = 2L, + grid = "square" + )) + } + if (spec$special == "steel") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + spacing_bigpts <- convertX( + unit(params$pattern_spacing, params$pattern_units), + "bigpts", + valueOnly = TRUE + ) + lwd_bigpts <- params$pattern_linewidth * .pt + density <- min(lwd_bigpts / spacing_bigpts * 2.5, 0.9) + return(patternGrob( + "pch", + x = boundary_df$x, + y = boundary_df$y, + id = boundary_df$id, + colour = col, + fill = col, + shape = 3, + spacing = params$pattern_spacing, + density = density, + angle = 0, + grid = "square" + )) + } + if (spec$special == "c_hex") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + return(patternGrob( + "text", + x = boundary_df$x, + y = boundary_df$y, + id = boundary_df$id, + colour = col, + shape = "c", + spacing = params$pattern_spacing, + angle = 0, + grid = "hex_circle" + )) + } + if (spec$special == "checker") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + return(patternGrob( + "polygon_tiling", + x = boundary_df$x, + y = boundary_df$y, + id = boundary_df$id, + type = "square", + spacing = params$pattern_spacing, + angle = 0, + colour = NA, + fill = c(col, "transparent") + )) + } + if (spec$special == "cendree") { + # Build segments directly at exact grid intersections (no lty approximation). + # At each (m, j) grid point, draw an H dash if (m+j) is even, V dash if odd. + # Dashes have length exactly S so centers are precisely at grid positions. + default.units <- "bigpts" + boundary_df_pts <- convert_polygon_df_units(boundary_df, default.units) + params_pts <- convert_params_units(params, default.units) + vpm <- get_vp_measurements(default.units) + col <- update_alpha(params_pts$pattern_colour, params_pts$pattern_alpha) + lwd <- params_pts$pattern_linewidth * .pt + gp_seg <- gpar( + col = col, + lwd = lwd, + lty = "solid", + lineend = params_pts$pattern_lineend + ) + S <- params_pts$pattern_spacing + xo <- params_pts$pattern_xoffset + yo <- params_pts$pattern_yoffset + half <- S / 2 + # Grid indices covering viewport + margin (same extent as get_xy_grid) + idx_seq <- seq_robust(from = 0, to = vpm$length, by = S) + idx <- round(c(rev(-idx_seq[-1L]), idx_seq) / S) # ..., -2, -1, 0, 1, 2, ... + x_grid <- xo + vpm$x + idx * S + y_grid <- yo + vpm$y + idx * S + ng <- length(idx) + m_mat <- matrix(idx, nrow = ng, ncol = ng, byrow = FALSE) + j_mat <- matrix(idx, nrow = ng, ncol = ng, byrow = TRUE) + x_mat <- matrix(x_grid, nrow = ng, ncol = ng, byrow = FALSE) + y_mat <- matrix(y_grid, nrow = ng, ncol = ng, byrow = TRUE) + is_H <- (m_mat + j_mat) %% 2L == 0L + x_H <- as.vector(x_mat[is_H]) + y_H <- as.vector(y_mat[is_H]) + x_V <- as.vector(x_mat[!is_H]) + y_V <- as.vector(y_mat[!is_H]) + maskee <- grid::grobTree( + segmentsGrob( + x_H - half, + y_H, + x_H + half, + y_H, + default.units = default.units, + gp = gp_seg + ), + segmentsGrob( + x_V, + y_V - half, + x_V, + y_V + half, + default.units = default.units, + gp = gp_seg + ) + ) + masker <- convert_polygon_df_to_polygon_grob( + boundary_df_pts, + default.units = default.units, + gp = gpar(fill = "white", col = NA, lwd = 0) + ) + return(alphaMaskGrob( + maskee, + masker, + use_R4.1_masks = params$pattern_use_R4.1_masks, + png_device = params$pattern_png_device, + res = params$pattern_res + )) + } + if (spec$special == "proper") { + col <- update_alpha(params$pattern_colour, params$pattern_alpha) + spacing_bigpts <- convertX( + unit(params$pattern_spacing, params$pattern_units), + "bigpts", + valueOnly = TRUE + ) + density <- params$pattern_linewidth * .pt / spacing_bigpts + return(patternGrob( + "wave", + x = boundary_df$x, + y = boundary_df$y, + id = boundary_df$id, + colour = NA, + fill = c(col, "transparent"), + angle = 135, + spacing = params$pattern_spacing, + density = density, + type = "triangle" + )) + } + } + + # Build all line-segment grobs and apply a single mask. Using separate + # alphaMaskGrobs in a grobTree breaks compositing (full-viewport rasters + # overwrite each other), so we combine the maskees first. + default.units <- "bigpts" + boundary_df_pts <- convert_polygon_df_units(boundary_df, default.units) + params_pts <- convert_params_units(params, default.units) + vpm <- get_vp_measurements(default.units) + + spacings <- spec$spacings %||% rep(1, length(spec$angles)) + maskees <- mapply( + function(angle, lty, spacing_mult) { + p <- params_pts + p$pattern_angle <- angle + p$pattern_linetype <- lty + p$pattern_stagger <- (lty != "solid") + p$pattern_spacing <- p$pattern_spacing * spacing_mult + create_line_maskee(p, vpm, default.units) + }, + spec$angles, + spec$linetypes, + spacings, + SIMPLIFY = FALSE + ) + maskee <- if (length(maskees) == 1L) maskees[[1L]] else do.call(grid::grobTree, maskees) + masker <- convert_polygon_df_to_polygon_grob( + boundary_df_pts, + default.units = default.units, + gp = gpar(fill = "white", col = NA, lwd = 0) + ) + alphaMaskGrob( + maskee, + masker, + use_R4.1_masks = params$pattern_use_R4.1_masks, + png_device = params$pattern_png_device, + res = params$pattern_res + ) +} diff --git a/R/utils-params.R b/R/utils-params.R index 81b5900..e2ad82f 100644 --- a/R/utils-params.R +++ b/R/utils-params.R @@ -132,6 +132,7 @@ default_pattern_type <- function(pattern) { pattern, ambient = "simplex", aRtsy = "strokes", + hatch = "gules", image = "fit", placeholder = "bear", polygon_tiling = "square", diff --git a/README.Rmd b/README.Rmd index 61c8eb7..271a818 100644 --- a/README.Rmd +++ b/README.Rmd @@ -34,6 +34,7 @@ as well as original "pch", "polygon_tiling", "regular_polygon", "rose", "text", 1. [crosshatch](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_crosshatch.html): crosshatch geometry patterns 1. [fill](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_fill.html): simple fill patterns 1. [gradient](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_gradient.html): gradient array/geometry patterns +1. [hatch](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_hatch.html): heraldic hatching patterns 1. [image](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_image.html): image array patterns 1. [line](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_line.html): line geometry patterns 1. [magick](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_magick.html): imagemagick array patterns diff --git a/README.md b/README.md index 5653878..9ab4c55 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ as well as original "pch", "polygon_tiling", "regular_polygon", "rose", "text", 1. [crosshatch](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_crosshatch.html): crosshatch geometry patterns 1. [fill](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_fill.html): simple fill patterns 1. [gradient](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_gradient.html): gradient array/geometry patterns +1. [hatch](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_hatch.html): heraldic hatching patterns 1. [image](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_image.html): image array patterns 1. [line](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_line.html): line geometry patterns 1. [magick](https://trevorldavis.com/R/gridpattern/dev/reference/grid.pattern_magick.html): imagemagick array patterns diff --git a/man/figures/README-piecepackr-1.png b/man/figures/README-piecepackr-1.png index c4d1b098536bc5b5eff2506eaf0303bdcae4eefc..5a8cf92a80cec6baffa527df53cca28cc0f1e12a 100644 GIT binary patch literal 58269 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 literal 58523 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 literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_gules.png b/tests/figs/array/hatch_gules.png new file mode 100644 index 0000000000000000000000000000000000000000..b1142365b4386d6367db6c2d11d0bad2d2cd47e8 GIT binary patch literal 715 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IW0X`wFK>FRgcmMzY=ez!EDgy&k zt*47)NX4AD*B$#F8wfZDmcOfiG4oz2gLfNOysP5Rm`fdJ#j6*;o_ex$sob2(H|I`37MGBv#+)H!0-R9cOMAm+Jcc*d<@ z<8!4MXW5%2Z|qpFU(H{t%(Lyc2ubT7?gz{2Y@4!w zCKRw8o4ZJ?enN9#e%uF}%Vn>&we;md#<_n-V=?c=a#%`KMb)_-48zd1%B5) z9{KNJ?YZj7i#gW076k8?JGhl2@6~nX9byN4|5mYF|5W(xGVh)2c}8}Pgg&ebxsLQ06(5dE&u=k literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_olive.png b/tests/figs/array/hatch_olive.png new file mode 100644 index 0000000000000000000000000000000000000000..d7f098107196a5452bb77a4811a63e082ba06054 GIT binary patch literal 821 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IXW&u7Su0UE}U*EvMz|hdp$;rvZ z#l_Xt)y>V#$H(XR@#7~>oH%*%;%<%-P=9fInjAxvKGAzQsxYqwmNGK{O znPOc3#n}AE)16zNM_=Bx{Cxa8rPrT*c=v6w*ZcqPsdM&xAXkL7K85Xj{i6H(>wXJH z&Oh#}ef{v=Y1fhs<{m6zyDaIG4rSP+ee15fm!P0o@l8WL|BEnB-?_=%*XDDuxj#Q? z`uaRe^Wx7>W_?X>I(V_N=;ANoyr^WkciXn`qq=Rzxd+9o_r5sJ&V2B}RX59z7yZ>M zCjb0=V9ze$J;(o4ro`RlyqEmX#%TRJmVBdnxf$WRGv`LXH8cLZZHqmINr$ua-&MZ0 zKDfF!?bEgUd~vz+=KQdoF#F)@vy+YYvkUP6O_Je@%L9oDn6Eu-w|(Y|DtVh1$YBKv zH2!6CZ>LYW{J8Sv;jGUo=e_yY)<3U&S-d*eY2NgGr`h?@f_KKb=FIl1ZpQPb%f-B} zd|CZ}!|^{iKVPaZylnNRcy{Ny?+2S^z^ zZ>c@0e7U>k!vaL)L9|S2x?X+py(d>({*=qYJonQ7-N7UuFEf4iFwgt90n8SdA?HHB zFS>7Rxg&4NWn*)TkH4iOm$9YS)hl}k7W@Hny6P@{e4n3Ec=^=Vg6&JcO28u+%J?t; zIsQv^SJU(UqN=W@^=r@0y;^#eH9mXYAMX0YbJx$fbgKxM{uw-7{an^LB{Ts5WG7F)c$mZHGKPR{(H5D z_uj6phe!eS{%KTJO1@>juWfs?{GYTvm%v&;X6EbvV3u#+ZNFi9+3y2y`*L4Sg;-Po zG<4Lpc@`*o+V%3WEsqZ@1^Eyh1QmBc z8elfOW{lUHWwuO~U5^!R@3AfC&;NN>|0M$1A(EH>RXlV2@Te}j{5LDY3%@42AK%i= ztuMFt?~`AGzYp?%@`78w_v{1zp3L|6p0@w}miPVG{UbT|3QUm#+<2L7#R>DfONHT% g|G&Q2U-pmq!~OS5y;uAa2Sz!Ar>mdKI;Vst0K|X`a{vGU literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_proper.png b/tests/figs/array/hatch_proper.png new file mode 100644 index 0000000000000000000000000000000000000000..5a6cacd639add905acdb7e8204c2d45f182de7fb GIT binary patch literal 2063 zcmV+q2=MobP)Vm<+&WcM)+uRBFA45@tVdWHrE%-{RN#{ ze#J#?OJX~L3vAQJJ%J1$03m-Dj)!(avWUww3ji#40FWW;L74IPHv*X{hSkUBz0A|! zM-do(Y$FV|)AHy?AGhPI7J<~q)cJe4zYu0s2;!Cr3QieUks2Q2^+m3~-`N(!R=B9Y zuf5GcVaxjaQG~sKyb)C2xxG#=prjV@B0Nh+`w44!@Q@DeF#_Z_jih>gL?vL^7qa9xXHLD0HUH%xAc7e{vv4FarShEXr~JP zetSvFq#*pg4Wl~R?C+l!cLJbw{JK3(bo5AnU#*Y3m}o~I>P@-7xWZLU!O5EWNE2ir zBex7#xyp9JM$GB0yDb5*G0T%oI3yVc5Yr-2gv;vV&Z)m@xex$C*qW?viv$rKF+C|6u2SREu-|vWmH*x97WhQ%oBlZ5i)9CeVp1C2?*p*G$v{d5Yi%=13vd^kLhTe zzpqp@L3km}Vru+LFL35^PI`1~q{e?*%lm7mqk2hNTJk^qMI)A_8 z_?@NNr8KuwuD{>JmxhY)CpVPr9W&0~*X!dRVI-ND64l2-t04&e!ZefchET@ezeec2 zr2*JV{{994AsmEjhJ_H!vq-Xe`y@#`W9 zkC@xZzC1@o1D0@Bi{_ER{=P8F)}nf(uD_2!b)OL1iPy*NIIBf47|1}7xIFaz#R>@k zXs-iX_Fak)X8rxIK*kcU5sdm6iCXF7cAV9w$ny7f)m%SdYNt$pUje2_Hj%$?59?gF zaLGIo*aN<1h4HBCw8QiWx0jWbPeE}Lcl02_16zbTe;4g&)d4?9a4-nAtywE*h7cAn z>W%tXt7yC~Ne24+n4obXj9$0wj;Vydzfm+mW`=Mne;Kf z5T-B+=;PE>Q41XMHIyLi=kF_WIY8L&FOI=cd|e0&Qx5t#=!YGEI?~@)B=g4mEDzPu ztUd;YE9`iGU!BYYi8YWr{(gDFG_0q;ugEqYHp)QmWQ#$qtB%jGK=qT)A#f*n=P?}#O tTxUFl?>-*FcOMVoyN`$P-N(-n{tq*d*ZP=|G_(K!002ovPDHLkV1k;$32FcU literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_sable.png b/tests/figs/array/hatch_sable.png new file mode 100644 index 0000000000000000000000000000000000000000..e579f6e5de77bee5b5028f4a828f64e76192b88a GIT binary patch literal 883 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IXJOMr-u0ZE7;3cK3AI2`ylDOtAX^vqw~1=>;BAro_g2K@B5DmlZu1R{2cn%6+g5*Ns`aG zdw1TPi_WX}2ry-T|9UdcS-0h2)qYpU*9{xaMaLHCHnD%buDnC+VDqonf-&6>9RGgh zSohSPEj{5*^p&IWCl0Pq*RQbOe2_){nU2KXDX%YWejs;!eThTCtRgdkicM>G-&TGg z{wJk<^71cr*`l8558O-X4RCxf9d}X>ee|nOS|_z_<~)BAHRykJJ#jwLOkE&9^L&g z%gk0mcKes#*MFUy_h!S#DSF?UE;DnsRUdrW$Z{B$A=RDbhqG^eX(+jK>3#fb_3BNB zj|K0#!Hr}z4ny9n-s4@Ld71fAq4)m!we#Pco(!{r1`%0{0rtikmJ|7ZU@^!4PF1=IN-%x+<>RZjzGv$9tKAAkJ z*nXSYwhJ!Nzf_JmXM>~2?ZZQTAJ**sqFal8if=Q%KRL%0M+BEW*lNGS?zZ=e(p~#q zuc}w`Zo5!c6un(k{j%_;)1?BJnGe1GyLr9+y-PnTH literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_sanguine.png b/tests/figs/array/hatch_sanguine.png new file mode 100644 index 0000000000000000000000000000000000000000..45b4c5f94bf0d886caa79109c48561773dc0d909 GIT binary patch literal 2572 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 literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_steel.png b/tests/figs/array/hatch_steel.png new file mode 100644 index 0000000000000000000000000000000000000000..afba2f70e0e88a596e89a98ce456cbdbf7f2b01a GIT binary patch literal 799 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IX5&=FTu0VR~)Twvx-hKP_?YnpH z-oJnU`Sa)h|NqN=crC!dz;wvd#WAE}&f6Pz^9~sZxFmiK{`4>Z4x3WmE#6JLrTGv4 z)T?do>zGn;%joFRt*7V2=M~p|Q~zQ0d^)Gyv9()o&xxNWvS*Ip@)S? z@@5M#i7m^)7UNyH)dMy)RdvZ#?&4 z&D@>8Y_5sUF+CMJ|NTqObF*SspD!2O>seQyUAOYH?m_LX?i+tijQdjd zY|q|V*DtxfZC?26@L_R;H^BzxUs+fB=2G(WH<$Rc9!u~3a^t5gNaw`ZsvmQ%Uz&W2 zoq6{c4PclcY=6gbZ$<3u8%vL$ySY^U$}#h@mm5D@H-%^QEA9UB!E*gl^{xDDWiJC! z0^qLTp1|GLHhTBZ-Ry0@qBkF8V>!$ED{`)7FCP}idHZL{^SrsV7#0Cg%PJO^txcXT zXPrL1E?yT#3KS)WuA>KTd4GQ5x`eHPdHHL)zpwzqXLoT<%LCi2{=Bcsdtbag^YNd|!>wPMZn3+4Z9o1S zB`v(^dyw+<`rf#s2fvE{0+H6m>n}Y}cH0>@^i5KFr_niy85}S Ib4q9e0JMgw0{{R3 literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_unicode_grey.png b/tests/figs/array/hatch_unicode_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..7e9d24d0d28dcfe2d6a98ca8ab2b0bef67006bee GIT binary patch literal 976 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IXYymzYu0Z<#|Nl#G&cDmRz})KT z;uumf=k4{2dD4bFE*HQ4|6kI$%=PuLXGKdCr^m+ct#Ucn*?7Q8PQI*fHFt^pfpYtX z_jL^M8?#+F|1W!N`{S?sKdIdAq=NYVWv0g^cXZ#`^vuHOfn3q+`j?N-X1=|!i^t%> zFTS;>8JO3-2Xa2x9$3DA#ij?>ie3w!uXkE@kTrf^^&?3K*4rnaRdP0Pm6>nh^JWa$ zFg-_R8vBX_^_ym1%qtS)Z<>0rou~{CJU@lCVcz$U(lZe(FDl({{^gWru-zYDUhSY? zvH$(EuelBL?&s_&y~*_9Y4F_bdi5edoNn}0etov|{*!ap_mr(?TJZe(D*f15jB&5` z6)fdh@Lc@XX>l*csp02W$2O8R$8JO94BtE1t zBphg9WS+;vz$0Oha9}cH17r6CP1m_Um+xMA!2l@Ps46PO#=v8-VTsdWh6YCVX(1UB z3ikz{<~$wo65|m zgY2X|%=1F7Ol0S&ut1m!^uvRbKrb^gv)M>8NNnJk7j$K2{jS+~+`9YV`>+_+i08`= zt}nmBbfiXX(wV(?g19bxs&upZndGnNa9W!~@po>pq{HcK7R|pAS|M}9S)=|`My?CU zjeEB~?nia(vH;zRg?sCso2Y3qZf@3g1Oopd_-VCyUI?R9T$G())KboW)fe}3)3 z;;auq|-pUEH%0*4jYSz2ntlGtPb>E^M-P*;k;sUSrolD%WeQf@!gktCSJDS3$ z>|Rhg@3-jF_385^&P+Kt_o-GNm)=W^MDBYqeS=@c<#==EysS%`pK)*fG;^=}^+l7z sL*je4`Cin{a_ZbI2hB?N|NddHI6r;mj8(__fmxHm)78&qol`;+08b64wEzGB literal 0 HcmV?d00001 diff --git a/tests/figs/array/hatch_unicode_orange.png b/tests/figs/array/hatch_unicode_orange.png new file mode 100644 index 0000000000000000000000000000000000000000..c3212ae1d7188cd8ae382b5c1eec8e4b25e29c10 GIT binary patch literal 1523 zcmZ{kYc!Mz7{>?0+%vJ9NiNA{T9@W+v5ZKCtc2QRkWm`Eg=n3$NF znwpuJnVXxFNF)mj3rkB&Yiny88yj0&TRS^DM@L68ne61`OTlCMG8*r>3T6W@eO1<^24- zTCLV-G+M28eSKYazHDPjKiKeCcxCibTJLb;KIs>y!+N@kwuCwk-jUWWz6?C}g; z)^11Y!_;RSPkT5z*Db2tNDxamgoYWwMBUYQR#)!I6dkE)nxVrLZ^?wq<2n3C3u8_J zjI3PLN_uz`KQgDN#Qux!>cA4q;{Y+?l{30&EYD!8I-6U~C>15bel?vBYw&{my1w1o z5%>m&zzh`KgH6DrG(E^wbp_g`N`O36ry#285ag@!f%d6fpg@&n5=A~Yd))7xSjM0T zE)7{}W4{xSRnxQ9EB=Nn!ltOz=c~(%q2eofGQ}J-P3uwqE(kgBOY6ZPVpZtl187U> zk!wqEHrne(|5AJ>5{gqHp+t&J!bpcvv@TgZB|HRKsjjTdcG_57LP zU+ip29nt9&!jy38mLUkJ!mk1F#GssrpC^b%X;W4Rts49kkQ6pzQ?Cmgsr@HfESe1zolJuxWXPU?!*Z)4&> zAG$SurYmE!kP1BYG>~Y@#E<0mr2azvHkK)Pif)OYy_t~%Q&zcXQak&18%!bN@=EGi zz9sE_1CI2JPfyHW>D{Z$dm%L4gaVT&i<=NDG;5NmL*I@;1<@1(-dJhoK3Eew&Sg}9 zB+6!nkj|@fd|dVtVMGXL+(R*Qz&fdi)!8ZVEc&|)lS8R zMNV-ySk^PT5WAYl7?XcXtN1F-_^Y%KhLz;AeCZE;pg@=JN1`Ps(9uS^tpA_tJdmA_ z%~G4#u)vZtI!|NIk>Xl5DjS?@dX{3I)bf-tun0_2t-X}}1=~rT(Z~{J2IqK*PdriobS_As<%)9Er|)2t(R zuHj;^d3)IRe+FO@n`LXt`{*&3DCt1uE=wRIf4T1bhu!Kne|JYX1KdiZLYr$zgV~YT z1c@`UNs*_w^>a4ImFXZVgZ05^`7#(o<2omFI4{$@`Q328SjfDP`C{?7k#4#gH~+>N zSJPj6r>JDXOnRX;H+BbGfIasSgSAmzJB{nF|DuU^jY>`L*?Ifm=tW_p$b;Xr?M{4c zTW`;Y+Xt77jo;2NQ$^V{GpH=mpz2cuLdhD=%oBEr-^-<7Dm?~z+3YGXpdU>#mP;I} xRS_&ob%7y|Z#ZJV&Ps7Amp*?rJ{K9khCnNxuw$xx|JwN8h~U7mfSUba#^1qo;V=LI literal 0 HcmV?d00001 diff --git a/tests/figs/array/line_stagger.png b/tests/figs/array/line_stagger.png new file mode 100644 index 0000000000000000000000000000000000000000..242973a64bf07aef623c0a1528c00e894bbb3c04 GIT binary patch literal 948 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IX2LgOTT!AzrBO?3vy7cN}9c=6`Vo40S@e);m{>({T}y?gim{re9eK79Q6@zbYIpFe;8^5x6d zuV24?`}Y0&_a8rg{QC9l_wV0-{`~p-_wT=d|Nj5~Z?j)AkAZ<{y{C&~NX4ADw<7&T z97P-+x(Nszz;Z){lWkL+Saqy?PBDe8!>sFW$C%gPnNxWm%rkS^U_(}iCME2 z%GsPrc8|_&wy9j9U$uedvapZ8f$hOB$c&gpm)G9$l#_4$sL}uZQ;GGRC+C;^*zL>o zr+Dc}-Tbo0{^o~&NDmgR5iUs zVq6!mnPM;3{&wqTn|uF%ue)6~@7>dv8Zk0?wL-_1SJns|dtSL`^77rbl6w!Hef0d@ z1+u{4wf7 z&5hrC<}W+{c-o7}CUM-Ccb4DX^5NsdZ`blKO)pmOnZB>=v3_^ro>}`kuAjYn_;}Om z=u0Sp1`kFVzlLMHn{QgiSn-L=e`7FTFO*~YruN*qj}O@st^PZ1@ScSpHV_A{wvLIq z`|;tc16Or3QNs=vn(N*bWR@OpU(Fo8eF<{d!sM5!@7Q&C=~Y?zb$l^*Ct-;Mpuz{M z|K5|FJ@@d^|NMFSvz9(Sta{*W>efd6S5cRiAg2MicVZS@YWMcucU$E^Yd`<=+MO@i z#p`xv#$P&m|E#<&R(JBU$oZx;uk&~Q+uohO|Jz&f)sI~LN>g_H24+77Pgg&ebxsLQ E0NXy`djJ3c literal 0 HcmV?d00001 diff --git a/tests/testthat/test_array.R b/tests/testthat/test_array.R index 865d996..88e9862 100644 --- a/tests/testthat/test_array.R +++ b/tests/testthat/test_array.R @@ -14,10 +14,13 @@ test_raster <- function(ref_png, fn, update = FALSE) { bool <- attr(diff, "distortion") < 0.01 if (!bool) { grDevices::dev.new() + grid::grid.text(ref_png, y = 0.95) grid::pushViewport(grid::viewport(x = 0.25, width = 0.5)) + grid::grid.text("ref", y = 0.9) grid::grid.raster(ref) grid::popViewport() grid::pushViewport(grid::viewport(x = 0.75, width = 0.5)) + grid::grid.text("new", y = 0.9) grid::grid.raster(image) grid::popViewport() } @@ -79,9 +82,91 @@ test_that("array patterns works as expected", { test_raster("image_squish.png", function() { grid.pattern_image(x, y, filename = logo_filename, type = "squish") }) + test_raster("hatch_azure.png", function() { + grid.pattern_hatch(x, y, type = "azure", colour = "black", spacing = 0.1) + }) + test_raster("hatch_gules.png", function() { + grid.pattern_hatch(x, y, type = "gules", colour = "black", spacing = 0.1) + }) + test_raster("hatch_sable.png", function() { + grid.pattern_hatch(x, y, type = "sable", colour = "black", spacing = 0.1) + }) + test_raster("hatch_sanguine.png", function() { + grid.pattern_hatch(x, y, type = "sanguine", colour = "black", spacing = 0.1) + }) + test_raster("hatch_or.png", function() { + grid.pattern_hatch(x, y, type = "or", colour = "black", spacing = 0.1) + }) + test_raster("hatch_cendree.png", function() { + grid.pattern_hatch(x, y, type = "cendree", colour = "black", spacing = 0.1) + }) + test_raster("hatch_olive.png", function() { + grid.pattern_hatch(x, y, type = "olive", colour = "black", spacing = 0.1) + }) + test_raster("hatch_proper.png", function() { + grid.pattern_hatch( + x, + y, + type = "proper", + subtype = "fox-davies", + colour = "black", + spacing = 0.1 + ) + }) + test_raster("hatch_steel.png", function() { + grid.pattern_hatch( + x, + y, + type = "steel", + subtype = "goodman", + colour = "black", + spacing = 0.1 + ) + }) + test_raster("hatch_copper.png", function() { + grid.pattern_hatch( + x, + y, + type = "copper", + subtype = "goodman", + colour = "black", + spacing = 0.1 + ) + }) + test_raster("hatch_unicode_orange.png", function() { + grid.pattern_hatch( + x, + y, + type = "orange", + subtype = "unicode", + colour = "black", + spacing = 0.1 + ) + }) + test_raster("hatch_unicode_grey.png", function() { + grid.pattern_hatch( + x, + y, + type = "grey", + subtype = "unicode", + colour = "black", + spacing = 0.1 + ) + }) test_raster("line.png", function() { grid.pattern_line(x, y, colour = "black", angle = 0, spacing = 0.1) }) + test_raster("line_stagger.png", function() { + grid.pattern_line( + x, + y, + colour = "black", + angle = 0, + spacing = 0.1, + linetype = "dashed", + stagger = TRUE + ) + }) test_raster("magick.png", function() { grid.pattern_magick(x, y, type = "octagons", fill = "blue", scale = 2) }) diff --git a/tests/testthat/test_utils.R b/tests/testthat/test_utils.R index e614f56..d487ef4 100644 --- a/tests/testthat/test_utils.R +++ b/tests/testthat/test_utils.R @@ -88,6 +88,109 @@ test_that("`mix_col()` works as expected", { expect_equal(mix_col(c("red", "yellow", "blue"), w = c(2, 1, 1)), "#F46666FF") }) +test_that("`names_hatch()` works as expected", { + # default is combinatorial, returns color equivalents + nms <- names_hatch() + expect_type(nms, "character") + expect_true(all(!duplicated(nms))) + expect_true(all(c("red", "blue", "green", "purple", "yellow", "black", "white") %in% nms)) + expect_true(all(c("magenta", "teal", "violet", "orange", "lime green") %in% nms)) + expect_true(all(c("pink", "grey", "mint green", "light blue", "lavender") %in% nms)) + + # fox-davies returns heraldic names + fd <- names_hatch("fox-davies") + expect_true(all(!duplicated(fd))) + expect_true(all(c("gules", "azure", "vert", "purpure", "sable", "argent", "or") %in% fd)) + expect_true(all( + c( + "eisenfarbe", + "brunatre", + "sanguine", + "tenne", + "carnation", + "cendree", + "orange", + "bleu celeste", + "proper" + ) %in% + fd + )) + + # goodman: has new tinctures, lacks eisenfarbe and proper + gd <- names_hatch("goodman") + expect_true(all(!duplicated(gd))) + expect_true(all(c("murrey", "steel", "copper", "bronze", "lead") %in% gd)) + expect_false("eisenfarbe" %in% gd) + expect_false("proper" %in% gd) + + # unicode returns color equivalents matching emoji heart colors + un <- names_hatch("unicode") + expect_true(all(!duplicated(un))) + expect_true(all( + c( + "red", + "blue", + "green", + "yellow", + "purple", + "black", + "white", + "brown", + "orange", + "light blue", + "grey", + "pink" + ) %in% + un + )) + + # accent substitutes accented spellings + fd_acc <- names_hatch("fox-davies", accent = TRUE) + expect_true("tenné" %in% fd_acc) + expect_true("brunâtre" %in% fd_acc) + expect_true("cendrée" %in% fd_acc) + expect_true("bleu céleste" %in% fd_acc) + expect_false("tenne" %in% fd_acc) + + # subtype matching is case-insensitive and ignores hyphens + expect_equal(names_hatch("Fox-Davies"), names_hatch("fox-davies")) + expect_equal(names_hatch("UNICODE"), names_hatch("unicode")) + expect_equal(names_hatch("Goodman"), names_hatch("goodman")) +}) + +test_that("`grid.pattern_hatch()` warns on unsupported type/subtype combinations", { + x <- c(0, 0, 1, 1) + y <- c(1, 0, 0, 1) + render <- function(expr) { + f <- tempfile(fileext = ".pdf") + on.exit(unlink(f)) + prev_dev <- dev.cur() + pdf(f) + on.exit( + { + suppressWarnings(dev.off()) + if (prev_dev > 1L) dev.set(prev_dev) + }, + add = TRUE + ) + force(expr) + } + # unknown type errors + expect_error( + render(grid.pattern_hatch(x, y, type = "notacolor")), + "Unknown hatching type" + ) + # type valid globally but not in this subtype errors + expect_error( + render(grid.pattern_hatch(x, y, type = "violet", subtype = "fox-davies")), + "not supported by the 'fox-davies' subtype" + ) + expect_error( + render(grid.pattern_hatch(x, y, type = "proper", subtype = "goodman")), + "not supported by the 'goodman' subtype" + ) +}) + test_that("`assert_suggested()` works as expected", { expect_error( assert_suggested("doesnotexist", pattern = "blueberry"), diff --git a/vignettes/hatching.Rmd b/vignettes/hatching.Rmd new file mode 100644 index 0000000..c3a6066 --- /dev/null +++ b/vignettes/hatching.Rmd @@ -0,0 +1,553 @@ +--- +title: "Heraldic Color Hatching" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Heraldic Color Hatching} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +can_render <- capabilities("png") || guess_has_R4.1_features("masks") +``` + +The `gridpattern` package supports heraldic color hatching via `grid.pattern_hatch()`. +Hatching encodes color information using patterns of lines and dots, allowing +images to be reproduced in black and white while retaining color identity. + +Four systems are supported via the `subtype` argument: + +- `"combinatorial"` (default): extends the seven standard Petra Sancta tinctures with systematically derived mixed-color combinations following three rules: + + 1. white combined with a color is represented by dashed lines + 2. yellow combined with a color is represented by dotdash lines + 3. color combined with a color is represented by crossing solid lines + +- `"fox-davies"`: contains the sixteen hatchings from Fox-Davies' [*A Complete Guide to Heraldry*](https://en.wikisource.org/wiki/A_Complete_Guide_to_Heraldry/Chapter_7#74) covering the seven standard Petra Sancta tinctures plus nine extensions from German heraldry. +- `"goodman"`: contains all the hatchings from David Goodman's [Heraldic Tincture](https://david.goodman.graphics/portfolio/item/crests-heraldry-and-coats-of-arms/) reference (v2.0, 2024). +- `"unicode"`: the system used in the official Unicode character chart pdfs to render "colored" (emoji) glyphs in black-and-white. + +Use `names_hatch()` to query supported tincture names for a given subtype. The "hatch" pattern will also coerce some of the more common color names to the right tincture e.g. "gold" and "yellow" will be both coerced to "or" and vice versa. + +```{r setup} +library("grid") +library("gridpattern") +``` + +```{r names} +names_hatch() +names_hatch("fox-davies") +names_hatch("goodman") +names_hatch("unicode") +``` + +## Combinatorial color hatching + +The default `"combinatorial"` subtype starts from the set of five [Munsell primary colors](https://en.wikipedia.org/wiki/Munsell_color_system) of red, yellow, green, blue, and purple plus black and white and the standard Petra Sancta color hatching system and then systematically derives additional color hatchings via three rules: + +1. **white + color** → dashed lines +2. **yellow + color** → dotdash lines +3. **color + color** → crossing solid lines + +```{r combinatorial-table, fig.alt = "Color table showing the Combinatorial Petra Sancta tinctures arranged by achromatic, primaries, secondaries, and notable combinations", fig.width = 7, fig.height = 8.5, eval = can_render && requireNamespace("aqp", quietly = TRUE), echo = FALSE} +# Munsell primary colors +p_col <- c( + argent = "#FFFFFF", + sable = "#000000", + gules = "#C83030", # 5R 4/14 + # or = "#E8C840", # 5Y 8/12 + or = "#D4B828", # 5Y 7/12 + azure = "#0072B0", # 5B 4/10 + vert = "#008060", # 5G 5/10 + # purpure = "#7B4090" # 5P 4/10 + purpure = "#9050C0" # 5P 6/12 +) +# p_col <- c( +# argent = aqp::parseMunsell("N 9.5/"), +# azure = aqp::parseMunsell("5B 4/10"), +# gules = aqp::parseMunsell("5R 4/14"), +# or = aqp::parseMunsell("5Y 7/12"), +# sable = aqp::parseMunsell("N 1/"), +# purpure = aqp::parseMunsell("5P 6/12"), +# vert = aqp::parseMunsell("5G 5/10") +# ) + +# Five Munsell secondary hues via subtractive mixing +s_col <- c( + orange = mix_col(c(p_col["gules"], p_col["or"])), # YR: red + yellow + lime = mix_col(c(p_col["or"], p_col["vert"])), # GY: yellow + green + teal = mix_col(c(p_col["azure"], p_col["vert"])), # BG: blue + green + violet = mix_col(c(p_col["azure"], p_col["purpure"])), # PB: blue + purple + sanguine = mix_col(c(p_col["gules"], p_col["purpure"])) # RP: red + purple +) + +# Notable combination colors +w_col <- c( + carnation = mix_col(c(p_col["argent"], p_col["gules"])), + cendree = mix_col(c(p_col["argent"], p_col["sable"])), + mint = mix_col(c(p_col["argent"], p_col["vert"])), + `bleu celeste` = mix_col(c(p_col["argent"], p_col["azure"])), + lavender = mix_col(c(p_col["argent"], p_col["purpure"])) +) +o_col <- c( + tenne = mix_col(c(p_col["gules"], p_col["vert"])), + slate = mix_col(c(p_col["purpure"], p_col["vert"])), + olive = mix_col(c(p_col["or"], p_col["sable"])), + rose = mix_col(c(p_col["or"], p_col["purpure"])), + brunatre = mix_col(c(p_col["azure"], p_col["gules"], p_col["vert"])) +) + +groups <- list( + list( + label = "Achromatic", + tinctures = c("argent", "sable"), + cols = p_col[c("argent", "sable")], + names = c("white (W)", "black (K)") + ), + list( + label = "Munsell Primary Hues", + tinctures = c("gules", "or", "vert", "azure", "purpure"), + cols = p_col[c("gules", "or", "vert", "azure", "purpure")], + names = c("red (R)", "yellow (Y)", "green (G)", "blue (B)", "purple (P)") + ), + list( + label = "Munsell Secondary Hues", + tinctures = c("orange", "lime", "teal", "violet", "sanguine"), + cols = s_col, + names = c("orange (R+Y)", "lime (Y+G)", "teal (G+B)", "violet (B+P)", "magenta (P+R)") + ), + list( + label = "Combinations with White", + tinctures = c("carnation", "cendree", "mint", "bleu celeste", "lavender"), + cols = w_col, + names = c("pink (R+W)", "grey (K+W)", "mint (G+W)", "light blue (B+W)", "lavender (P+W)") + ), + list( + label = "Other Combinations*", + tinctures = c("tenne", "olive", "slate", "brunatre", "rose"), + cols = o_col[c("tenne", "olive", "slate", "brunatre", "rose")], + names = c("brown (R+G)", "olive (Y+K)", "slate (G+P)", "umbre (B+R+G)", "rose (Y+P)") + ) +) + +rx <- c(0, 0, 1, 1) +ry <- c(1, 0, 0, 1) +ncols_fig <- 5L + +row_heights <- unlist(lapply(groups, function(g) { + n_sr <- ceiling(length(g$tinctures) / ncols_fig) + c(0.28, rep(1, n_sr)) +})) + +grid.newpage() +grid.rect(gp = gpar(fill = "white", col = NA)) +pushViewport(viewport(width = 0.97, height = 0.97)) +grid.text( + "Combinatorial Petra Sancta", + y = unit(1, "npc") - unit(0.25, "cm"), + just = "top", + gp = gpar(fontsize = 31, fontface = "bold") +) + +# Upper-right rules legend +pushViewport(viewport( + x = unit(1, "npc") - unit(0.2, "cm"), + y = unit(1, "npc") - unit(1.3, "cm"), + just = c("right", "top"), + width = unit(10.0, "cm"), height = unit(3.1, "cm") +)) +grid.rect(gp = gpar(fill = "grey98", col = "grey60", lwd = 0.8)) +grid.text("Combination rules:", x = 0.01, y = 0.91, just = c("left", "top"), + gp = gpar(fontsize = 14, fontface = "bold")) +legend_rules <- c( + "1. Dashed lines — combined with white", + "2. Dot-dash lines — combined with yellow", + "3. Crossed solid lines — mixed colors (if not black)" +) +for (i in seq_along(legend_rules)) { + grid.text(legend_rules[i], x = 0.01, y = 0.66 - (i - 1L) * 0.25, + just = c("left", "top"), gp = gpar(fontsize = 11)) +} +popViewport() + +pushViewport(viewport( + y = 0.49, height = 0.90, + layout = grid.layout(length(row_heights), ncols_fig, heights = unit(row_heights, "null")) +)) + +layout_row <- 1L +for (g in groups) { + pushViewport(viewport(layout.pos.row = layout_row, layout.pos.col = 1:ncols_fig)) + grid.text(g$label, x = 0.01, just = "left", + gp = gpar(fontsize = 18, fontface = "bold", col = "black")) + popViewport() + layout_row <- layout_row + 1L + + n_sr <- ceiling(length(g$tinctures) / ncols_fig) + for (sr in seq_len(n_sr)) { + idx_from <- (sr - 1L) * ncols_fig + 1L + idx_to <- min(sr * ncols_fig, length(g$tinctures)) + for (ci in idx_from:idx_to) { + t <- g$tinctures[ci] + col_i <- (ci - 1L) %% ncols_fig + 1L + if (is.na(t)) next + col <- unname(g$cols[ci]) + nm <- g$names[ci] + display_col <- if (t == "argent") "grey55" else col + + pushViewport(viewport(layout.pos.row = layout_row, layout.pos.col = col_i)) + pushViewport(viewport(y = 0.58, width = 0.90, height = 0.72, + layout = grid.layout(1, 2))) + pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 1)) + grid.rect(gp = gpar(fill = col, col = display_col, lwd = 1.5)) + popViewport() + pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 2)) + grid.pattern_hatch(rx, ry, type = t, color = display_col, + spacing = 0.18, linewidth = 0.8) + grid.rect(gp = gpar(fill = NA, col = display_col, lwd = 1.5)) + popViewport() + popViewport() + grid.text(nm, y = unit(0.105, "npc"), gp = gpar(fontsize = 9, col = "black")) + popViewport() + } + layout_row <- layout_row + 1L + } +} + +popViewport() + +# Footnote +grid.text( + "* Display colors are sensitive to the exact primary pigments chosen;\n results are roughly consistent for saturated heraldic primaries with Munsell pigment mixing.", + x = 0.01, y = 0.004, just = c("left", "bottom"), + gp = gpar(fontsize = 10, col = "black", fontface = "italic") +) + +popViewport() +``` + +**Note:** The mixed display colors shown above can be sensitive to the exact hex values chosen for the primaries. +The results are fairly consistent when the primaries are the saturated, high-chroma colors typical of heraldry combined with Munsell pigment mixing +but softer or more neutral primaries can shift some secondaries noticeably +(for example, mixing yellow and blue can yield anything from olive-grey to muted purple depending on the blue's hue angle). + +## Heraldic tincture hatching + +### Fox-Davies + +The `"fox-davies"` hatching subtype includes the seven standard tinctures plus nine extensions from German heraldry whose hatchings were included in Fox-Davies' [*A Complete Guide to Heraldry*](https://en.wikisource.org/wiki/A_Complete_Guide_to_Heraldry/Chapter_7#74). + +```{r fox-davies-shields, fig.alt = "Heraldic shields showing the Fox-Davies hatching tinctures", fig.width = 7, fig.height = 6.0, eval = can_render, echo = FALSE} +# Approximate display color for each tincture +tincture_col <- c( + argent = "grey40", + azure = "#003399", + `bleu celeste` = "#4499CC", + brunatre = "#7B3A10", + carnation = "#CC6688", + cendree = "#708090", + gules = "#CC0000", + eisenfarbe = "#708090", + proper = "#228B22", + or = "#DAA520", + orange = "#EE7700", + purpure = "#660099", + sable = "#111111", + sanguine = "#880000", + tenne = "#BB6600", + vert = "#006400" +) +color_equiv <- c( + argent = "white/silver", + azure = "blue", + `bleu celeste` = "light blue", + brunatre = "(earth) brown", + carnation = "carnation", + cendree = "ash grey", + gules = "red", + eisenfarbe = "iron grey", + proper = "color of nature", + or = "yellow/gold", + orange = "orange", + purpure = "purple", + sable = "black", + sanguine = "blood red", + tenne = "(tawny) brown", + vert = "green" +) + +# Heater shield polygon (normalised to [0,1] x [0,1]) +sx <- c(0.0, 0.0, 0.5, 1.0, 1.0) +sy <- c(1.0, 0.35, 0.0, 0.35, 1.0) + +tinctures <- names_hatch("fox-davies") +tincture_labels <- names_hatch("fox-davies", accent = TRUE) +n <- length(tinctures) +ncols <- 4L +nrows <- ceiling(n / ncols) + +grid.newpage() +grid.rect(gp = gpar(fill = "white", col = NA)) +pushViewport(viewport(width = 0.97, height = 0.97)) +grid.text( + "Heraldic Hatching (Petra Sancta + German Heraldry Extensions)", + y = unit(1, "npc") - unit(0.25, "cm"), + just = "top", + gp = gpar(fontsize = 13, fontface = "bold") +) +pushViewport(viewport(y = 0.47, height = 0.90, layout = grid.layout(nrows, ncols))) + +for (i in seq_len(n)) { + t <- tinctures[i] + col <- tincture_col[t] + row_i <- ((i - 1L) %/% ncols) + 1L + col_i <- ((i - 1L) %% ncols) + 1L + + pushViewport(viewport(layout.pos.row = row_i, layout.pos.col = col_i)) + pushViewport(viewport(y = 0.60, width = 0.78, height = 0.70)) + + grid.polygon(sx, sy, gp = gpar(fill = "white", col = NA)) + grid.pattern_hatch(sx, sy, type = t, subtype = "fox-davies", color = col, spacing = 0.12, linewidth = 1.0) + grid.polygon(sx, sy, gp = gpar(fill = NA, col = col, lwd = 1.5)) + + popViewport() + + grid.text(tincture_labels[i], y = unit(0.20, "npc"), gp = gpar(fontsize = 8.5, col = "grey20")) + grid.text(color_equiv[t], y = unit(0.06, "npc"), gp = gpar(fontsize = 7.5, col = col)) + + popViewport() +} + +popViewport() +popViewport() +``` + +### Goodman + +The `"goodman"` hatching subtype includes all the hatchings in David Goodman's [Heraldic Tincture](https://david.goodman.graphics/portfolio/item/crests-heraldry-and-coats-of-arms/) reference (v2.0, 2024). This shares most hatchings with Fox-Davies but differs in few ways: + +- Goodman's **sanguine** hatching instead uses horizontal plus diagonal `\` crossing lines. +- Goodman also has a distinct **murrey** hatching which uses crossing diagonal lines (Fox-Davies' instead uses this hatching for the eisenfarbe (iron grey) hatching). +- New **rose** with the same hatching as carnation (which Goodman also lists). +- New **steel** metal rendered as plus signs in a square grid. +- New **copper**, **bronze**, and **lead** metals which are each rendered as the letter "c" in a hex grid. +- Goodman omits the **proper** hatching that Fox-Davies included. + +```{r goodman-shields, fig.alt = "Heraldic shields showing Goodman tinctures that differ from Fox-Davies", fig.width = 7, fig.height = 4.0, eval = can_render, echo = FALSE} +tincture_col <- c( + sanguine = "#880000", + murrey = "#990055", + steel = "#708090", + copper = "#B87333" +) + +sx <- c(0.0, 0.0, 0.5, 1.0, 1.0) +sy <- c(1.0, 0.35, 0.0, 0.35, 1.0) + +tinctures <- names(tincture_col) +n <- length(tinctures) +ncols <- 4L +nrows <- ceiling(n / ncols) + +grid.newpage() +grid.rect(gp = gpar(fill = "white", col = NA)) +pushViewport(viewport(width = 0.97, height = 0.97)) +grid.text( + "Goodman — New and Different Tinctures", + y = unit(1, "npc") - unit(0.25, "cm"), + just = "top", + gp = gpar(fontsize = 13, fontface = "bold") +) +pushViewport(viewport(y = 0.44, height = 0.85, layout = grid.layout(nrows, ncols))) + +for (i in seq_len(n)) { + t <- tinctures[i] + col <- tincture_col[t] + row_i <- ((i - 1L) %/% ncols) + 1L + col_i <- ((i - 1L) %% ncols) + 1L + + pushViewport(viewport(layout.pos.row = row_i, layout.pos.col = col_i)) + pushViewport(viewport(y = 0.60, width = 0.78, height = 0.70)) + + grid.polygon(sx, sy, gp = gpar(fill = "white", col = NA)) + grid.pattern_hatch(sx, sy, type = t, subtype = "goodman", color = col, spacing = 0.12, linewidth = 0.8) + grid.polygon(sx, sy, gp = gpar(fill = NA, col = col, lwd = 1.5)) + + popViewport() + + grid.text(t, y = unit(0.20, "npc"), gp = gpar(fontsize = 18, col = "black")) + popViewport() +} + +popViewport() +popViewport() +``` + +## Unicode color hatching + +The `"unicode"` hatching subtype provides each of the hatching used in the official Unicode character chart pdfs to assign a distinct pattern to each color to render "colored" (emoji) glyphs in black-and-white. +Notably Unicode has twelve different [colored heart emoji](https://unicode.org/emoji/charts/full-emoji-list.html#heart) (red, blue, green, yellow, purple, black, white, brown, orange, light blue, grey, pink) that each needed a separate hatching. + +```{r unicode-hearts, fig.alt = "Twelve Unicode colored hearts rendered with hatching patterns", fig.width = 7, fig.height = 6, eval = requireNamespace("Unicode", quietly = TRUE) && can_render, echo = FALSE} +library("Unicode") + +# The 12 Unicode colored hearts in codepoint order +heart_codepoints <- c( + red = 0x2764L, # HEAVY BLACK HEART (displays as red via emoji VS) + blue = 0x1F499L, + green = 0x1F49AL, + yellow = 0x1F49BL, + purple = 0x1F49CL, + black = 0x1F5A4L, + white = 0x1F90DL, + brown = 0x1F90EL, + orange = 0x1F9E1L, + `light blue` = 0x1FA75L, + grey = 0x1FA76L, + pink = 0x1FA77L +) + +# Approximate display colors +heart_col <- c( + red = "#CC0000", + blue = "#0055CC", + green = "#006400", + yellow = "#CCAA00", + purple = "#6600AA", + black = "#111111", + white = "#999999", # grey stroke so argent pattern is visible + brown = "#7B3A10", + orange = "#FF8000", + `light blue` = "#4499CC", + grey = "#666666", + pink = "#DD4488" +) + +uchars <- as.u_char(as.integer(heart_codepoints)) +labels <- u_char_name(uchars) +heart_shape <- "♥" # U+2665 BLACK HEART SUIT — uniform shape template + +n <- length(heart_codepoints) +ncols <- 4L +nrows <- ceiling(n / ncols) + +grid.newpage() +grid.rect(gp = gpar(fill = "white", col = NA)) +pushViewport(viewport(width = 0.95, height = 0.95)) +grid.text( + "Unicode Colored Hearts with Hatching", + y = unit(1, "npc") - unit(0.25, "cm"), + just = "top", + gp = gpar(fontsize = 22, fontface = "bold") +) +pushViewport(viewport(y = 0.48, height = 0.90, layout = grid.layout(nrows, ncols))) + +for (i in seq_len(n)) { + col_i <- ((i - 1L) %% ncols) + 1L + row_i <- ((i - 1L) %/% ncols) + 1L + col <- heart_col[i] + + pushViewport(viewport(layout.pos.row = row_i, layout.pos.col = col_i)) + pushViewport(viewport(width = 0.85, height = 0.85)) + + pfill <- patternFill( + "hatch", + type = names(heart_codepoints)[i], + subtype = "unicode", + color = col, + spacing = 0.14, + linewidth = 0.8 + ) + grid.draw( + fillStrokeGrob( + textGrob(heart_shape, gp = gpar(fontsize = 84)), + gp = gpar(fill = pfill, col = col) + ) + ) + + grid.text(labels[i], y = unit(0.12, "npc"), + gp = gpar(fontsize = 12, col = "black")) + grid.text(sprintf("U+%04X", heart_codepoints[i]), y = unit(0.00, "npc"), + gp = gpar(fontsize = 10, col = "black")) + + popViewport() + popViewport() +} + +popViewport() +popViewport() +``` + +## Okabe-Ito hatching + +One of the techniques to meet Web Content Accessibility Guidelines (WCAG) is to [use color and pattern](https://www.w3.org/WAI/WCAG21/Techniques/general/G111) to ensure things are accessible to the color-blind. + +The [Okabe-Ito palette](https://jfly.uni-koeln.de/color/) is a widely used colorblind-friendly palette. Here is an example of adding a simple hatching scheme to go with this palette to provide visual redundancy: + + * "yellow", "blue", and "white" are given their standard Petra Sancta hatchings + + "reddish purple" is given a "purple" Petra Sancta hatching + * "bluish green" is given a "green" Petra Sancta hatching + * "vermillion" (red orange) is given a "red" Petra Sancta hatching + + we use a simple black fill for "black" instead of a "sable" crosshatch + * "orange" and "sky blue" are given the hatchings from German heraldry (see Fox-Davies' section above) + +```{r okabe-ito, fig.alt = "Table of Okabe-Ito palette colors paired with heraldic hatching patterns", fig.width = 6, fig.height = 6, eval = can_render} +oi_names <- c( + "black", "orange", "sky blue", "bluish green", + "yellow", "blue", "vermilion", "reddish purple", "white" +) +oi_hex <- c( + "#000000", "#E69F00", "#56B4E9", "#009E73", + "#F0E442", "#0072B2", "#D55E00", "#CC79A7", "#FFFFFF" +) +oi_hatch <- c( + NA, "orange", "bleu celeste", "vert", + "or", "azure", "gules", "purpure", NA +) +sx <- c(0, 0, 1, 1) +sy <- c(1, 0, 0, 1) +n <- length(oi_names) + +grid.newpage() +grid.rect(gp = gpar(fill = "white", col = NA)) +pushViewport(viewport(width = 0.90, height = 0.94)) +grid.text( + "Okabe-Ito Palette with Heraldic Hatching", + y = unit(1, "npc") - unit(0.25, "cm"), + just = "top", + gp = gpar(fontsize = 13, fontface = "bold") +) +pushViewport(viewport( + y = 0.46, height = 0.88, + layout = grid.layout( + n, 3, + widths = unit(c(3, 2.5, 4), "null"), + heights = unit(rep(1, n), "null") + ) +)) + +for (i in seq_len(n)) { + grid.text(oi_names[i], x = 0.90, just = "right", + gp = gpar(fontsize = 12, col = "black"), + vp = viewport(layout.pos.row = i, layout.pos.col = 1)) + + grid.text(oi_hex[i], + gp = gpar(fontsize = 12, fontfamily = "mono", col = "black"), + vp = viewport(layout.pos.row = i, layout.pos.col = 2)) + + pushViewport(viewport(layout.pos.row = i, layout.pos.col = 3)) + grid.rect(gp = gpar(fill = oi_hex[i], col = "black", lwd = 3.0)) + if (!is.na(oi_hatch[i])) { + grid.pattern_hatch(sx, sy, type = oi_hatch[i], + colour = "black", spacing = 0.18, linewidth = 0.8) + } + popViewport() +} + +popViewport() +popViewport() +```