77

Is there a way to fill the strips of facets created with facet_wrap based on a variable supplied with the data frame?

Example data:

MYdata <- data.frame(fruit = rep(c("apple", "orange", "plum", "banana", "pear", "grape")), farm = rep(c(0,1,3,6,9,12), each=6), weight = rnorm(36, 10000, 2500), size=rep(c("small", "large")))

Example plot:

p1 = ggplot(data = MYdata, aes(x = farm, y = weight)) + geom_jitter(position = position_jitter(width = 0.3), aes(color = factor(farm)), size = 2.5, alpha = 1) + facet_wrap(~fruit)

I know how to change the background color of the strips (e.g. to orange):

p1 + theme(strip.background = element_rect(fill="orange"))

facet_wrap and orange strip color

Is there a way to pass on the values in the variable size in MYdata to the parameter fill in element_rect?

Basically, instead of 1 color for all strips I would like the strip background color of small fruits (apple, plum, pear) to be green and the background color of large fruits (orange, banana, grape) to be red.

tonytonov
  • 25,060
  • 16
  • 82
  • 98
Dalmuti71
  • 1,509
  • 3
  • 15
  • 19

5 Answers5

70

With a little bit of work, you can combine your plot with a dummy gtable that has the right grobs,

enter image description here

d <- data.frame(fruit = rep(c("apple", "orange", "plum", "banana", "pear", "grape")), 
                farm = rep(c(0,1,3,6,9,12), each=6), 
                weight = rnorm(36, 10000, 2500), 
                size=rep(c("small", "large")))

p1 = ggplot(data = d, aes(x = farm, y = weight)) + 
  geom_jitter(position = position_jitter(width = 0.3), 
              aes(color = factor(farm)), size = 2.5, alpha = 1) + 
  facet_wrap(~fruit)

dummy <- ggplot(data = d, aes(x = farm, y = weight))+ facet_wrap(~fruit) + 
  geom_rect(aes(fill=size), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) +
  theme_minimal()

library(gtable)

g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(dummy)

gtable_select <- function (x, ...) 
{
  matches <- c(...)
  x$layout <- x$layout[matches, , drop = FALSE]
  x$grobs <- x$grobs[matches]
  x
}

panels <- grepl(pattern="panel", g2$layout$name)
strips <- grepl(pattern="strip_t", g2$layout$name)
g2$layout$t[panels] <- g2$layout$t[panels] - 1
g2$layout$b[panels] <- g2$layout$b[panels] - 1

new_strips <- gtable_select(g2, panels | strips)
grid.newpage()
grid.draw(new_strips)

gtable_stack <- function(g1, g2){
  g1$grobs <- c(g1$grobs, g2$grobs)
  g1$layout <- transform(g1$layout, z= z-max(z), name="g2")
  g1$layout <- rbind(g1$layout, g2$layout)
  g1
}
## ideally you'd remove the old strips, for now they're just covered
new_plot <- gtable_stack(g1, new_strips)
grid.newpage()
grid.draw(new_plot)
jbaums
  • 27,115
  • 5
  • 79
  • 119
baptiste
  • 75,767
  • 19
  • 198
  • 294
  • 2
    This just [R]ocked my world! Been trying to do this for ages! – orville jackson Aug 19 '14 at 09:14
  • Thanks for this! Any idea how to include the legend for the strip colors as well? – jayelm May 03 '16 at 23:38
  • Extremely useful, thanks! That's going into a paper I'm writing at the moment (and I'm citing this answer in the code I'll eventually archive). – lukeholman May 30 '16 at 02:07
  • 1
    @duckertito You could have edited the post. I did it now. In the current version **ggplot2_2.2.1** you have to grepl for "strip-t" instead of strip_t. Also changed this in my edit. Thanks for the nice solution btw! – andschar Feb 21 '17 at 08:50
  • 1
    Yup, works for me too (with @andrasz change). I wish I could put a legend for the strip color though. Any idea how to do that? – mic Apr 09 '18 at 23:38
  • 3
    This is exactly what I need, Thanks. But, the facet labels do not show up when I run this code. – user1757654 May 29 '18 at 22:46
  • 2
    I'm also having trouble with the facet labels not showing up. Any solution found yet? – philiporlando Dec 03 '18 at 04:51
  • kinda late answer...I have just encountered the same issue and solve it by adding this line `stript <- grepl(pattern="strip-t", g2$layout$name)` and then replace `new_strips <- gtable_select(g2, panels | strips)` with `new_strips <- gtable_select(g2, panels | strips | stript)` – SamPer Sep 22 '20 at 05:42
  • When I try this I get an error: Error in grid.Call.graphics(C_setviewport, vp, TRUE) : invalid 'layout.pos.col', yet I can't seem to figure out how to fix this. Any suggestions? – clions226 Jul 08 '21 at 19:36
