10

I want to draw a rectangular annotation that will span over the facet borders in ggplot.

library(ggplot2)
myPlot <- ggplot(mpg, aes(displ, hwy)) + 
    geom_point() + 
    facet_grid(class ~ .)

# add annotation
myPlot +
  annotate("rect", xmin = 3, xmax = 4, ymin = -Inf, ymax = Inf, fill = "green", alpha = 1/5)

What I have so far:

What I have so far

I'd like to draw 1 large rectangle that spans the facet edges like so:

Messy paint edit of what I want

Is there a way to do this with built-in ggplot2 code or with ggforce or do I have to mess around with grid? My ideal use-case would still allow me to have myPlot as a ggplot object which is why I've avoided any complicated grid stuff until this point.

user131291
  • 147
  • 6
  • 1
    Have you tried [this approach](https://stackoverflow.com/a/31691313/1552004)? – Dan Jun 28 '18 at 17:43
  • 2
    This question deserves to be upvoted since you have provided expected behaviors. – Ṃųỻịgǻňạcểơửṩ Jun 28 '18 at 17:43
  • @Lyngbakr, thanks for the link, for some reason my googling didn't turn those up. I'm playing around with that strategy right now, but am having some difficulty adapting them to ggplot 2.2.1 (as seems to be common with new ggplot versions and this approach). I'll update with a solution if I get one. – user131291 Jun 28 '18 at 19:27

1 Answers1

3

An approach that uses grid functions to edit your plot.

It is easy to draw a rectangle within a grid viewport. Is it possible to construct a grid viewport that will overlay the set of ggplot panels exactly? The answer is "Yes". The trick, for drawing the rectangle, is to get the x-axis "native" coordinates from the ggplot_build information into the grid viewport.

library(ggplot2)
library(grid)

plot <- ggplot(mpg, aes(displ, hwy)) + 
    geom_point() + 
    facet_grid(class ~ .) 

plot

# Construct a grid.layout that is the same as the ggplot layout
gt = ggplotGrob(plot)
lay = grid.layout(nrow = length(gt$heights), ncol = length(gt$widths),
                  widths = gt$widths, heights = gt$heights)

# Push to this viewport
pushViewport(viewport(layout = lay))

# Within the layout, push to a viewport that spans the plot panels.
pos = gt$layout[grep("panel", gt$layout$name), c("t", "l")]  # Positions of the panels
pushViewport(viewport(layout.pos.col = pos$l, layout.pos.row = min(pos$t):max(pos$t)))

# Get the limits of the ggplot's x-scale, including any expansion.
## For ggplot ver 2.2.1
# x.axis.limits = ggplot_build(plot)$layout$panel_ranges[[1]][["x.range"]]

## For ver 3.0.0
# axis.limits = ggplot_build(plot)$layout$panel_params[[1]]$x.range
# But possibly the following will be more immune to future changes to ggplot
x.axis.limits = summarise_layout(ggplot_build(plot))[1, c('xmin', 'xmax')]


# Set up a data viewport,
# so that the x-axis units are, in effect, "native", 
# but y-axis units are, in effect, "npc".
# And push to the data viewport.
pushViewport(dataViewport(yscale = c(0, 1), 
                          xscale = x.axis.limits))

# Draw the rectangle
grid.rect(x = 3, y = 0,
          width = 1, height = 1,
          just = c("left", "bottom"), default.units = "native",
          gp = gpar(fill = "green", col = "transparent", alpha = .2))

# Back to the root viewport
upViewport(0)

enter image description here

Sandy Muspratt
  • 31,719
  • 12
  • 116
  • 122
  • This works great! Thanks. It required a small edit to update for ggplot v 2.2.1 (from [this gist](https://gist.github.com/tomhopper/9076152) see 'stefanedwards' comment on 6/20/2018) – user131291 Jun 29 '18 at 17:37
  • There is actually a small issue with this solution in that it does not seem to account for the existence of axis padding or something because the rectangle is not exactly positioned at the target coordinates. – user131291 Jun 29 '18 at 17:48
  • @user131291 I was using ggplot2 ver 2.2.1, and the original response worked fine. I am reversing the edit, but adding an update for the soon-to-be-released ggplot version 2.3.0. BTW, your method for selecting the range for the x-axis (in your edit, selecting `layout$panel_scales_x`) did not allow for expansion on the x-axis. – Sandy Muspratt Jun 30 '18 at 03:54