Developing Patterns

library("ggplot2")
library("ggpattern")

Developing Patterns - overview

To develop a pattern for use with {ggpattern} (and any other packages that use {gridpattern}) you will need to:

  1. Decide whether this is a geometry-based pattern or an array-based pattern.
  2. Create a function with the required arguments and return value
  3. Use gridpattern::register_pattern() to let {gridpattern} know that a certain pattern name should be delegated to your function.

This vignette shows how to:

  • write a create_pattern() function of the correct signature for a geometry-based pattern or array-based pattern
  • Instruct {ggpattern} on where to find this user-defined pattern

Pattern Classification - geometry-based and array-based

There are only 2 high-level classes of pattern supported by gridpattern - geometry-based and array-based.

Geometry-based patterns create a series of geometry objects and trim them (using sf::st_intersection(), gridGeometry::polyclipGrob(), gridpattern::alphaMaskGrob(), etc) to be within the boundary of the grob. For example, the ‘stripes’ pattern in gridpattern is a series of equally spaced rectangular polygons.

Array-based patterns are RGBA image arrays. Any supplied image will be processed by gridpattern to ensure it is masked to only apply to the area within the grob’s boundary.

{ggpattern} Aesthetics - Descriptions and Defaults

Although custom {gridpattern} pattern parameters need not limit itself to the set of aesthetics provided by {ggpattern} doing so may make your pattern more useful for others:

A default of “pattern (x)” means the aesthetic is unset by default (internally NaN) so the pattern’s own default applies, which for most patterns is {gridpattern}’s generic default x (see the documentation of the corresponding gridpattern::grid.pattern_<name>() function).

