Search code examples
rggplot2facetgtablegrob

rectGrob / textGrob not appearing on plot when using gtable_add_grob


I'm using grid graphics in order to position strip labels on a facet_wrap similar to facet_grid (like this question), and am using the code provided in this answer. I am trying to facet by 2 variables, covariate name and ecoregion. I adjusted the code slightly, because some labels in my data are too long, and I needed to be able to change the font size. Here's my code:

# Base plot:

  occPlot_all <- ggplot(data= occpred_all) +
  geom_ribbon(aes(x= occ_cov, ymin= lower, ymax= upper), fill= "gray80") +
  geom_line(aes(x= occ_cov, y= Predicted),color= "seagreen4") +
  labs(x="Percent Landcover Type", y="Probability of Site Occupancy") +
  theme(panel.border=element_rect(color="black",fill="transparent"), panel.background = element_rect(fill="white"))

g1 <- occPlot_all +
  facet_wrap(c("CovariateName", "Ecoregion"), scales = "free") +
  theme(strip.background = element_blank(),
        strip.text = element_blank())

g2 <- occPlot_all +
  facet_grid(c("CovariateName", "Ecoregion"), scales = "free") 

gt1 <- ggplot_gtable(ggplot_build(g1))

gt2 <- ggplot_gtable(ggplot_build(g2))

# Add space above plots
gt1 <- gtable_add_rows(gt1, heights = unit(0.5, 'cm'), pos = 2)

# Try making top labels the same way as side labels so I can change font size
gt.top1 <- gtable_filter(gt2, 'strip-t-1')
gt.top2 <- gtable_filter(gt2, 'strip-t-2')
gt.top3 <- gtable_filter(gt2, 'strip-t-3')
gt.top4 <- gtable_filter(gt2, 'strip-t-4')
gt.top5 <- gtable_filter(gt2, 'strip-t-5')
gt.top6 <- gtable_filter(gt2, 'strip-t-6')
# instead, make my own so I can change font size
nwfm_grob <- grobTree(rectGrob(height = gt.top1$heights[1],  
                                   gp = gpar(fill = "grey82", col = "grey82")),
                          textGrob(expression("Northwestern Forested Mountains"),
                                   gp = gpar(col = "black", fontsize = 6)))

panel_id_top <- gt1$layout[grep('strip-t.+1$', gt1$layout$name),]

# Add top labels
gt1 <- gtable_add_grob(gt1, gt.top1, t = 2, l = panel_id_top$l[1])
gt1 <- gtable_add_grob(gt1, gt.top2, t = 2, l = panel_id_top$l[2])
gt1 <- gtable_add_grob(gt1, gt.top3, t = 2, l = panel_id_top$l[3])
gt1 <- gtable_add_grob(gt1, gt.top4, t = 2, l = panel_id_top$l[4])
gt1 <- gtable_add_grob(gt1, gt.top5, t = 2, l = panel_id_top$l[5])
gt1 <- gtable_add_grob(gt1, gt.top6, t = 2, l = panel_id_top$l[6])
# nwfm grob -- THE ONE THAT'S NOT APPEARING
gt1 <- gtable_add_grob(gt1, nwfm_grob, t = 2, l = panel_id_top$l[7]) 

### right side labels ###

gt.side1 <- gtable_filter(gt2, 'strip-r-1')
gt.side2 <- gtable_filter(gt2, 'strip-r-2')
gt.side3 <- gtable_filter(gt2, 'strip-r-3')
gt.side4 <- gtable_filter(gt2, 'strip-r-4')
gt.side5 <- gtable_filter(gt2, 'strip-r-5')
gt.side6 <- gtable_filter(gt2, 'strip-r-6')
gt.side7 <- gtable_filter(gt2, 'strip-r-7')

# Add column for right hand strip labels
gt1 = gtable_add_cols(gt1, widths=gt.side1$widths[1], pos = -1)

panel_id <- gt1$layout[grep('panel-.+1$', gt1$layout$name),]

