Search code examples
rggplot2usmap

R how to specify custom color gradients with breakpoints


I'm trying to create a custom gradient scale in R that will change colors based on values.

Basically:

I want the fill color to be a gradient between darkblue and blue for values that are between 1 and 50.

I want the fill color to be a gradient between lightblue and yellow for values that are between 50 and 100.

And so on...

I've tried the code below, but this is clearly the wrong way to do it:

scale_fill_gradientn(limits = c(0,50),colours=c("darkblue","blue"),na.value="#101010") +
scale_fill_gradientn(limits = c(50,100),colours=c("lightblue","yellow"),na.value="#101010") +
scale_fill_gradientn(limits = c(100,1000),colours=c("orange","orangered"),na.value="#101010") +
scale_fill_gradientn(limits = c(1000,10000),colours=c("darkred","red"),na.value="#101010") + 
scale_fill_gradientn(limits = c(10000,30000),colours=c("red","#FF877D"),na.value="#101010") + 

Could someone help me out?

Edit:

Using this code:

scale_fill_gradientn(
  limits  = range(0,max(da$val)),
  colours = colours[c(1, seq_along(colours), length(colours))],
  values  = c(0, scales::rescale(colour_breaks, from = range(0,da$val)),range(0,da$val))) + 

I get this effect:

Example

Which is close to what I'm going for, but I don't really understand the rescale code and what it's doing.

I want it to start as blue and go up to red using the breaks.

It's close - just starting at yellow.

Edit:

With help from @teunbrand, I got it it working!

Here's the code:

my_limits <- range(c(0, usa.dat[,50:ncol(usa.dat)]))
max_value <- max(usa.dat[, 50:ncol(usa.dat)])

for (i in 2:ncol(usa.dat)) {
  da <- data.frame(fips=usa.dat$countyFIPS,val=usa.dat[,i])
  tot <- sum(da$val)
  colour_breaks <- c(0,1,100,500,1000,10000,30000)
  colours <- c("#101010","blue","lightblue","yellow","red","darkred","magenta")
  
  current_max <- max(da$val)
  relative_max <- max_value / current_max
  
  theDate <- substr(names(usa.dat)[i],2,100)
  plot_usmap(regions = "counties",
             data=da,
             values="val"
  ) + 
  labs(title=paste("Covid-19 Deaths - ",str_replace_all(theDate,"\\.","/")," - Total: ",tot,sep='')) +
  scale_fill_gradientn(limits  = range(0, current_max),
                       colours = colours,
                       values = scales::rescale(colour_breaks, to = c(0, relative_max), from = c(0, max_value)),
                       name="Deaths",na.value="#101010") + 
  theme(panel.background = element_rect(color = "#101010", fill = "#101010"))
  ggsave(paste(sprintf("%03d",i),".png",sep=''))

Results: https://www.youtube.com/watch?v=HZcnhUr6ghw

Edit: Magma Palette: https://www.youtube.com/watch?v=QrgYIl_xqUU

Edit 2: 3D version: https://youtu.be/Lk7E8o6VIQY


Solution

  • You can set the exact points where a particular colour should be by using the values argument of the scale. In the example below, we want "darkblue" at 10, "lightblue" at 20 and "yellow" at 30.

    The only catch is that the values argument works for rescaled values between 0 and 1, so we ought to fix the limits and apply the same rescaling to our breaks.

    Lastly, because now values outside the 10-30 range are undefined, it might be best to repeat the colours at the extremes at the (rescaled) values 0 and 1.

    library(ggplot2)
    
    colour_breaks <- c(10, 20, 30)
    colours <- c("darkblue", "lightblue", "yellow")
    
    ggplot(mpg, aes(displ, hwy, colour = cty)) +
      geom_point() +
      scale_colour_gradientn(
        limits  = range(mpg$cty),
        colours = colours[c(1, seq_along(colours), length(colours))],
        values  = c(0, scales::rescale(colour_breaks, from = range(mpg$cty)), 1),
      )
    

    Created on 2021-10-13 by the reprex package (v2.0.1)

    As a small note: in your description it is unclear what the colour should be at 50: in one gradient it should be blue and in the other it should be lightblue. At best, you can set one of the colours (let's say blue) to 50 and use a 50 + a miniscule offset (e.g. .Machine$double.eps) for the lightblue break.