22

If you want to have different fills to the strip backgrounds, you can use facets in ggh4x to set a more complicated strip with strip_themed(). No hassle with gtables and your plot remains a ggplot, so you can add the usual layers/scales/theme options etc afterwards.

library(ggh4x)
#> Loading required package: ggplot2

# Only colour strips in x-direction
strip <- strip_themed(background_x = elem_list_rect(fill = rainbow(7)))

# Wrap variant
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap2(~ class, strip = strip)

It works for the grid layout too, but if you want to colour the vertical strips, you'd need to set the background_y argument in strip_themed() too.

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_grid2(year ~ cyl, strip = strip)

Created on 2023-01-04 by the reprex package (v2.0.1)

Disclaimer: I'm the author of ggh4x

teunbrand
  • 33,645
  • 4
  • 37
  • 63
  • 2
    Def, best solution! Very nice ggplot2 extension. It's so easy to use! This should be the answer now! – Ni-Ar Jan 27 '23 at 17:54
  • 1
    I agree. This is the ggplot2 extension is a great solution. It works for changing all elements of the strip text (e.g. font, face, size, color) and rectangle (fill, etc.) . It is intuitive too. I just wish the help pages integrated into R Studio a little better. (R studio, doesn't find the function names in the help tab even when the library is loaded). Other than that.. Perfect! – ESELIA Mar 07 '23 at 15:14
  • This is fantastic! Great package @teunbrand, thanks for the contributions – Ricardo Saporta Jun 16 '23 at 21:20
5

You can find an updated answer to this question here.

g <- ggplot_gtable(ggplot_build(p))
stripr <- which(grepl('strip-r', g$layout$name))
fills <- c("red","green","blue","yellow")
k <- 1
for (i in stripr) {
  j <- which(grepl('rect', g$grobs[[i]]$grobs[[1]]$childrenOrder))
  g$grobs[[i]]$grobs[[1]]$children[[j]]$gp$fill <- fills[k]
  k <- k+1
}
grid::grid.draw(g)

enter image description here

filups21
  • 1,611
  • 1
  • 19
  • 22
  • Anyone have an idea on how to add a legend for the strip colours? – Max J. Feb 07 '23 at 14:34
  • 1
    @MaxJ. If you're not using the "color" or "fill" aesthetic, you could probably create a dummy legend that matches the strip colors instead of the data. But that's another hassle on top of the hassle of modifying the gtable. Perhaps `ggh4x` offers an easier solution? – filups21 Feb 08 '23 at 14:56
  • I was able to create a legend by creating a dummy plot and add it via cowplot but I do not know how to do it in ggplot directly. – Max J. Feb 09 '23 at 16:46
0

I would love to know how to do that, it is a great idea. One idea is to generate each chart independently with a different color as you do and then use something like multiplot or viewports to show then side by side - it will require a bit more work.

if you want to extract the legend, which you will need for this approach - here is some code from Hadley that I found a while back

g_legend<-function(a.gplot){
  tmp <- ggplot_gtable(ggplot_build(a.gplot))
  leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
  legend <- tmp$grobs[[leg]]
  return(legend)}

see how it is extracted it from chart p, and then I took it out of the plot legend <- g_legend(p) lwidth <- sum(legend$width) #if you want to define the viewport based on this p <- p + theme(legend.position="none")

then you eventually draw it

grid.newpage()
vp <- viewport(width = 1, height = 1)
#print(p, vp = vp)

submain <- viewport(width = 0.9, height = 0.9, x = 0.5, y = 1,just=c("center","top"))
print(p, vp = submain)
sublegend <- viewport(width = 0.5, height = 0.2, x = 0.5, y = 0.0,just=c("center","bottom"))
print(arrangeGrob(legend), vp = sublegend)

Good luck

user1617979
  • 2,370
  • 3
  • 25
  • 30
0

It's not directly for differently coloring your facets but here you have another (very quick and simpler) solution, based on facet by two variables (size ~ fruit) instead one (~ fruit):

ggplot(data = MYdata, aes(x = farm, y = weight)) + 
  geom_jitter(position = position_jitter(width = 0.3), 
      aes(color = factor(farm)), size = 2.5, alpha = 1) + 
  facet_wrap(size ~ fruit)

enter image description here

jgarces
  • 519
  • 5
  • 17