# Make different grob to replace gt.side1 so I can change font size to make woody savanna fit 
woodysav_grob <- grobTree(rectGrob(width = gt.side1$widths[1],
                                   gp = gpar(fill = "grey82", col = "grey82")),
  textGrob(expression("Woody Savanna"), rot = -90,
    gp = gpar(col = "black", fontsize = 6)))

# Add land cover type labels
gt1 <- gtable_add_grob(gt1, woodysav_grob, t = panel_id$t[1], l = ncol(gt1)) # this one works fine
gt1 <- gtable_add_grob(gt1, gt.side2, t = panel_id$t[2], l = ncol(gt1))
gt1 <- gtable_add_grob(gt1, gt.side3, t = panel_id$t[3], l = ncol(gt1))
gt1 <- gtable_add_grob(gt1, gt.side4, t = panel_id$t[4], l = ncol(gt1))
gt1 <- gtable_add_grob(gt1, gt.side5, t = panel_id$t[5], l = ncol(gt1))
gt1 <- gtable_add_grob(gt1, gt.side6, t = panel_id$t[6], l = ncol(gt1))
gt1 <- gtable_add_grob(gt1, gt.side7, t = panel_id$t[7], l = ncol(gt1))


# New page
grid.newpage()

# Print
grid.draw(gt1)

