diff --git a/DESCRIPTION b/DESCRIPTION index 2f7b081..8cb45db 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: gridpattern Type: Package Title: 'grid' Pattern Grobs -Version: 1.2.0-1 +Version: 1.2.0-2 Authors@R: c(person("Mike", "FC", role = "aut", comment = "Code/docs adapted from ggpattern"), person("Trevor L.", "Davis", role=c("aut", "cre"), email="trevor.l.davis@gmail.com", comment = c(ORCID = "0000-0001-6341-4639")), diff --git a/NEWS.md b/NEWS.md index ebfad76..711eeb5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,15 +3,18 @@ gridpattern v1.2.0 (development) * `patternFill()` wraps `patternGrob()` to return a `grid::pattern()` fill object (#70). - +* The "geometry" patterns (e.g. "circle", "stripe", etc.) now allow + the `fill` to be pattern fills (#67). * `update_alpha()` updates fill colour and/or pattern transparency. + It is a fork of `ggplot2::fill_alpha()` by @teunbrand. + It does not depend on `{ggplot2}` or `{scales}`. + It does not throw an error with a length one list of a vector of multiple colours. - -* The "geometry" patterns (e.g. "circle", "stripe", etc.) now allow - the `fill` to be pattern fills (#67). + + It is available as a "standalone" file. + You may copy its source under the permissive MIT license into your own R package by either + using `usethis::use_standalone("trevorld/gridpattern", "standalone-update_alpha.R")` + or simply copying `standalone-update_alpha.R` into your R directory and adding + `grDevices`, `grid`, and `rlang` to the `Imports` of your `DESCRIPTION` file. gridpattern v1.1.1 ================== diff --git a/R/pattern-both-rose.R b/R/pattern-both-rose.R index dd230cb..8000ec6 100644 --- a/R/pattern-both-rose.R +++ b/R/pattern-both-rose.R @@ -71,7 +71,7 @@ create_pattern_rose <- function(params, boundary_df, aspect_ratio, legend = FALS # construct grobs using subsets if certain inputs are vectorized fill <- update_alpha(params$pattern_fill, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth * .pt lty <- params$pattern_linetype diff --git a/R/pattern-both-text.R b/R/pattern-both-text.R index 40f96a9..266bba9 100644 --- a/R/pattern-both-text.R +++ b/R/pattern-both-text.R @@ -67,7 +67,7 @@ create_pattern_text <- function(params, boundary_df, aspect_ratio, legend = FALS grid_xy <- get_xy_grid(params, vpm) # vectorize fill, col, lwd, lty, density, rot, and shape - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha_col(params$pattern_colour, params$pattern_alpha) fontsize <- params$pattern_size fontfamily <- params$pattern_fontfamily fontface <- params$pattern_fontface diff --git a/R/pattern-geometry-crosshatch.R b/R/pattern-geometry-crosshatch.R index bfdede1..4cca813 100644 --- a/R/pattern-geometry-crosshatch.R +++ b/R/pattern-geometry-crosshatch.R @@ -58,7 +58,7 @@ create_crosshatch_via_sf_helper <- function(params, boundary_df, add_top_hatch = grid_xy <- get_xy_grid(params, vpm) fill <- update_alpha(params$pattern_fill, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth * .pt lty <- params$pattern_linetype gp <- gpar(col = col, fill = fill, lwd = lwd, lty = lty, lineend = 'square') diff --git a/R/pattern-geometry-pch.R b/R/pattern-geometry-pch.R index accd992..2285fc2 100644 --- a/R/pattern-geometry-pch.R +++ b/R/pattern-geometry-pch.R @@ -68,7 +68,7 @@ grid.pattern_pch <- function(x = c(0, 0, 1, 1), y = c(1, 0, 0, 1), id = 1L, ..., create_pattern_pch <- function(params, boundary_df, aspect_ratio, legend = FALSE) { # vectorize fill, col, lwd, lty, density, rot, and shape fill <- update_alpha(params$pattern_fill, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth lty <- params$pattern_linetype params$pattern_alpha <- NA_real_ diff --git a/R/pattern-geometry-regular_polygon.R b/R/pattern-geometry-regular_polygon.R index 2c0e9c7..139045b 100644 --- a/R/pattern-geometry-regular_polygon.R +++ b/R/pattern-geometry-regular_polygon.R @@ -106,7 +106,7 @@ create_pattern_regular_polygon_via_sf <- function(params, boundary_df, aspect_ra # construct grobs using subsets if certain inputs are vectorized fill <- update_alpha(params$pattern_fill, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth * .pt lty <- params$pattern_linetype diff --git a/R/pattern-geometry-tiling.R b/R/pattern-geometry-tiling.R index 6d19a38..1a57aab 100644 --- a/R/pattern-geometry-tiling.R +++ b/R/pattern-geometry-tiling.R @@ -149,7 +149,7 @@ create_pattern_polygon_tiling <- function(params, boundary_df, aspect_ratio, leg xyi <- boundary_df fill <- update_alpha(params$pattern_fill, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth lty <- params$pattern_linetype stopifnot(length(fill) < 4L, max(lengths(list(col, lwd, lty))) == 1L) diff --git a/R/pattern-geometry-wave.R b/R/pattern-geometry-wave.R index 58eace0..dd1f45a 100644 --- a/R/pattern-geometry-wave.R +++ b/R/pattern-geometry-wave.R @@ -63,7 +63,7 @@ create_pattern_wave_via_sf <- function(params, boundary_df, aspect_ratio, grid_xy <- get_xy_grid(params, vpm) fill <- update_alpha(params$pattern_fill, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth * .pt lty <- params$pattern_linetype density <- params$pattern_density diff --git a/R/pattern-geometry-weave.R b/R/pattern-geometry-weave.R index 390608a..d1c4eca 100644 --- a/R/pattern-geometry-weave.R +++ b/R/pattern-geometry-weave.R @@ -87,7 +87,7 @@ create_warp_via_sf <- function(params, boundary_df) { grid_xy <- get_xy_grid(params, vpm) fill <- update_alpha(params$pattern_fill2, params$pattern_alpha) - col <- alpha(params$pattern_colour, params$pattern_alpha) + col <- update_alpha(params$pattern_colour, params$pattern_alpha) lwd <- params$pattern_linewidth * .pt lty <- params$pattern_linetype gp <- gpar(col = col, fill = fill, lwd = lwd, lty = lty, lineend = 'square') diff --git a/R/standalone-update_alpha.R b/R/standalone-update_alpha.R new file mode 100644 index 0000000..69cf5a4 --- /dev/null +++ b/R/standalone-update_alpha.R @@ -0,0 +1,132 @@ +# --- +# repo: trevorld/gridpattern +# file: standalone-update_alpha.R +# last-updated: 2024-04-18 +# license: https://spdx.org/licenses/MIT.html +# imports: [grDevices, grid, rlang] +# --- +# +# nocov start +# +# You may copy this source into your own R package +# by either using `usethis::use_standalone("trevorld/gridpattern", "standalone-update_alpha.R")` +# or simply copying this file into your `R` directory and adding `grDevices`, `grid`, and `rlang` to +# the `Imports` of your `DESCRIPTION` file. + +# The MIT License (MIT) +# ===================== +# +# Copyright © 2024 Trevor L. Davis +# Copyright © 2020 mikefc@coolbutuseless.com +# Copyright © 2023 ggplot2 authors +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the “Software”), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +# Pattern utilities mainly added to ggplot2 by Teun van den Brand +# Tweaked by Trevor L. Davis to remove external dependencies +# and work better for {ggpattern} / {gridpattern} use cases. +update_alpha <- function(fill, alpha) { + if (!is.list(fill)) { + # Happy path of no patterns + update_alpha_col(fill, alpha) + } else if (is_pattern(fill) || any(vapply(fill, is_pattern, logical(1)))) { + # Path with patterns + update_pattern_alpha(fill, alpha) + } else if (is.list(fill) && length(fill) == 1L && !any(vapply(fill, is_pattern, logical(1)))) { + # List of length one of (possibly multiple) colours + update_alpha_col(fill[[1L]], alpha) + } else { + # We are either dealing with faulty fill specification + stop("`fill` must be a vector of colours or list of objects.") + } +} + +# Similar to grid:::is.pattern +is_pattern <- function(x) { + inherits(x, "GridPattern") +} + +# replacement for `scales::alpha()` that only depends on {grDevices} +update_alpha_col <- function(colour, alpha = NA_real_) { + n <- max(lengths(list(colour, alpha))) + colour <- rep_len(colour, n) + alpha <- rep_len(alpha, n) + m <- grDevices::col2rgb(colour, alpha = TRUE) / 255.0 + m[4, ] <- ifelse(is.na(alpha), m[4, ], alpha) + apply(m, 2, function(x) grDevices::rgb(x[1], x[2], x[3], x[4])) +} + +#' Modify transparency for patterns +#' +#' This generic allows you to add your own methods for adding transparency to +#' pattern-like objects. +#' +#' @param x Object to be interpreted as pattern. +#' @param alpha A `numeric` vector between 0 and 1. If `NA`, alpha values +#' are preserved. +#' +#' @return `x` with modified transparency +#' @noRd +update_pattern_alpha <- function(x, alpha, ...) { + UseMethod("update_pattern_alpha") +} + +#' @export +update_pattern_alpha.default <- function(x, alpha, ..., name = NULL) { + if (!is.atomic(x)) { + stop("Can't apply `update_pattern_alpha()` to this object.") + } + grid::pattern(grid::rectGrob(name = name), + gp = grid::gpar(fill = update_alpha_col(x, alpha))) +} + +#' @export +update_pattern_alpha.GridPattern <- function(x, alpha, ...) { + x$colours <- update_alpha_col(x$colours, alpha[1]) + x +} + +#' @export +update_pattern_alpha.GridTilingPattern <- function(x, alpha, ...) { + if (all(is.na(alpha) | alpha == 1)) { + return(x) + } + grob <- rlang::env_get(environment(x$f), "grob") + gp <- grid::gpar(fill = update_alpha_col("white", alpha)) + mask <- grid::as.mask(grid::rectGrob(gp = gp)) + if (is.null(grob$vp)) { + grob$vp <- grid::viewport(mask = mask) + } else { + grob$vp <- grid::editViewport(grob$vp, mask = mask) + } + new_env <- new.env(parent = environment(x$f)) + rlang::env_bind(new_env, grob = grob) + environment(x$f) <- new_env + x +} + +#' @export +update_pattern_alpha.list <- function(x, alpha, ...) { + Map(update_pattern_alpha, x = x, alpha = alpha) +} + +# nocov end diff --git a/R/utils-ggplot2.R b/R/utils-ggplot2.R index 8faae01..7fe5965 100644 --- a/R/utils-ggplot2.R +++ b/R/utils-ggplot2.R @@ -1,13 +1,3 @@ -# replacement for scales::alpha that only depends on grDevices -alpha <- function(colour, alpha = NA_real_) { - n <- max(lengths(list(colour, alpha))) - colour <- rep(colour, length.out = n) - alpha <- rep(alpha, length.out = n) - m <- grDevices::col2rgb(colour, alpha=TRUE) / 255.0 - m[4, ] <- ifelse(is.na(alpha), m[4, ], alpha) - apply(m, 2, function(x) grDevices::rgb(x[1], x[2], x[3], x[4])) -} - # Added to ggplot2 by Thomas Lin Pedersen # Fast data.frame constructor and indexing # No checking, recycling etc. unless asked for @@ -36,104 +26,3 @@ data_frame <- function(...) { new_data_frame(list(...)) } .pt <- 2.845276 # ggplot2 constant - -# Pattern utilities mainly added to ggplot2 by Teun van den Brand - -#' Update fill colour and/or pattern transparency -#' -#' `update_alpha()` modifies the transparency of fill colours and/or patterns. -#' -#' * Like [ggplot2::fill_alpha()] but unlike [scales::alpha()] it also attempts -#' to set the transparency of `` objects. -#' * Unlike [ggplot2::fill_alpha()] it will work on a list of length one -#' with more than one color. -#' * `update_alpha()` does not depend on `ggplot2` or `scales`. -#' -#' @param fill A fill colour given as a `character` or `integer` vector, or as a -#' (list of) `` object(s) and/or colour(s). -#' @param alpha A transparency value between 0 (transparent) and 1 (opaque), -#' parallel to `fill`. -#' -#' @return A `character` vector of colours or list of `` objects. -#' @export -#' -#' @examples -#' # Typical colour input -#' update_alpha("red", 0.5) -#' -#' if (utils::packageVersion("grid") > "4.1") { -#' # Pattern input -#' update_alpha(list(grid::linearGradient()), 0.5) -#' } -update_alpha <- function(fill, alpha) { - if (!is.list(fill)) { - # Happy path of no patterns - alpha(fill, alpha) - } else if (is_pattern(fill) || any(vapply(fill, is_pattern, logical(1)))) { - # Path with patterns - update_pattern_alpha(fill, alpha) - } else if (is.list(fill) && length(fill) == 1L && !any(vapply(fill, is_pattern, logical(1)))) { - # List of length one of (possibly multiple) colours - alpha(fill[[1L]], alpha) - } else { - # We are either dealing with faulty fill specification - stop("`fill` must be a vector of colours or list of objects.") - } -} - -# Similar to grid:::is.pattern -is_pattern <- function(x) { - inherits(x, "GridPattern") -} - -#' Modify transparency for patterns -#' -#' This generic allows you to add your own methods for adding transparency to -#' pattern-like objects. -#' -#' @param x Object to be interpreted as pattern. -#' @param alpha A `numeric` vector between 0 and 1. If `NA`, alpha values -#' are preserved. -#' -#' @return `x` with modified transparency -#' @noRd -update_pattern_alpha <- function(x, alpha, ...) { - UseMethod("update_pattern_alpha") -} - -#' @export -update_pattern_alpha.default <- function(x, alpha, ..., name = NULL) { - if (!is.atomic(x)) { - stop("Can't apply `update_pattern_alpha()` to this object.") - } - pattern(rectGrob(name = name), gp = gpar(fill = alpha(x, alpha))) -} - -#' @export -update_pattern_alpha.GridPattern <- function(x, alpha, ...) { - x$colours <- alpha(x$colours, alpha[1]) - x -} - -#' @export -update_pattern_alpha.GridTilingPattern <- function(x, alpha, ...) { - if (all(is.na(alpha) | alpha == 1)) { - return(x) - } - grob <- rlang::env_get(environment(x$f), "grob") - mask <- as.mask(rectGrob(gp = gpar(fill = alpha("white", alpha)))) - if (is.null(grob$vp)) { - grob$vp <- viewport(mask = mask) - } else { - grob$vp <- editViewport(grob$vp, mask = mask) - } - new_env <- new.env(parent = environment(x$f)) - rlang::env_bind(new_env, grob = grob) - environment(x$f) <- new_env - x -} - -#' @export -update_pattern_alpha.list <- function(x, alpha, ...) { - Map(update_pattern_alpha, x = x, alpha = alpha) -} diff --git a/R/z_update_alpha_docs.R b/R/z_update_alpha_docs.R new file mode 100644 index 0000000..7aa2c3c --- /dev/null +++ b/R/z_update_alpha_docs.R @@ -0,0 +1,36 @@ +# The code for this function is in `standalone-update_alpha.R` + +#' Update colour and/or pattern transparency +#' +#' `update_alpha()` modifies the transparency of colours and/or patterns. +#' +#' * This is a fork of pattern utilities mainly added to `{ggplot2}` by Teun van den Brand. +#' * `update_alpha()` does not depend on `{ggplot2}` or `{scales}`. +#' * Like [ggplot2::fill_alpha()] but unlike [scales::alpha()] it also attempts +#' to set the transparency of `` objects. +#' * Unlike [ggplot2::fill_alpha()] it will work on a list of length one +#' containing a vector of color strings. +#' +#' @section Usage in other packages: +#' +#' To avoid taking a dependency on `gridpattern` you may copy the source of `update_alpha()` +#' into your own package under the permissive MIT license. Either use +#' `usethis::use_standalone("trevorld/gridpattern", "standalone-update_alpha.R")` +#' or copy the file `update_alpha.R` into your `R` directory and +#' add `grDevices`, `grid`, and `rlang` to the `Imports` of your `DESCRIPTION` file. +#' +#' @param fill A fill colour given as a `character` or `integer` vector, or as a +#' (list of) `` object(s) and/or colour(s). +#' @param alpha A transparency value between 0 (transparent) and 1 (opaque), +#' parallel to `fill`. +#' @return A `character` vector of colours or list of `` objects. +#' @examples +#' # Typical color input +#' update_alpha("red", 0.5) +#' +#' # Pattern input +#' if (getRversion() >= "4.2" && requireNamespace("grid", quietly = TRUE)) { +#' update_alpha(list(grid::linearGradient()), 0.5) +#' } +#' @export +update_alpha <- update_alpha diff --git a/man/update_alpha.Rd b/man/update_alpha.Rd index 32ccacf..98b08db 100644 --- a/man/update_alpha.Rd +++ b/man/update_alpha.Rd @@ -1,8 +1,8 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-ggplot2.R +% Please edit documentation in R/z_update_alpha_docs.R \name{update_alpha} \alias{update_alpha} -\title{Update fill colour and/or pattern transparency} +\title{Update colour and/or pattern transparency} \usage{ update_alpha(fill, alpha) } @@ -17,23 +17,34 @@ parallel to \code{fill}.} A \code{character} vector of colours or list of \verb{} objects. } \description{ -\code{update_alpha()} modifies the transparency of fill colours and/or patterns. +\code{update_alpha()} modifies the transparency of colours and/or patterns. } \details{ \itemize{ +\item This is a fork of pattern utilities mainly added to \code{{ggplot2}} by Teun van den Brand. +\item \code{update_alpha()} does not depend on \code{{ggplot2}} or \code{{scales}}. \item Like \code{\link[ggplot2:fill_alpha]{ggplot2::fill_alpha()}} but unlike \code{\link[scales:alpha]{scales::alpha()}} it also attempts to set the transparency of \verb{} objects. \item Unlike \code{\link[ggplot2:fill_alpha]{ggplot2::fill_alpha()}} it will work on a list of length one -with more than one color. -\item \code{update_alpha()} does not depend on \code{ggplot2} or \code{scales}. +containing a vector of color strings. } } +\section{Usage in other packages}{ + + +To avoid taking a dependency on \code{gridpattern} you may copy the source of \code{update_alpha()} +into your own package under the permissive MIT license. Either use +\code{usethis::use_standalone("trevorld/gridpattern", "standalone-update_alpha.R")} +or copy the file \code{update_alpha.R} into your \code{R} directory and +add \code{grDevices}, \code{grid}, and \code{rlang} to the \code{Imports} of your \code{DESCRIPTION} file. +} + \examples{ -# Typical colour input +# Typical color input update_alpha("red", 0.5) -if (utils::packageVersion("grid") > "4.1") { - # Pattern input +# Pattern input +if (getRversion() >= "4.2" && requireNamespace("grid", quietly = TRUE)) { update_alpha(list(grid::linearGradient()), 0.5) } } diff --git a/tests/testthat/test_geometry.R b/tests/testthat/test_geometry.R index b924c7e..5570ab4 100644 --- a/tests/testthat/test_geometry.R +++ b/tests/testthat/test_geometry.R @@ -77,7 +77,7 @@ test_that("geometry patterns work as expected", { pch = params$pattern_shape, size = unit(params$pattern_size, 'char'), default.units = "npc", - gp = grid::gpar(col = alpha(params$pattern_fill, params$pattern_alpha)) + gp = grid::gpar(col = update_alpha(params$pattern_fill, params$pattern_alpha)) ) } options(ggpattern_geometry_funcs = list(centroid = centroid_dot_pattern))