{ggpattern} aesthetic summary - click to open/close
aesthetic description default possible values
pattern Name of the pattern to draw ‘stripe’ gridpattern::names_pattern
pattern_alpha Alpha pattern (NA) value in range [0, 1] or NA
pattern_angle Rotation angle (entire pattern) pattern (30) angle in degrees
pattern_aspect_ratio Aspect ratio adjustment plot / key aspect ratio usual range [0.01, 10]
pattern_colour Stroke colour pattern (‘grey20’) colour
pattern_density Approx. fraction of area the pattern fills pattern (0.2) value in range [0, 1]
pattern_filename Image filename/URL pattern (’’) Filename/URL
pattern_fill Fill colour pattern (‘grey80’) colour (or grid pattern/gradient)
pattern_fill2 Second fill colour pattern (‘#4169E1’) colour (or grid pattern/gradient)
pattern_filter Image scaling filter pattern (‘lanczos’) magick::filter_types()
pattern_frequency Frequency pattern (0.1)
pattern_gravity Image placement pattern (‘center’) magick::gravity_types()
pattern_grid Pattern grid type pattern (‘square’) ‘square’, ‘hex’, ‘hex_circle’
pattern_key_scale_factor Scale factor for pattern in legend 1
pattern_linetype Stroke linetype pattern (1) linetype
pattern_option_1 - 5 Generic options for expansion pattern (0)
pattern_orientation Orientation pattern (‘vertical’) ‘vertical’, ‘horizontal’, ‘radial’
pattern_phase Phase pattern (0)
pattern_res Pattern resolution (pixels per inch) ≈ device resolution
pattern_rot Rotation angle (shape within pattern) pattern (0) angle in degrees
pattern_scale Scale pattern (1) Multiplier
pattern_shape Plotting shape pattern (1) shapes
pattern_linewidth Stroke linewidth pattern (0.5) linewidth
pattern_size Fontsize (or legacy stroke linewidth) pattern (0.5) fontsize (or linewidth)
pattern_spacing Spacing between repetitions of pattern pattern (0.05) value in pattern_units grid units
pattern_subtype Generic control option pattern (NA) pattern-dependent
pattern_type Generic control option pattern (NA) pattern-dependent
pattern_units Pattern grid unit pattern (‘snpc’) grid::unit() unit i.e. ‘snpc’, ‘cm’, and ‘inches’ (not ‘npc’, ‘native’, or ‘null’)
pattern_xoffset Shift pattern along x axis pattern (0) value in pattern_units grid units
pattern_yoffset Shift pattern along y axis pattern (0) value in pattern_units grid units

Note {ggpattern} may also pass other geom aesthetics of possible interest such as fill. Also note that {ggpattern} will only pass pattern aesthetics values of length one but if the pattern is called directly by gridpattern::patternGrob() then the pattern may be passed pattern parameters of arbitrary length.

Geometry-based pattern functions - Formal Arguments and Return Values

All geometry-based pattern creation functions must:

  1. Have the exact function signature: function(params, boundary_df, aspect_ratio, legend)

    • params - parameters for the pattern (the aesthetics) e.g pattern_fill
    • boundary_df - data.frame containing polygon information i.e. The polygon_df format.
    • aspect_ratio - the best guess that {gridpattern} / {ggpattern} is able to make as to the aspect ratio of the viewport in which this pattern is being rendered.
    • legend logical value to indicate whether or not this function is being called to render a key legend or the in-place geom fill.
  2. Return a grid grob object. This can be any valid grob including a grid::grobTree(). The user should make sure it lies within the boundary represented by boundary_df either by clipping with functions like sf::st_intersection(), gridGeometry::polyclipGrob(), gridpattern::alphaMaskGrob(), etc. or using bounded grob functions like gridpattern::patternGrob() or grid::polygonGrob().

Array-based pattern functions - Formal Arguments and Return Values

All array-based pattern creation functions must:

  1. Have the exact function signature: function(width, height, params, legend)
    • width,height - dimensions of the bounding box of the geom area
    • params - parameters from the geom (the aesthetics) e.g pattern_fill
    • legend logical value to indicate whether or not this function is being called to render a key legend or the in-place geom fill.
  2. Return a 3D array of RGBA values (all values in the range [0, 1]). gridpattern itself will mask this image so that it only applies to the area within the grob’s boundary.

The polygon_df data.frame format

The polygon_df is a very simple data.frame format to contain polygon values. This is used to pass the coordinates of the geom boundary from the geom to the pattern generating function.

It contains only ‘x’ and ‘y’ columns for the coordinates, and an ‘id’ column used to signify which polygon the coordinates belong to.

The following polygon_df data.frame contains 2 polygons:

example data in ‘polygon_df’ format
x y id
0 0 1
1 0 1
1 1 1
0 1 1
0 0 2
2 0 2
2 1 2
0 1 2

Associating a function with {gridpattern} pattern name

Use gridpattern::register_pattern() to associate a pattern function with a pattern name. Use kind = "geometry" (the default) for geometry-based patterns and kind = "array" for array-based patterns.

gridpattern::register_pattern("your_pattern_name", your_geometry_pattern_function)
gridpattern::register_pattern("your_pattern_name", your_array_pattern_function, kind = "array")
  • Packages should register their patterns in their .onLoad() function (ideally wrapped in rlang::on_package_load()). See the “Registering patterns from a package” section of {gridpattern}’s Developing Patterns vignette (vignette("developing-patterns", package = "gridpattern")) for a template.
  • Patterns can always be referred to by their qualified name "owner::name" e.g. "pkg::name", "gridpattern::stripe", or "R_GlobalEnv::name" (for patterns registered by the user).
  • A bare name "name" resolves to the pattern the user has preferred with gridpattern::prefer_pattern() (if any) and otherwise to the pattern of the only owner (the user, {gridpattern}, or a package) with that name. If more than one owner has a pattern with that name and no preference has been declared there is a conflict and using the bare name throws an error. Resolve it with either a qualified name or a preference e.g. prefer_pattern("R_GlobalEnv::stripe") lets pattern = "stripe" use a user’s custom “stripe” pattern instead of the builtin one. gridpattern::conflicting_patterns() lists the conflicting pattern names.
  • register_pattern()’s defaults argument may set pattern-specific default parameter values e.g. defaults = list(pattern_type = "running", pattern_spacing = 0.1). Most {ggpattern} pattern aesthetics are unset by default (internally NaN, see the table above) so these pattern-specific defaults apply under {ggpattern} too (as do defaults for any parameter {ggpattern} doesn’t know about) and a pattern that declares no default for one of these gets {gridpattern}’s generic default.
  • gridpattern::registered_patterns() lists all the available patterns, gridpattern::has_pattern() checks whether a pattern name resolves to a pattern, gridpattern::unregister_pattern() removes a registration, and gridpattern::local_pattern() registers a pattern only until the current function (or test) exits.
  • The legacy ggpattern_geometry_funcs / ggpattern_array_funcs global options (named lists of pattern functions) are still supported but we no longer recommend them since setting them may clobber patterns registered by other packages.

Example: Three-color polygon tilings (adapting {ggplot2} aesthetics)

All geometry-based pattern creation functions must:

  1. Have the exact function signature: function(params, boundary_df, aspect_ratio, legend)
    • params - parameters from the geom (the aesthetics) e.g pattern_fill
    • boundary_df - data.frame containing polygon information i.e. The polygon_df format.
    • aspect_ratio - the best guess that {gridpattern} / {ggpattern} is able to make as to the aspect ratio of the viewport in which this pattern is being rendered.
    • legend logical value to indicate whether or not this function is being called to render a key legend or the in-place geom fill.
  2. Return a grid grob bounded by the boundary represented by boundary_df (including grid structures like a grid::grobTree()).

For this example we’ll adapt the built-in ‘polygon_tiling’ pattern to create a new ‘tiling3’ pattern that fills the polygon tiling with three colors: the fill aesthetic, the pattern_fill aesthetic, and their “average” color.

tiling3_pattern <- function(params, boundary_df, aspect_ratio, legend = FALSE) {
    args <- as.list(params)
    args <- args[grep("^pattern_", names(args))]

    # hexagonal tiling using "regular_polygon" pattern
    args$pattern <- "polygon_tiling"

    # three-color tiling using `fill`, `pattern_fill` and their "average"
    avg_col <- gridpattern::mean_col(params$fill, params$pattern_fill)
    args$pattern_fill <- c(params$fill, avg_col, args$pattern_fill)

    args$x <- boundary_df$x
    args$y <- boundary_df$y
    args$id <- boundary_df$id
    args$prefix <- ""

    do.call(gridpattern::patternGrob, args)
}

We register the function with gridpattern::register_pattern(). The name corresponds to the pattern name used with the geom - in this case we will be using pattern = 'tiling3'.

gridpattern::register_pattern("tiling3", tiling3_pattern)
df <- data.frame(trt = c("a", "b", "c"), outcome = c(2.3, 1.9, 3.2))
ggplot(df, aes(trt, outcome)) +
    geom_col_pattern(aes(fill = trt, pattern_type = trt),
                     pattern = 'tiling3',
                     pattern_angle = 45,
                     pattern_spacing = 0.15) +
    scale_pattern_type_manual(values = c("hexagonal", "tetrakis_square", "rhombille")) +
    theme(legend.key.size = unit(1.2, 'cm'))

ggplot2 plot using a custom polygon tiling pattern fill.

Other examples

The {gridpattern} package contains a vignette on developing {gridpattern} / {ggpattern} patterns with a few more examples including an example of layering patterns on top of each other. Use vignette("developing-patterns", package="gridpattern") to see it.

Also the {gridpattern} source contains the full source code for 21patterns: https://github.com/trevorld/gridpattern/tree/main/R

    • Developing Patterns - overview
    • Pattern Classification - geometry-based and array-based
    • {ggpattern} Aesthetics - Descriptions and Defaults
    • Geometry-based pattern functions - Formal Arguments and Return Values
    • Array-based pattern functions - Formal Arguments and Return Values
    • The polygon_df data.frame format
    • Associating a function with {gridpattern} pattern name
    • Example: Three-color polygon tilings (adapting {ggplot2} aesthetics)
    • Other examples