thomasp85 / scico

Palettes for R based on the Scientific Colour-Maps
Other
411 stars 24 forks source link

Allow scales to be centered at zero for diverging palettes? #6

Closed andrewheiss closed 2 years ago

andrewheiss commented 5 years ago

There are several diverging color palettes like broc, cork, vic, etc. where the center color value is as meaningful as the extremes.

However, if you use scale_*_scico() on a variable that's not symmetrically distributed around zero, the colors also won't be centered around zero:

library(tidyverse)
library(scico)

fake_data <- tribble(
  ~category, ~pct_change,
  "A",      0.5,
  "B",      0.3,
  "C",      0.01,
  "D",      -0.2
)

# Zero should be the lightest color, or the middle of the scico gradient, but
# it's not because the range here isn't symmetrical
ggplot(fake_data, aes(x = category, y = pct_change, fill = pct_change)) +
  geom_col() +
  scale_fill_scico(palette = "vik") +
  theme_minimal()

One workaround is to extract the extreme and center values from the palette with scico(3) and use scale_fill_gradientn() to interpolate colors between low, mid, and high. This unfortunately loses the magical range of colors in the palette that makes scico so great

fake_scico <- scico(3, palette = "vik")

ggplot(fake_data, aes(x = category, y = pct_change, fill = pct_change)) +
  geom_col() +
  scale_fill_gradient2(low = fake_scico[1], mid = fake_scico[2], high = fake_scico[3]) +
  theme_minimal()

The better solution is to use the limits argument to make the scale be symmetrical, but doing so requires additional code and math to work

ggplot(fake_data, aes(x = category, y = pct_change, fill = pct_change)) +
  geom_col() +
  scale_fill_scico(palette = "vik", 
                   limits = c(-1, 1) * max(abs(fake_data$pct_change))) +
  theme_minimal()

Would it be worth it to add an argument like center = TRUE or center = 0 to scale_*_scico() for the diverging palettes, or should users handle the centering on their own?

I haven't found any other examples of how this is done in other packages. None of the viridis palettes are diverging, and the ColorBrewer diverging palettes that are accessible with scale_*_brewer(type = "div", palette = "PuOr") only work with discrete scales (which lets ggplot get around this centering issue)

thomasp85 commented 5 years ago

Yeah... I've been thinking about this as well, so thanks for reminding me :-) we definitely need this

tungttnguyen commented 5 years ago

+1 would be great!

jens-daniel-mueller commented 4 years ago

+1 would be great!

modche commented 3 years ago

+1 would be great, any progress here?

burchill commented 2 years ago

I whipped something up myself for my own purposes (slightly different than what OP is asking for---I had continuous data). If you (@thomasp85 ) want me to make a pull request I can.

Set mid to the value you want as the midpoint. This works for continuous data only, though!

scale_fill_scico_mid <- function(..., mid = 0, alpha = NULL, begin = 0, end = 1, direction = 1, palette = "broc") {
  if (!requireNamespace("ggplot2", quietly = TRUE)) {
        stop("ggplot2 is required for this functionality", call. = FALSE)
  }
  force(mid)
  ggplot2::continuous_scale(
    aesthetics = "fill", 
    scale_name = "gradient2",
    palette = scales::gradient_n_pal(
      colours = scico(256, alpha, begin, end, direction, palette), 
      values = NULL, space = "Lab"),
    guide="colourbar",
    rescaler = function(x, to = c(0, 1), from = range(x, na.rm = TRUE)) {
      scales::rescale_mid(x, to, from, mid)
    },
    ...
  )
}
thomasp85 commented 2 years ago

fixed by 3777e5f1982bdc91