Search code examples
rggplot2plotvisualizationr-grid

Insert rectangle outside of ggplot to visualize plot segments


I hope you can help me. I have the idea of visualizing segments within a plot with a rectangle that can be placed next to the y or x-axis which means that it would be outside of the plot area. It should look similar as in the image below:

enter image description here

I tried to reach the mentioned output by trying two different approaches:

  1. I created two viewports with the grid package and put the plot in one viewport that I placed at the bottom and one viewport on top of that. The big problem here is that I need the coordinates from where the grey background panel of the ggplot starts so I can place the top viewport exactly there, so that the segments conincide with the x-axis length. My code looked like following:
container_viewport <- viewport(x=0,y=0,height=1,width=1,just = c("left","bottom"))
pushViewport(container_viewport)
grid.draw(rectGrob())
popViewport()

section_viewport <- viewport(x=0.055,y=0.99,height=0.085,width=0.935,just=c("left","top"))
pushViewport(section_viewport)

plot_obj <- ggplot_build(testplot)
plot_data <- plot_obj$data[[1]]

grid.draw(rectGrob(gp = gpar(col = "red")))
popViewport()

plot_viewport <- viewport(x=0,y=0,height=0.9,width=1,just=c("left","bottom"))
pushViewport(plot_viewport)
grid.draw(ggplotGrob(testplot))
popViewport()

enter image description here

This looks fine but I had to hardcode the coordinates of the viewport at the top.

  1. I used grid.arrange() to arrange to stack the plots vertically (instead of a grob for the rectangle like in the other approach I create a ggplot instead for that). Here, basically the same problem exists, since I somehow need to put the plot representing the rectangle at the top in the right position on the x-axis. My code looked like following:
p1 <- plot_data %>%
  ggplot()+
  geom_rect(aes(xmin=-Inf,xmax=Inf,ymin=-Inf,ymax=Inf))
p2 <- testplot

test_plot <- grid.arrange(p1,p2,heights=c(1,10))

This approach does not work that good.

Since I would like to create a solution that can be applied generally, trial and error with the coordinates of the viewport is no option since the length of the y-axis label or tick labels can vary and therefore the length and coordinates of the background panel. When this step is done the segmentation of the rectangle should be no problem anymore.

Maybe this is just not possible but if then I would appreciate any help. Thank you!


Solution

  • I would probably use patchwork here. Let's start by replicating your plot:

    library(ggplot2)
    library(patchwork)
    
    p <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
      geom_point(color = "red") +
      labs(x = "test", y = "test")
    
    p
    

    That looks very similar. Now we define (in our own co-ordinates) where we want the section split to occur on the x axis.

    section_split <- 5.25
    

    Using just this number, we add rectangles and text annotations that cover a copy of our original plot, and remove its axis annotations using theme_void:

    p2 <- p + 
      annotate("rect", xmin = c(-Inf, section_split), ymin = c(-Inf, -Inf),
                 xmax = c(section_split, Inf), ymax = c(Inf, Inf),
                 fill = c("#00a2e8", "#ff7f27")) +
      annotate("text", label = c("Section A", "Section B"), size = 6,
               y = rep(mean(layer_scales(p)$y$range$range), 2),
               x = c((min(layer_scales(p)$x$range$range) + section_split)/2,
                     (max(layer_scales(p)$x$range$range) + section_split)/2)) +
      theme_void()
    

    Now we just draw this second plot above our first, adjusting the relative heights to about 1:10

    p2/p + plot_layout(heights = c(1, 10))
    

    The benefit of doing it this way is that, since we copied the original plot, the positional mapping of the x axis is identical between the two plots, and patchwork will automatically line up the panels.

    Created on 2023-02-04 with reprex v2.0.2