Context
I'm trying to create a custom color scale to be called using something like scale_fill_perso in ggplot. I followed the steps described in this nice blog post. My discrete scale has 7 levels.
I managed to set the scale correctly (see below). When using a graph with 7 levels, I have the expected colors. However, when I don't use as many colors, I would like R to respect the order of my palette and not interpolate between values (see example). For instance, if I have 3 colors, I would like R to use the first three values of my color vector.
I think this comes from my_pal that itself uses grDevices::colorRampPalette which, when using a number of classes smaller than the size of the color vector, cuts the color vector using extremes rather than order.
So my question is: Is there some way to capture the number of classes and, if number classes < length(color vector) do not use colorRampPalette interpolation ?
Current implementation
Steps follow the aforementioned blog post.
First, create a color vector and a way to call it:
mycolors <- c(
`red` = "#E2447A",
`green` = "#BCE550",
`blue` = "#708DD3",
`grey` = "#666666",
`orange` = "#FFBAA8",
`violet` = "#D1A3FF",
`lightgrey` = "#B2B2B2"
)
my_cols <- function(...) {
cols <- c(...)
if (is.null(cols))
return (mycolors)
mycolors[cols]
}
call_palettes <- function(palette = "main"){
if (palette == "main"){ return(my_cols()) }
}
There's only one palette for the moment but this might change. Then create the palette function that interpolates values (for what I understood):
my_pal <- function(palette = "main", reverse = FALSE, ...) {
args <- list(...)
#return(args)
pal <- call_palettes(palette, ...)
if (reverse) pal <- rev(pal)
grDevices::colorRampPalette(pal, ...)
}
Then create scale_fill_perso function to use that palette.
scale_fill_perso <- function(palette = "main", discrete = TRUE, reverse = FALSE, ...) {
pal <- my_pal(palette = palette, reverse = reverse)
if (discrete) {
ggplot2::discrete_scale("fill", paste0("my_pal_", palette), palette = pal, ...)
} else {
ggplot2::scale_fill_gradientn(colours = pal(256), ...)
}
}
Output
Using 7 classes, no problem:
iris$random <- sample(1:7, nrow(iris), replace = TRUE)
ggplot2::ggplot(iris) +
ggplot2::geom_histogram(ggplot2::aes(x = Sepal.Width, y = ..density..,
fill = factor(random))) +
scale_fill_perso(palette = "main")
However, when using a number of colors smaller, I would like to use the first three colors of my vector (red-green-blue), which is not the case for the moment
ggplot2::ggplot(iris) +
ggplot2::geom_histogram(ggplot2::aes(x = Sepal.Width, y = ..density..,
fill = factor(Species))) +
scale_fill_perso(palette = "main")
I identified that comes from the fact that my_pal is not taking order of the vector as informative. For instance, for 2 colors, it takes the two extremes of the vector:
my_pal()(2)
# "#E2447A" "#B2B2B2"
mycolors
# red green blue grey orange violet lightgrey
# "#E2447A" "#BCE550" "#708DD3" "#666666" "#FFBAA8" "#D1A3FF" "#B2B2B2"
and for three, it adds the middle value:
my_pal()(3)
# "#E2447A" "#666666" "#B2B2B2"
mycolors
# red green blue grey orange violet lightgrey
# "#E2447A" "#BCE550" "#708DD3" "#666666" "#FFBAA8" "#D1A3FF" "#B2B2B2"
How can I ensure to follow vector order when number classes < number colors ?


