4

A plot will be made from these data:

  mtcars %>%
  gather(-mpg, key = "var", value = "value") %>%
  ggplot(aes(x = value, y = mpg)) +
  geom_point() +
  facet_wrap(~ var, scales = "free") +
  theme_bw()

How can I change the gray color of the titles of the panels for instance

 panels of am and hp   green 
 panels of gear drat disp   red 
 panels  of vs wt   blue
 panels cyl qsec carb black 

add a legend

      green  = area 
      red=  bat
      blue= vege
      black = indus
chemdork123
  • 12,369
  • 2
  • 16
  • 32
bic ton
  • 1,284
  • 1
  • 9
  • 16

1 Answers1

5

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

enter image description here

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

enter image description here

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

enter image description here

chemdork123
  • 12,369
  • 2
  • 16
  • 32
  • 1
    Thanks, the only problem of this approach is when y axis is totally different between the variables, so we can not do aes(xmin=-Inf, xmax=Inf, ymin=36, ymax=42. Do you know how to assign the max of each variable? – bic ton Jan 25 '22 at 09:12
  • It's a bit of work, but totally doable. Instead of defining a value for `ymin` and `ymax` within `aes()` you would define those values within `hacky_df` as new columns and then map the column. Ex.: add `bottom = c(...),` and `top = c(...)` to `hacky_df`, then replace `ymin=36` with `ymin=bottom` and `ymax=42` with `ymax=top`. – chemdork123 Jan 25 '22 at 14:11
  • Thanks and we need ylim? how to cope with it? – bic ton Jan 25 '22 at 17:04
  • Yes, you will need to keep the `ylim(...)` part within `coord_cartesian()`. Otherwise, the scale will automatically adjust to keep the rectangles inside the dimension of the plot. You can just set the dimensions based on the data: ex. `ylim = c(max(mtcars$mpg), min(mtcars$mpg))` – chemdork123 Jan 25 '22 at 17:44
  • Thanks but this will not be the max and min for each variable. it will be min and max for all varaibles. – bic ton Jan 25 '22 at 17:54
  • Correct. You'll have to adjust the positions of `ymin=` and `ymax=` for the individual facets by mapping in `hacky_df` like I reference above if the y axis scales are different and your actual data has variation. The question posted has the y scale identical for all, so it was not clear to me your data has this variation. I see now setting `ylim()` universally is going to be a problem there. – chemdork123 Jan 25 '22 at 18:03
  • Your option might be `patchwork` or `grid.arrange()` or `cowplot()` with individual plots or a list of plots, then you can control each individually. – chemdork123 Jan 25 '22 at 18:04
  • Neat @chemdork123, even easier than [the nice solution](https://stackoverflow.com/a/21589891/10852113) by baptiste. – Dion Groothof Jan 25 '22 at 18:50