This was the best way I was able to do this, and it works for the right-hand side labels, but when I do this with the top labels, it is not appearing on the plot (it should be at the top of the last column, next to Northern Forests--

see plot with my data here

When I view just the grob that I made, it looks fine--

see grob here

And here's a reprex--

reprex_all <- ggplot(data= iris) +
  geom_line(aes(x= Petal.Width, y= Petal.Length)) +
  theme(panel.border=element_rect(color="black",fill="transparent"), 
        panel.background = element_rect(fill="white"))

g1_r <- reprex_all +
  facet_wrap(~Species, scales = "free") +
  theme(strip.background = element_blank(),
        strip.text = element_blank())

g2_r <- reprex_all +
  facet_grid(~Species, scales = "free") 

gt1_r <- ggplot_gtable(ggplot_build(g1_r))

gt2_r <- ggplot_gtable(ggplot_build(g2_r))

# Add space above plots
gt1_r <- gtable_add_rows(gt1_r, heights = unit(0.5, 'cm'), pos = 2)

# Try making top labels the same way as side labels so I can change font size
gt_top1_r <- gtable_filter(gt2_r, 'strip-t-1')
gt_top2_r <- gtable_filter(gt2_r, 'strip-t-2')
# instead, make my own so I can change font size
virg_grob <- grobTree(rectGrob(height = gt.top1$heights[1],  
                               gp = gpar(fill = "grey82", col = "grey82")),
                      textGrob(expression("virginica"),
                               gp = gpar(col = "black", fontsize = 6)))

panel_id_top_r <- gt1_r$layout[grep('strip-t.+1$', gt1_r$layout$name),]

# Add top labels
gt1_r <- gtable_add_grob(gt1_r, gt_top1_r, t = 2, l = panel_id_top_r$l[1])
gt1_r <- gtable_add_grob(gt1_r, gt_top2_r, t = 2, l = panel_id_top_r$l[2])
gt1_r <- gtable_add_grob(gt1_r, virg_grob, t = 2, l = panel_id_top_r$l[3])


# New page
grid.newpage()

# Print
grid.draw(gt1_r)

Does anyone know why that last column label is not appearing? Thank you!


Solution

  • original plot

    Why does it not work?

    As we can see above, the original plot is missing the right grob. This seems to happen because it thinks some part of it is going out of bounds of the plot. As such it completely ignores it. So to fix this we can change any settings that move the label away from the borders of the figure. The other solution would be to directly edit the grobs created by facet_wrap.

    Solution 1: change t

    If we change the value of t when adding the grobs to the gtable using gtable_add_grob to 3 then it will show the label. However, it seems to overlap a little with the plot and looks a little awkward.

    reprex_all <- ggplot(data= iris) +
      geom_line(aes(x= Petal.Width, y= Petal.Length)) +
      theme(panel.border=element_rect(color="black",fill="transparent"), 
            panel.background = element_rect(fill="white"))
    
    g1_r <- reprex_all +
      facet_wrap(~Species, scales = "free") +
      theme(strip.background = element_blank(),
            strip.text = element_blank())
    
    g2_r <- reprex_all +
      facet_grid(~Species, scales = "free") 
    
    gt1_r <- ggplot_gtable(ggplot_build(g1_r))
    
    gt2_r <- ggplot_gtable(ggplot_build(g2_r))
    
    # Add space above plots
    gt1_r <- gtable_add_rows(gt1_r, heights = unit(0.5, 'cm'), pos = 2)
    
    # Try making top labels the same way as side labels so I can change font size
    gt_top1_r <- gtable_filter(gt2_r, 'strip-t-1')
    gt_top2_r <- gtable_filter(gt2_r, 'strip-t-2')
    # instead, make my own so I can change font size
    virg_grob <- grobTree(rectGrob(height = gt_top1_r$heights[1],  
                                   gp = gpar(fill = "grey82", col = "grey82")),
                          textGrob(expression("virginica"),
                                   gp = gpar(col = "black", fontsize = 6)))
    
    panel_id_top_r <- gt1_r$layout[grep('strip-t.+1$', gt1_r$layout$name),]
    
    # Add top labels
    gt1_r <- gtable_add_grob(gt1_r, gt_top1_r, t = 3, l = panel_id_top_r$l[1])
    gt1_r <- gtable_add_grob(gt1_r, gt_top2_r, t = 3, l = panel_id_top_r$l[2])
    gt1_r <- gtable_add_grob(gt1_r, virg_grob, t = 3, l = panel_id_top_r$l[3])
    
    
    # New page
    grid.newpage()
    
    # Print
    grid.draw(gt1_r)
    

    Solution 2: create a border

    Alternatively, we can add a border around the figure. You already tried to do this with gtable_add_rows. However, since you set pos = 2 it overlaps with where the labels are and so it never actually creates a border. Alternatively, you can set pos = 1, and then it will create a border around the figure and show the label, or if you would like to separate the labels from the plot you can leave pos = 2 and add an extra gtable_add_rows which has a pos = 1, as shown below.

    reprex_all <- ggplot(data= iris) +
      geom_line(aes(x= Petal.Width, y= Petal.Length)) +
      theme(panel.border=element_rect(color="black",fill="transparent"), 
            panel.background = element_rect(fill="white"))
    
    g1_r <- reprex_all +
      facet_wrap(~Species, scales = "free") +
      theme(strip.background = element_blank(),
            strip.text = element_blank())
    
    g2_r <- reprex_all +
      facet_grid(~Species, scales = "free") 
    
    gt1_r <- ggplot_gtable(ggplot_build(g1_r))
    
    gt2_r <- ggplot_gtable(ggplot_build(g2_r))
    
    # Add space above plots
    gt1_r <- gtable_add_rows(gt1_r, heights = unit(0.5, 'cm'), pos = 1)
    gt1_r <- gtable_add_rows(gt1_r, heights = unit(0.5, 'cm'), pos = 2)
    
    # Try making top labels the same way as side labels so I can change font size
    gt_top1_r <- gtable_filter(gt2_r, 'strip-t-1')
    gt_top2_r <- gtable_filter(gt2_r, 'strip-t-2')
    # instead, make my own so I can change font size
    virg_grob <- grobTree(rectGrob(height = gt_top1_r$heights[1],  
                                   gp = gpar(fill = "grey82", col = "grey82")),
                          textGrob(expression("virginica"),
                                   gp = gpar(col = "black", fontsize = 6)))
    
    panel_id_top_r <- gt1_r$layout[grep('strip-t.+1$', gt1_r$layout$name),]
    
    # Add top labels
    gt1_r <- gtable_add_grob(gt1_r, gt_top1_r, t = 2, l = panel_id_top_r$l[1])
    gt1_r <- gtable_add_grob(gt1_r, gt_top2_r, t = 2, l = panel_id_top_r$l[2])
    gt1_r <- gtable_add_grob(gt1_r, virg_grob, t = 2, l = panel_id_top_r$l[3])
    
    
    # New page
    grid.newpage()
    
    # Print
    grid.draw(gt1_r)
    

    Solution 3: turn of clipping

    Perhaps the easiest solution using this method would be to just turn off clipping. This tells R to still draw the label even if it is going out of bounds. This can be done easily by adding clip = "off" in gtable_add_grob as shown below.

    reprex_all <- ggplot(data= iris) +
      geom_line(aes(x= Petal.Width, y= Petal.Length)) +
      theme(panel.border=element_rect(color="black",fill="transparent"), 
            panel.background = element_rect(fill="white"))
    
    g1_r <- reprex_all +
      facet_wrap(~Species, scales = "free") +
      theme(strip.background = element_blank(),
            strip.text = element_blank())
    
    g2_r <- reprex_all +
      facet_grid(~Species, scales = "free") 
    
    gt1_r <- ggplot_gtable(ggplot_build(g1_r))
    
    gt2_r <- ggplot_gtable(ggplot_build(g2_r))
    
    # Add space above plots
    gt1_r <- gtable_add_rows(gt1_r, heights = unit(0.5, 'cm'), pos = 2)
    
    # Try making top labels the same way as side labels so I can change font size
    gt_top1_r <- gtable_filter(gt2_r, 'strip-t-1')
    gt_top2_r <- gtable_filter(gt2_r, 'strip-t-2')
    # instead, make my own so I can change font size
    virg_grob <- grobTree(rectGrob(height = gt_top1_r$heights[1],  
                                   gp = gpar(fill = "grey82", col = "grey82")),
                          textGrob(expression("virginica"),
                                   gp = gpar(col = "black", fontsize = 6)))
    
    panel_id_top_r <- gt1_r$layout[grep('strip-t.+1$', gt1_r$layout$name),]
    
    # Add top labels
    gt1_r <- gtable_add_grob(gt1_r, gt_top1_r, t = 2, l = panel_id_top_r$l[1])
    gt1_r <- gtable_add_grob(gt1_r, gt_top2_r, t = 2, l = panel_id_top_r$l[2])
    gt1_r <- gtable_add_grob(gt1_r, virg_grob, t = 2, l = panel_id_top_r$l[3], clip = "off")
    
    
    # New page
    grid.newpage()
    
    # Print
    grid.draw(gt1_r)
    

    Solution 4: edit facet_wrap values (easiest)

    By far the easiest solution would be to simply edit the grobs that are created by facet_wrap. This can be done using the functions below. This can of course be simplified further but I wanted to use the structure from your original code to make it easier to understand.

    where <- function(x){c(1:length(x))[x]}
    change_font <- function(plot, label, font_size){
      plot$grobs[[where(grepl(label, .subset2(plot$layout, "name")))]]$
        grobs[[1]]$
        children[[2]]$
        children[[1]]$
        gp$
        fontsize <- font_size
      return(plot)
    }
    
    reprex_all <- ggplot(data= iris) +
      geom_line(aes(x= Petal.Width, y= Petal.Length)) +
      theme(panel.border=element_rect(color="black",fill="transparent"), 
            panel.background = element_rect(fill="white"))
    
    g2_r <- reprex_all +
      facet_grid(~Species, scales = "free")
    
    gt2_r <- ggplot_gtable(ggplot_build(g2_r))
    
    # Change font size
    gt2_r <- change_font(gt2_r, 'strip-t-3', 6)
    
    # New page
    grid.newpage()
    
    # Print
    grid.draw(gt2_r)