Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patterned fills don't render segmentsGrobs. #5710

Closed
dansmith01 opened this issue Feb 23, 2024 · 6 comments
Closed

Patterned fills don't render segmentsGrobs. #5710

dansmith01 opened this issue Feb 23, 2024 · 6 comments

Comments

@dansmith01
Copy link

I was excited to read the recent blog post announcing support for patterned fills! However, it doesn't seem to be working with line, segment, or bezier grobs.

Below is some code adapted from the blog post linked above.

library(grid)
library(ggplot2)

# gpar everywhere to rule out an "invisible lines" issue
gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")

checkered <- pattern(
  rectGrob(x = c(0.25, 0.75), y = c(0.25, 0.75), width = 0.5, height = 0.5, gp = gp),
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp )

striped <- pattern(
  segmentsGrob(x0 = c(0, 0.5), y0 = c(0.5, 0), x1 = c(0.5, 1), y1 = c(1, 0.5), gp = gp), 
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp )

ggplot(mpg, aes(factor(cyl), fill = factor(cyl))) +
  geom_bar() +
  scale_fill_manual(values = list(striped, checkered, striped, checkered))

Rplot

The segment grob draws fine on it's own:

grid.newpage()
grid.draw(segmentsGrob(x0 = c(0, 0.5), y0 = c(0.5, 0), x1 = c(0.5, 1), y1 = c(1, 0.5), gp = gp))

Rplot01

I'm working with RStudio 2023.12.1 on Windows 10. R 4.3.2, ggplot2 3.5.0, all other packages up to date as well. I've tried switching RStudio to both "Cairo PNG" and "AGG" in the General/Graphics options.

@yutannihilation
Copy link
Member

It seems it's not that these Grobs don't work, but the parameters are a bit tricky. I'm not familiar with these things, and don't figure out the rule here (I'm also on Windows and use the AGG device, so it's possible the result is different on macOS or Linux).

library(grid)
library(ggplot2)

gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")

do_plot <- function(x0, y0, x1, y1) {
  checkered <- pattern(
    rectGrob(x = c(0.25, 0.75), y = c(0.25, 0.75), width = 0.5, height = 0.5, gp = gp),
    width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp
  )
  
  striped <- pattern(
    segmentsGrob(x0 = x0, y0 = y0, x1 = x1, y1 = y1, gp = gp), 
    width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp
  )
  
  ggplot(mpg, aes(factor(cyl), fill = factor(cyl))) +
    geom_bar() +
    scale_fill_manual(values = list(striped, checkered, striped, checkered)) +
    ggtitle(glue::glue("({x0}, {y0}) - ({x1}, {y1})"))
}

patchwork::wrap_plots(
  do_plot(0, 0, 1, 1   ),
  do_plot(0, 0, 1, 0.9),
  do_plot(0, 0, 1, 0.8),
  do_plot(0, 0, 1, 0.7)
)

Created on 2024-02-24 with reprex v2.1.0

To be clear, ggplot2 does nothing here. You can experiment with grid like this:

library(grid)

gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")


striped <- pattern(
  segmentsGrob(x0 = c(0, 0.5), y0 = c(0.5, 0), x1 = c(0.5, 1), y1 = c(1, 0.5), gp = gp), 
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp )

grid.newpage()
grid.rect(width  = 0.5, height = 0.5, gp = gpar(fill = striped))

@yutannihilation
Copy link
Member

Okay, I think now I understand. pattern() clips the pattern from the canvas by specified x, y, width, and height. As you specify the box as 5mm x 5mm, it's very small.

library(grid)

gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")

seg <- segmentsGrob(x0 = c(0, 0.5), y0 = c(0.5, 0), x1 = c(0.5, 1), y1 = c(1, 0.5), gp = gp)

grid.newpage()
grid.draw(seg)
grid.rect(
  x = 0.5, y = 0.5, # default value of pattern()
  width = unit(5, "mm"), height = unit(5, "mm"),
  gp = gpar(col = "red", fill = "transparent")
)

Created on 2024-02-24 with reprex v2.1.0

So, if you tweak the position of the segments, the pattern should be drawn.

library(grid)
library(ggplot2)

# gpar everywhere to rule out an "invisible lines" issue
gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")

checkered <- pattern(
  rectGrob(x = c(0.25, 0.75), y = c(0.25, 0.75), width = 0.5, height = 0.5, gp = gp),
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp )

