Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set a document-persistent ggplot2 color theme

Tags:

r

ggplot2

I would like to define a color palette for every plot built in a markdown document. In essence this would overwrite the default choices.

There are several very old answers -- thanks for the links here and here suggested by @dww -- that solve for old versions (specifically calling out a solution on 0.8.2 when the modern release several major releases ahead, currently at 3.2.x).

I'll illustrate the closest use case, setting themes. In the case of general purpose themes, this is trivial: rather than appending + theme_minimal() on every plot, I can instead set the theme which persists across all plots.

library(ggplot2) 

d <- diamonds[sample(1:nrow(diamonds), 1000), ]

## without theming 
ggplot(d, aes(x=carat, y=price, color=clarity)) + 
  geom_point() + 
  theme_minimal() # must set this theme call for every plot 

## setting theme 
theme_set(theme_minimal())

ggplot(d, aes(x=carat, y=price, color=clarity)) + 
  geom_point()    # plot in theme, for all subsequent plots

Is there a similar, modification that exists to set the color palette throughout? For example, a theme-based replacement for calling,

ggplot(d, aes(x=carat, y=price, color=clarity)) + 
  geom_point() + 
  scale_color_brewer(palette='Set2') # requesting a global option to set this for all plots

The linked solution that does not depend on old versions instead overloads the entire ggplot function. That seems risky.

ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer(palette = 'Set1')
like image 565
alex Avatar asked Feb 09 '20 03:02

alex


1 Answers

There is a ggplot_global environment which is used internally within ggplot2 but isn't exported. You can see its structure by temporarily unlocking the bindings of a ggplot function and modifying it to return the contents of the environment as a list. You can do this non-destructively like this:

library(ggplot2)

get_ggplot_global <- function()
{
  unlockBinding("theme_set", as.environment("package:ggplot2"))
  backup <- body(theme_set)[[5]]
  body(theme_set)[[5]] <- substitute(return(as.list(ggplot_global)))
  global_list <- theme_set(theme_bw())
  body(theme_set)[[5]] <- backup
  lockBinding("theme_set", as.environment("package:ggplot2"))
  return(global_list)
}

global_list <- get_ggplot_global()
names(global_list)
#> [1] "date_origin"    "element_tree"   "base_to_ggplot" "all_aesthetics"
#> [5] "theme_current"  "time_origin"   

By examining this you can see that ggplot global environment has an object called theme_current, which is why you can set the various line, text and axis elements globally including their colours.

When you are talking about a colour scheme in your question, you are referring to the colours defined in a scale object. This is not part of the ggplot_global environment, and you can't change the default scale object because there isn't one. When you create a new ggplot(), it has an empty slot for "scales".

You therefore have a few options:

  1. Wrap ggplot with my_ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer()
  2. Overwrite ggplot with the above function (as suggested by @Phil)
  3. Create your own theme object that you add on with standard ggplot syntax

The best thing might be to just write a wrapper around ggplot. However, the third option is also quite clean and idiomatic. You could achieve it like this:

set_scale <- function(...)
{
  if(!exists("doc_env", where = globalenv()))
    assign("doc_env", new.env(parent = globalenv()), envir = globalenv())
  doc_env$scale <- (ggplot() +  eval(substitute(...)))$scales$scales[[1]]
}

my_scale <- function() if(exists("doc_env", where = globalenv())) return(doc_env$scale)

You would use this by doing (for example)

set_scale(scale_color_brewer(palette = "Set2"))

At the start of your document. So now you can just do + my_scale() for each plot:

d <- diamonds[sample(1:nrow(diamonds), 1000), ]

ggplot(d, aes(x=carat, y=price, color=clarity)) +
  geom_point() +
  my_scale()

enter image description here

like image 64
Allan Cameron Avatar answered Sep 29 '22 22:09

Allan Cameron