rggplot2grob

Grobs are not plotted at all or in wrong position


I am having trouble understanding how grobs are being placed into the plotting area. Here's an example of what I mean. Let's assume I have the following plot.

library(tidyverse)
p <- mpg %>%
  ggplot(aes(year)) +
  geom_bar() 
p

I understand that I can get a list of grobs from that and plot it like so.

l <- ggplotGrob(p)
grid::grid.newpage()
grid::grid.draw(l)

From the structure of list l I also understand that the rectangles are drawn as rectGrobs. I found these in.

grobs <- l$grobs[[6]]
rect_grobs <- grobs$children[[3]]

But if I want to plot at least one rectangle using the data straight from rect_grobs, nothing happens. This is independent of whether I create a new canvas with grid::grid.newpage() or not.

# This draws nothing
grid::grid.draw(
  grid::rectGrob(
    x = rect_grobs$x[1],
    y = rect_grobs$y[1],
    height = rect_grobs$height[1],
    width = rect_grobs$width[1],
    just = rect_grobs$just[1],
  )
)

Yet if I plug in the values from rect_grobs by hand, then a rectangle is drawn in the wrong position. I suspect that this is related to viewports but I tried looking in l, grobs and rect_grobs but I found no clue what viewport so set. Wherever I look, there is only vp = NULL.

# This draws in wrong position
grid::grid.draw(
  grid::rectGrob(
    x = 0.0455,
    y = 0.955,
    width = 0.431,
    height = 0.909,
    just = 'left'
  )
)

So, my questions are

  1. Why does the code not draw a rectangle if I take the values straight from the rect_grobs list?
  2. How do I get the rectangle into the correct position if I manually add it?

Solution

  • The reason why rect_grobs doesn't draw is indeed that it has no viewport. If a grob has no viewport set, it will inherit from its parent, but since you have plucked it out of a gTable, it has no parent. This is easy to rectify by giving it a default viewport:

    rect_grobs$vp <- grid::viewport()
    grid::grid.draw(rect_grobs)
    

    enter image description here

    As long as we specify the correct just, we get the rectangle in the same place:

    red_rect <-   grid::rectGrob(
        x = 0.0455,
        y = 0.955,
        width = 0.431,
        height = 0.909,
        just = c("left", "top"),
        gp = grid::gpar(fill = "red")
      )
    
    grid::grid.draw(red_rect)
    

    enter image description here

    However, this is not the position the rectangle would be in a ggplot. That's because in ggplot the panel has its own viewport. To draw the rectangle in this context, we need to navigate to the correct viewport before calling grid.draw

    plot(p)
    
    grid::seekViewport(grid::vpPath("layout", "panel.7-5-7-5"))
    
    grid::grid.draw(red_rect)
    

    enter image description here