5

Is it possible to desaturate a ggplot easily?


In principle, there could be two possible strategies.
First, apply some function to a ggplot object (or, possibly, Grob object) to desaturate all colors. Second, some trick to print ggplot desaturated while rendering a .rmd file. Both strategies would be ok for me, but first one is, of course, more promissing.
Creating ggplot in greys from the beginning is not a good option as the idea is to have the same plot as if it was printed in shades of grey.


There were some similar questions and remarkably good answers on how to perform desaturation in R. Here is a convenient way to desaturate color palette. And here is the way of desaturating a raster image. What I'm looking for, is a simple way of desaturating the whole ggplot.

Community
  • 1
  • 1
ikashnitsky
  • 2,941
  • 1
  • 25
  • 43
  • Can you export the plot to PNG at high DPI, load the PNG via raster and desaturate that via the solution you link to? That seems like a low-cost solution if all you're trying to do is test what it would look like. – Forrest R. Stevens Aug 21 '15 at 18:47
  • I hope, there is a simple way of desaturating the ggplot object itself. Turning vector graphics to raster in R doesn't feel right. – ikashnitsky Aug 21 '15 at 18:52
  • 3
    Well, if it's just that you want to keep it in vector format you could export the `ggplot` output to a PDF device with `colormodel="gray"`, that might get the results you're looking for? – Forrest R. Stevens Aug 21 '15 at 19:04
  • 1
    Cool! I didn't know about `colormodel="gray"` option. Is there something similar when you render a plot in `knirt`? – ikashnitsky Aug 21 '15 at 19:16
  • 1
    I'm with @ikashnitsky, desaturating the ggplot object itself is useful. For example, it allows you to make a side-by-side plot of the original plot and the desaturated plot, straight from R, without having to write the plot to a file and reading it back in. It's really useful if you want to write R Markdown documents about the effects of desaturation and colorblindness. – Claus Wilke Dec 01 '17 at 05:27

3 Answers3

9

Just came across this question. The experimental package colorblindr (written by Claire McWhite and myself) contains a function that can do this in a generic way. I'm using the example figure from @hrbrmstr:

library(ggplot2)
library(viridis)

gg <- ggplot(mtcars) + 
  geom_point(aes(x=mpg, y=wt, fill=factor(cyl), size=factor(carb)), 
             color="black", shape=21) + 
  scale_fill_viridis(discrete = TRUE) +
  scale_size_manual(values = c(3, 6, 9, 12, 15, 18)) +
  facet_wrap(~am)
gg

enter image description here

Now let's desaturate this plot, using the edit_colors() function from colorblindr:

library(colorblindr) # devtools::install_github("clauswilke/colorblindr")
library(colorspace) # install.packages("colorspace", repos = "http://R-Forge.R-project.org") --- colorblindr requires the development version
# need also install cowplot; current version on CRAN is fine.

gg_des <- edit_colors(gg, desaturate)
cowplot::ggdraw(gg_des)

enter image description here

The function edit_colors() takes a ggplot2 object or grob and applies a color transformation function (here desaturate) to all colors in the grob.

We can provide additional arguments to the transformation function, e.g. to do partial desaturation:

gg_des <- edit_colors(gg, desaturate, amount = 0.7)
cowplot::ggdraw(gg_des)

enter image description here

We can also do other transformations, e.g. color-blind simulations:

gg_des <- edit_colors(gg, deutan)
cowplot::ggdraw(gg_des)

enter image description here

Finally, we can manipulate line colors and fill colors separately. E.g., we could make all filled areas blue. (Not sure this is useful, but whatever.)

gg_des <- edit_colors(gg, fillfun = function(x) "lightblue")
cowplot::ggdraw(gg_des)

enter image description here

Claus Wilke
  • 16,992
  • 7
  • 53
  • 104
3

As per my comment above, this might be the quickest/dirtiest way to achieve the desaturation for a ggplot2 object:

library(ggplot2)

set.seed(1)
p <- qplot(rnorm(50), rnorm(50), col="Class")
print(p)

enter image description here

pdf(file="p.pdf", colormodel="grey")
  print(p)
dev.off()

enter image description here

Forrest R. Stevens
  • 3,435
  • 13
  • 21
2

I tried this with the new viridis color palette since it desaturates well (i.e. it should be noticeable between the colored & non-colored plots):

library(ggplot2)
library(grid)
library(colorspace)
library(viridis) # devtools::install_github("sjmgarnier/viridis") for scale_fill_viridis

gg <- ggplot(mtcars) + 
  geom_point(aes(x=mpg, y=wt, fill=factor(cyl), size=factor(carb)), 
             color="black", shape=21) + 
  scale_fill_viridis(discrete = TRUE) +
  scale_size_manual(values = c(3, 6, 9, 12, 15, 18)) +
  facet_wrap(~am)

gb <- ggplot_build(gg)
gb$data[[1]]$colour <- desaturate(gb$data[[1]]$colour)
gb$data[[1]]$fill <- desaturate(gb$data[[1]]$fill)

gt <- ggplot_gtable(gb)

grid.newpage()
grid.draw(gt)

You end up having to manipulate on the grob level.

Here's the plot pre-desaturate:

enter image description here

and here's the plot post-desature:

enter image description here

I'm trying to figure out why the legend got skipped and this may miss other highly customized ggplot aesthetics & components, so even while it's not a complete answer, perhaps it might be useful (and perhaps someone else can tack on to it or expand on it in another answer). It should just be a matter of replacing the right bits in either the gb object or gt object.

UPDATE I managed to find the right grob element for the legend:

gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[4]]$gp$fill <- 
    desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[4]]$gp$fill)
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[6]]$gp$fill <- 
    desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[6]]$gp$fill)
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[8]]$gp$fill <- 
    desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[8]]$gp$fill)

grid.newpage()
grid.draw(gt)

enter image description here

The machinations to find the other gp elements that need desaturation aren't too bad either.

hrbrmstr
  • 77,368
  • 11
  • 139
  • 205
  • Thanks! That's really great! Your solution could be easily warped in a handy function. Yes, very strange behavior of legend labels. I cannot locate where the problem is. – ikashnitsky Aug 21 '15 at 20:09
  • I managed to figure out the grobs for them. You just need to (carefully) walk the `gt$grobs` tree and `desaturate()` all of the `gp` `fill` and `color` elements (some won't exists). – hrbrmstr Aug 21 '15 at 20:11
  • And thanks a lot for `viridis` palette. It's magnificent! Do I get it right that it is not only print-friendly but also colorblind-friendly? – ikashnitsky Aug 21 '15 at 20:18
  • 1
    Aye. I have some extended viridis examples [here](https://rud.is/b/2015/07/20/using-the-new-viridis-colormap-in-r-thanks-to-simon-garnier/). It's not perfect for discrete situations but it works well and is color-blind, print and far more "normal" linear precise than anything else to-date (perhaps parula comes close, but it's proprietary). – hrbrmstr Aug 21 '15 at 20:21
  • @ hrbrmstr Is there any way of anchoring `midpoint` with `viridis`? I mean like in `scale_fill_gradient2`. – ikashnitsky Aug 21 '15 at 20:46
  • Not that I'm aware of (at least with the current state of the way the base palette was generated by the vis researchers). I'll have a look at the python code, though, and see if it's possible. – hrbrmstr Aug 21 '15 at 21:25
  • 2
    A few months back I wrote a function that does exactly what @hrbrmstr suggests: "(carefully) walk the `gt$grobs` tree and `desaturate()` all of the `gp` `fill` and `color` elements." (In the most general case, though, you also need to cover raster grobs, which are a bit more tricky.) See the answer I posted. – Claus Wilke Dec 01 '17 at 05:22