Brian D
Brian D

Reputation: 2719

Resize strip.background to match strip.text in ggplot facet_wrap

I am attempting to make lots of "small multiples" plots.

(Aside/Background) I have too many facets to display in a single plot so I need to manually subset them up into separate plots. I was hoping I could just pass the desired ncol and nrow to facet_wrap and it would make as many as I needed. That is not the case.

Because I have so many facets, space is at a premium. So, I wanted to reduce the size of the facet labels. Easy enough to reduce the strip.text font size, but sadly the strip.background rectangle does not size with the font nor does it provide sizing parameters (apart from border size). Setting it to element_blank() doesn't actually remove the space that is set aside for it, it just makes the same space transparent.

MWE:

set.seed(1)
xcat = rep(1:10, 1000)
yvalue1 = rep(runif(10,5,7),1000)
yvalue2 = rep(runif(10,4,6),1000)
id = rep(1:1000,each=10)

df = data.frame(id,xcat,yvalue1,yvalue2)

for(i in seq(1, length(unique(df$id)), by=100)){
  print(
    ggplot(subset(df, id %in% unique(df$id)[i:(i+99)]), aes(x=xcat)) +
      annotate("line", y=yvalue1, x=xcat, colour="cornflowerblue") + 
      annotate("line", y=yvalue2, x=xcat, colour="grey20") + 
      theme(axis.text.x = element_text(size=5),
            axis.text.y = element_text(size=5),
            strip.text = element_text(size=5)) +
      facet_wrap(~id, ncol=10, nrow=10) + 
      expand_limits(y=0))
}

example image with default grey strip.background

Any ideas on how I could reduce the height of that strip.background so that it doesn't take up so much space? Ideally it would be sized to the exact height of the id text.

As a bonus, how to reduce the spacing between facets? (Is there a setting in facet_wrap that I'm missing?)

It also takes a REALLY long time to render in RStudio with facet_wrap (without facet_wrap it is much faster.

Upvotes: 4

Views: 4110

Answers (2)

beck1670
beck1670

Reputation: 98

According to Hadley, there's now a simpler solution I've provided an example with simpler code for the benefit of anyone else stumbling upon this answer. The key is to set margin = margin() whenever you set the text size.

set.seed(2112)
# dataframe with two groups
df <- data.frame(group1 = sample(LETTERS[1:5], size = 300, replace = TRUE),
    group2 = sample(1:5, size = 300, replace = TRUE),
    x = rnorm(300), y = rnorm(300))
ggplot(df, aes(x = x, y = y)) + 
    geom_point() + 
    facet_grid(group1 ~ group2) +
    theme(strip.text.x = element_text(size = 4, margin = margin()),
        strip.text.y = element_text(size = 20, margin = margin()))

Upvotes: 4

GGamba
GGamba

Reputation: 13680

The result you want is achievable modifying the grobs created by ggplot. The process is somewhat messy and bound to need some manual fine tuning:

Setting aside the loop, that is uninfluential on the problem, we can start by reducing the spacing between facet. That is easily done by adding the panel.spacing argument to the theme.

library(ggplot2)

d <- ggplot(subset(df, id %in% unique(df$id)[1:(100)]), aes(x=xcat)) +
  annotate("line", y=yvalue1, x=xcat, colour="cornflowerblue") + 
  annotate("line", y=yvalue2, x=xcat, colour="grey20") + 
  theme(axis.text.x = element_text(size=5),
        axis.text.y = element_text(size=5),
        strip.text = element_text(size=5),
        panel.spacing = unit(.5, 'pt')) +
  facet_wrap(~id, ncol=10, nrow=10) +
  expand_limits(y=0)

We then have to generate a ggplot2 plot grob. This create a list of individual elements composing the plot.

g <- ggplotGrob(d)

head(g, 5)
# TableGrob (5 x 43) "layout": 13 grobs
#    z         cells        name                                    grob
# 1  3 ( 5- 5, 4- 4)  axis-t-1-1                          zeroGrob[NULL]
# 2  3 ( 5- 5, 8- 8)  axis-t-2-1                          zeroGrob[NULL]
# 3  3 ( 5- 5,12-12)  axis-t-3-1                          zeroGrob[NULL]
# 4  3 ( 5- 5,16-16)  axis-t-4-1                          zeroGrob[NULL]
# 5  3 ( 5- 5,20-20)  axis-t-5-1                          zeroGrob[NULL]
# 6  3 ( 5- 5,24-24)  axis-t-6-1                          zeroGrob[NULL]
# 7  3 ( 5- 5,28-28)  axis-t-7-1                          zeroGrob[NULL]
# 8  3 ( 5- 5,32-32)  axis-t-8-1                          zeroGrob[NULL]
# 9  3 ( 5- 5,36-36)  axis-t-9-1                          zeroGrob[NULL]
# 10 3 ( 5- 5,40-40) axis-t-10-1                          zeroGrob[NULL]
# 11 4 ( 4- 4, 4-40)      xlab-t                          zeroGrob[NULL]
# 12 8 ( 3- 3, 4-40)    subtitle zeroGrob[plot.subtitle..zeroGrob.77669]
# 13 9 ( 2- 2, 4-40)       title    zeroGrob[plot.title..zeroGrob.77668]

Our objective is then to find and fix the height of the ones relative to the strips.

for(i in 1:length(g$grobs)) {
  if(g$grobs[[i]]$name == 'strip') g$grobs[[i]]$heights <- unit(5, 'points')
}

Unfortunately the strip grobs are also affected by the different heights the the main plot is divided into, so we have to reduce that ones also. I haven't found a way to identify programmatically which heights refers to the strip, but inspecting g$heights is quite fast to figure out which we need.

g$heights
# [1] 5.5pt               0cm                 0cm                 0cm                 0cm                
# [6] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [11] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [16] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [21] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [26] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [31] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [36] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [41] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [46] 0.518897450532725cm 1null               0cm                 0.5pt               0cm                
# [51] 0.518897450532725cm 1null               0.306264269406393cm 1grobheight         0cm                
# [56] 5.5pt       

strip_grob_position <- seq(6, length(g$heights) - 5, 5)
for(i in strip_grob_position) {
  g$heights[[i]] = unit(.05, 'cm')
}

We can finally draw the result:

library(grid)
grid.newpage()
grid.draw(g)

enter image description here

I am not an expert at all in the subject, so I may have said or explained something wrongly or inaccurately. Moreover there may be better (and simpler and more ggplot-ish) ways to obtain the result.

Upvotes: 3

Related Questions