library(grid)
library(ggplot2)
library(ggpattern)
#> 
#> Attaching package: 'ggpattern'
#> The following objects are masked from 'package:ggplot2':
#> 
#>     flip_data, flipped_names, gg_dep, has_flipped_aes, remove_missing,
#>     should_stop, waiver

Intersecting geometry

While developing {ggpattern}, two different methods of creating patterned areas were explored:

  1. Treat the pattern and the boundary of the drawn object as {sf} objects and treat the “cutting out” of the pattern as a spatial intersection operation. The pattern would then be converted from {sf} back to grid grobs and included in the plot.
  2. Treat everything as {grid} objects and use gridGeometry::polyclipGrob to create a special type of intersection grob (grid graphics object).

Overlapping geometry with gridGeometry::polyclipGrob

Build a cross as just the accumulation of 2 grobs

The two boxes are clearly seen to be just overlapping rectangles.


horizontal_box <- rectGrob(
  x      = 0.5,
  y      = 0.5,
  width  = 0.5, 
  height = 0.1,
  gp = gpar(fill = 'black', alpha = 0.5)
)

vertical_box <- rectGrob(
  x      = 0.5,
  y      = 0.5,
  width  = 0.1, 
  height = 0.5,
  gp = gpar(fill = 'black', alpha = 0.5)
)

cross <- grobTree(horizontal_box, vertical_box)

plot.new()
grid.draw(cross)

Applying polyclipGrob does funky things with the overlap at the centre.

This looks like the renderer is applying the even-odd rule and determining that in the centre of the cross there are an even number of crossings, so it must lie outside the drawn shape.

boundary <- rectGrob(
  x      = 0.5, 
  y      = 0.5,
  width  = 0.3, 
  height = 0.3
)

intersection_grob <- gridGeometry::polyclipGrob(
  boundary, cross,
  gp = gpar(
    fill = 'black'
  )
)

plot.new()
grid.draw(intersection_grob)

Build the cross as the union object using polyclipGrob

By using a union operation, the two boxes actually become a single entity

cross <- gridGeometry::polyclipGrob(
  horizontal_box, vertical_box,
  op = 'union',
  gp = gpar(fill = 'black', alpha = 0.5)
)

plot.new()
grid.draw(cross)

Applying polyclipGrob now does correct intersection operation

intersection_grob <- gridGeometry::polyclipGrob(
  boundary, cross,
  gp = gpar(
    fill = 'black',
    alpha = 0.5
  )
)

plot.new()
grid.draw(intersection_grob)

The problem with union operation in polyclipGrob

The great thing about polyclipGrob is that it gives the correct result.

But main problem with using it is that it is very slow for my use case.

In the following 2 plots, both an {sf} solution and a {grid} solution are presented.

  • The {sf} solution is approximately 20x faster
  • The {sf} behaviour at the crossover between two lines is different from the {grid} solution.

Using {sf} based intersection

df <- data.frame(trt = c("a", "b", "c"), outcome = c(2.3, 1.9, 3.2))
p <- ggplot(df, aes(trt, outcome)) +
  geom_col_pattern(pattern = 'crosshatch', fill='white', colour='black', pattern_spacing = 0.025, pattern_alpha = 0.5) +
  theme_bw() +
  labs(title = "ggpattern::geom_col_pattern()") +
  theme(legend.key.size = unit(1.5, 'cm')) +
  coord_fixed(ratio = 1/2)


outfile <- tempfile(fileext = ".png")

system.time({
  png(outfile, width=800, height=600)
  print(p)
  dev.off()
})
#>    user  system elapsed 
#>   0.267   0.020   0.311

# knitr::include_graphics(outfile)
p

Using {grid} based union and intersection via pattern = 'crosshatch2'

df <- data.frame(trt = c("a", "b", "c"), outcome = c(2.3, 1.9, 3.2))
p <- ggplot(df, aes(trt, outcome)) +
  geom_col_pattern(pattern = 'crosshatch2', fill='white', colour='black', pattern_spacing = 0.025, pattern_alpha = 0.5) +
  theme_bw() +
  labs(title = "ggpattern::geom_col_pattern()") +
  theme(legend.key.size = unit(1.5, 'cm')) +
  coord_fixed(ratio = 1/2)

outfile <- tempfile(fileext = ".png")
system.time({
  png(outfile, width=800, height=600)
  print(p)
  dev.off()
})
#> Warning: create_pattern_grobs() - pattern not supported:

#> Warning: create_pattern_grobs() - pattern not supported:

#> Warning: create_pattern_grobs() - pattern not supported:
#>    user  system elapsed 
#>   0.116   0.014   0.149

# knitr::include_graphics(outfile)
p
#> Warning: create_pattern_grobs() - pattern not supported:

#> Warning: create_pattern_grobs() - pattern not supported:

#> Warning: create_pattern_grobs() - pattern not supported:

Stack overflow

Occasionally for complex intersections, polyclipGrob gives Error: protect(): protection stack overflow