lve
lve

Reputation: 478

Map values to viridis colours in r

How do I create a colour scale function in r with a pre-defined scale (like viridis from library("viridis")?

The question is not how to use it within ggplot, but how to build a function I can feed arbitrary values within the specified value domain to and retrieve the according colour strings (rgb, hex or any other output format) from.

colorRamp and colorRampPalette seem to interpolate only between 2 colours within a given colour space. But how would I go about this with viridis for example?

EDIT:

Thanks to @sconfluentus' answer I wrote the following function that does what I want (w/o safety checks):

library("viridis")

number_vector <- c(0.772, 1.235, 5.78, 8.890, 10.543, 14.702)

map_viridis <- function(vec, num) {

  vector_expanded <-round(vec, 1) * 10 # expand to allow for decimal precision
  vector_exp_range <- max(vector_expanded) - min(vector_expanded)

  colour_vector <- viridis(vector_exp_range + 1) # get vector of colour values for all possible decimals between min and max value
  value_to_colour <- colour_vector[num * 10 - min(vector_expanded) + 1] # retrieve colour value for number

  return(value_to_colour)

}

map_viridis(number_vector, 0.8) # returns "#440154FF"
map_viridis(number_vector, 3.4) # returns "#424086FF"
map_viridis(number_vector, 14.7) # returns "#FDE725FF"

Just wondering if there's not a more direct way to achieve this result?

Upvotes: 10

Views: 10260

Answers (3)

SymbolixAU
SymbolixAU

Reputation: 26258

You can use library(colourvalues) (on CRAN 1st October 2018) to assign colours to values. The default palette is viridis.

number_vector <- c(0.772, 1.235, 5.78, 8.890, 10.543, 14.702)

colourvalues::colour_values(number_vector)
# [1] "#440154FF" "#470E60FF" "#2E6E8EFF" "#20A486FF" "#44BF70FF" "#FDE725FF"

Upvotes: 4

drammock
drammock

Reputation: 2543

I think this does what you want. Most of this was copied from the source of the viridis function. It takes in values between 0 and 1 and returns a color a corresponding fraction of the way along the viridis map.

get_viridis_color <- function(x, opt="D", begin=0, end=1, reverse=FALSE) {
    x <- x * (end - begin) + begin
    cmap <- viridisLite::viridis.map[viridisLite::viridis.map$opt == opt,]
    if (reverse) cmap <- cmap[rev(seq_len(nrow(cmap))),]
    map_rgbs <- grDevices::rgb(cmap$R, cmap$G, cmap$B)
    ramp <- grDevices::colorRamp(map_rgbs, space="Lab", interpolate="spline")
    out_rgbs <- ramp(x) / 255
    grDevices::rgb(out_rgbs[,1], out_rgbs[,2], out_rgbs[,3])
}

Values outside [0, 1] are not handled; for some reason this means negative numbers are #00FF00 and numbers greater than 1 are black. begin and end work like they do in viridis(), but reversing direction of the colormap is a boolean reverse argument instead of the 1 or -1 direction argument. Different values of opt give magma ("A"), inferno ("B"), and plasma ("C") maps instead of viridis.

Upvotes: 1

sconfluentus
sconfluentus

Reputation: 4993

Viridis itself is capable creating the values you are looking for provided you use it correctly in your own function and you know how many arbitrary values will be created.

viridis(12)
 [1] "#440154FF" "#482173FF" "#433E85FF" "#38598CFF" "#2D708EFF" "#25858EFF" "#1E9B8AFF"
 [8] "#2BB07FFF" "#51C56AFF" "#85D54AFF" "#C2DF23FF" "#FDE725FF"

So you could create a function which takes your domain, calculates the length of values in it and then creates a series of colors for each manipulating your data any way you choose. The basis for it would be like this

num_vals=length(x) # substitute nrow  for length if you have rows instead of a list
col_pal = viridis(num_vals)

As long as the set is ordered then you can simply use data[1] and col_pal[1] to associate them in pairs..or you could create a list of pairs and colors depending on how you choose to substitute them into your function.

or you could create a ramp with colorRampPalette as such

map_colors<-colorRampPalette(viridis(12)) # this would create 12 viridis colors

Upvotes: 13

Related Questions