mm_rel <- function(...) {
  unit(0.5, "npc") + unit((base::c(...) - 0.5) * 5, "mm")
}

striped <- pattern(
  segmentsGrob(x0 = mm_rel(0, 0.5), y0 = mm_rel(0.5, 0), x1 = mm_rel(0.5, 1), y1 = mm_rel(1, 0.5), gp = gp), 
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp )

ggplot(mpg, aes(factor(cyl), fill = factor(cyl))) +
  geom_bar() +
  scale_fill_manual(values = list(striped, checkered, striped, checkered))

Created on 2024-02-24 with reprex v2.1.0

@dansmith01
Copy link
Author

Wow! Thanks so much for this!

I'd written off those parameters as not the problem since they worked for the checkered pattern, but if I understand correctly, it was clipping to just the center corners of the checkered pattern too?

Thanks again!! 😃

@yutannihilation
Copy link
Member

Yeah. You should have noticed if the checkered pattern has an odd number of rectangles.

library(grid)
library(ggplot2)

# gpar everywhere to rule out an "invisible lines" issue
gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")

rect2 <- rectGrob(x = c(0.25, 0.75), y = c(0.25, 0.75), width = 0.5, height = 0.5, gp = gp)
checkered2 <- pattern(rect2, width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp)

rect3 <- rectGrob(x = c(1, 3, 5) / 6, y = c(1, 3, 5) / 6, width = 1 / 3, height = 1 / 3, gp = gp)
checkered3 <- pattern(rect3, width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", gp = gp)

ggplot(mpg, aes(factor(cyl), fill = factor(cyl))) +
  geom_bar() +
  scale_fill_manual(values = list(checkered2, checkered3, checkered2, checkered3))

grid.newpage()
grid.draw(rect2)
grid.rect(x = 0.5, y = 0.5, width = unit(5, "mm"), height = unit(5, "mm"), gp = gpar(col = "red", fill = "transparent"))

grid.newpage()
grid.draw(rect3)
grid.rect(x = 0.5, y = 0.5, width = unit(5, "mm"), height = unit(5, "mm"), gp = gpar(col = "red", fill = "transparent"))

Created on 2024-02-24 with reprex v2.1.0

@yutannihilation
Copy link
Member

Anyway, I'm closing as this is not a problem. I found these resources useful. Hope this helps!

https://www.stat.auckland.ac.nz/~paul/Reports/GraphicsEngine/vecpat/vecpat.html
https://coolbutuseless.github.io/2021/07/01/r-v4.1.0-grid-graphics-new-feature-patterns/

@dansmith01
Copy link
Author

Yes, it's definitely not any kind of bug with ggplot2.

And thank you so much for the help! It makes a lot more sense to me now how to proceed.

For anyone coming across this post in the future - I found that defining a viewport is another solution:

library(grid)
library(ggplot2)

# Graphics parameters and viewport
gp <- gpar(col = "black", fill = "black", lwd = 2, lty = "solid")
vp <- viewport(width = unit(5, "mm"), height = unit(5, "mm"))

striped <- pattern(
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat",
  segmentsGrob(gp = gp, vp = vp, x0 = 0, y0 = 0, x1 =1, y1 = 1) )

shingle <- pattern(
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat",
  segmentsGrob(gp = gp, vp = vp,
      x0 = c(0, 0, 0, 0.6),  y0 = c(0, 0.5, 0, 0.5),
      x1 = c(1, 1, 0.2, 0.8), y1 = c(0, 0.5, 0.5, 1) ))

fish <- pattern(
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat",
  bezierGrob(gp = gp, vp = vp, id = rep(1:3, each = 4),
    x  = {x <- c(0, .25, .75, 1);         c(x, x - 0.5, x + 0.5) },
    y  = {y <- c(0.5, -0.19, -0.19, 0.5); c(y, y + 0.5, y + 0.5) } ))

checkered <- pattern(
  width = unit(5, "mm"), height = unit(5, "mm"), extend = "repeat", 
  rectGrob(gp = gp, vp = vp, 
    x = c(0.25, 0.75), y = c(0.25, 0.75), width = 0.5, height = 0.5 ))

ggplot(mpg, aes(factor(cyl), fill = factor(cyl))) +
  geom_bar(color = "black") +
  scale_fill_manual(values = list(striped, shingle, fish, checkered))

Rplot

Getting the striped tiles to transition seamlessly is my next challenge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants