Ggplot2: Facet_Wrap Strip Color Based on Variable in Data Set

How to specify different background colors for each facet label in ggplot2?

Unfortunately, it seems the way to answer OP's question is still going to be quite hacky.

If you're not into gtable hacks like those referenced... here's another exceptionally hacky way to do this. Enjoy the strange ride.

TL;DR - The idea here is to use a rect geom outside of the plot area to draw each facet label box color

Here's the basic plot below. OP wanted to (1) change the gray color behind the facet labels (called the "strip" labels) to a specific color depending on the facet label, then (2) add a legend.

First of all, I just referenced the gathered dataframe as df, so the plot code looks like this now:

df <- mtcars %>% gather(-mpg, key = "var", value = "value")

ggplot(df, aes(x = value, y = mpg)) +
geom_point() +
facet_wrap(~ var, scales = "free") +
theme_bw()

Sample Image

How to recolor each facet label?

As referenced in the other answers, it's pretty simple to change all the facet label colors at once (and facet label text) via the theme() elements strip.background and strip.text:

plot + theme(
strip.background = element_rect(fill="blue"),
strip.text=element_text(color="white"))

Sample Image

Of course, we can't do that for all facet labels, because strip.background and element_rect() cannot be sent a vector or have mapping applied to the aesthetics.

The idea here is that we use something that can have aesthetics mapped to data (and therefore change according to the data) - use a geom. In this case, I'm going to use geom_rect() to draw a rectangle in each facet, then color that rect based upon the criteria OP states in their question. Moreover, using geom_rect() in this way also creates a legend automatically for us, since we are going to use mapping and aes() to specify the color. All we need to do is allow ggplot2 to draw layers outside the plot area, use a bit of manual fine-tuning to get the placement correct, and it works!

The Hack

First, a separate dataset is created containing a column called var that contains all facet names. Then var_color specifies the names OP gave for each facet. We specify color using a scale_fill_manual() function. Finally, it's important to use coord_cartesian() carefully here. We need this function for two reasons:

  1. Cut the panel area in the plot to only contain the points. If we did not specify the y limit, the panel would automatically resize to accomodate the rect geom.

  2. Turn clipping off. This allows layers drawn outside the panel to be seen.

We then need to turn strip.background to transparent (so we can see the color of the box), and we're good to go. Hopefully you can follow along below.

I'm representing all the code below for extra clarity:

library(ggplot2)
library(tidyr)
library(dplyr)

# dataset for plotting
df <- mtcars %>% gather(-mpg, key = "var", value = "value")

# dataset for facet label colors
hacky_df <- data.frame(
var = c("am", "carb", "cyl", "disp", "drat", "gear", "hp", "qsec", "vs", "wt"),
var_color = c("area", "indus", "indus", "bat", "bat", "bat", "area", "indus", "vege", "vege")
)

# plot code
plot_new <-
ggplot(df) + # don't specify x and y here. Otherwise geom_rect will complain.
geom_rect(
data=hacky_df,
aes(xmin=-Inf, xmax=Inf,
ymin=36, ymax=42, # totally defined by trial-and-error
fill=var_color, alpha=0.4)) +
geom_point(aes(x = value, y = mpg)) +
coord_cartesian(clip="off", ylim=c(10, 35)) +
facet_wrap(~ var, scales = "free") +
scale_fill_manual(values = c("area" = "green", "bat" = "red", "vege" = "blue", "indus" = "black")) +

theme_bw() +
theme(
strip.background = element_rect(fill=NA),
strip.text = element_text(face="bold")
)

plot_new

Sample Image

Changing colour of strip.text using facet_wrap of 2 variables

I'm experimenting with custom strips over at the github version of ggh4x. The feature isn't on CRAN yet, but you might find it useful. The main idea is that you can give a list of elements to a strip. Blatantly riffing off stefan's example:

library(ggplot2)
library(ggh4x) # devtools::install_github("teunbrand/ggh4x")

ggplot(mtcars, aes(hp, mpg)) +
geom_point() +
facet_wrap2(
cyl~am,
strip = strip_themed(
text_x = elem_list_text(colour = c("royalblue3", "black")),
by_layer_x = TRUE
)
)

Sample Image

Created on 2021-08-04 by the reprex package (v1.0.0)

(obligatory disclaimer: I'm the author ggh4x. If you find any bugs, it's a great time to report them)

R ggplot2 facet_wrap reordering subfigure and set different colors for each ID label

One approach to specifying colours for the strip texts is to use editing functions from the grid package. Usually grid.ls() and other editing functions see just one grob, but grid.force() makes all the grobs in the ggplot visible to grid's editing functions.

library(grid)
library(ggplot2)

ID <- rep(c('ABC123','DEF456','GHI789','JKL012'),each = 10)
Vref <- c((runif(10,1,2)),(runif(10,3,5)),(runif(10,6,9)),(runif(10,0,2)))
Time <- rep(c(1:10),4)
df <- data.frame(ID,Vref,Time)

# Change order of strip texts
df$ID = factor(df$ID, levels = c("GHI789", "ABC123", "JKL012", "DEF456"))

p = ggplot(df) + geom_point(aes(x=Time, y=Vref)) + facet_wrap(~ID, nrow = 2)

gp <- ggplotGrob(p) # Get ggplot grob

# Get the names of the grobs
# grid.force makes all the grobs visible to grid's editing functions
names.grobs <- grid.ls(grid.force(gp))$name

# Inspect the list. The required grobs' names begin with GRID.text,
# but so do other text grobs - to do with the axes.
# Therefore, in the edit, use gPath to limit the editing to GRID.text in the strip,
# and not the GRID.text in the axes.

strip.text <- names.grobs[which(grepl("GRID.text", names.grobs))]

# Set up the colours
colour <- c("yellow", "blue", "red", "green")

# The edit
for(i in 1:4) gp = editGrob(grid.force(gp),
gPath("GRID.titleGrob", strip.text[i]),
grep = TRUE,
gp = gpar(col = colour[i]))

# Draw it
grid.newpage()
grid.draw(gp)

Sample Image

Change facet label text and background colour

You can do:

ggplot(A) +
geom_point(aes(x = x, y = y)) +
facet_wrap(~z) +
theme_bw()+
theme(strip.background =element_rect(fill="red"))+
theme(strip.text = element_text(colour = 'white'))

Sample Image

Multiple colors in a facet STRIP background

For what it's worth, it's pretty straight-forward to adapt that previous gtable hack.

Sample Image

## Sample data
require(ggplot2)
dat <- mtcars
## Add in some colors based on the data
dat$facet_fill_color <- c("red", "green", "blue", "yellow", "orange")[dat$gear]

## Create main plot
p <- ggplot(dat, aes(x=cyl, y=wt)) +
geom_point(aes(fill=hp)) + facet_grid(gear+carb ~ .) +
theme(strip.background=element_blank())

dummy <- p
dummy$layers <- NULL
dummy <- dummy + geom_rect(data=dat, xmin=-Inf, ymin=-Inf, xmax=Inf, ymax=Inf,
aes(fill = facet_fill_color))

library(gtable)

g1 <- ggplotGrob(p)
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-right", g2$layout$name)
g2$grobs[strips] <- replicate(sum(strips), nullGrob(), simplify = FALSE)
g2$layout$l[panels] <- g2$layout$l[panels] + 1
g2$layout$r[panels] <- g2$layout$r[panels] + 2

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 <- 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)


Related Topics



Leave a reply



Submit