When using a color transformation in ggplot2, change the legend gradient instead of the legend break positions

Viewed 374

Suppose I have a raster plot where the fill color gradient isn't used very efficiently because the values are skewed, like this:

library(ggplot2)

set.seed(20)
d = expand.grid(x = seq(0, 10, len = 100), y = seq(0, 10, len = 100))
d = transform(d, z =
    1e-4 * ((x - 2)^2 + (2*y - 4)^2 + 10*rnorm(nrow(d)))^2)

ggplot(d) +
    geom_raster(aes(x, y, fill = z)) +
    scale_fill_distiller(palette = "Spectral",
        limits = c(0, 12), breaks = 0 : 12) +
    theme(legend.key.height = unit(20, "mm"))

example plot 1

I can quantile-transform the color scale like this:

ggplot(d) +
    geom_raster(aes(x, y, fill = z)) +
    scale_fill_distiller(palette = "Spectral",
        limits = c(0, 12), breaks = 0 : 12,
        trans = scales::trans_new("q",
            function(x) ecdf(d$z)(x),
            function(x) unname(quantile(d$z, x)))) +
    theme(legend.key.height = unit(20, "mm"))

enter image description here

I like what this does for the main part of the plot, but not the legend. The legend uses the same gradient as the original, while moving the breaks according to the transformation. I'd prefer to keep the breaks where they are, while transforming the gradient instead. Also, I'd like to avoid the floating-point noise that's been added to the break labels. How can I accomplish these changes?

2 Answers

I had a very similar idea to chemdork123, but wanted to stay a bit closer to the quantile idea. The idea is to set an exact palette of colours (i.e., one colour for every value) and space this out such that it follows the data.

library(ggplot2)
library(scales)
#> Warning: package 'scales' was built under R version 4.0.3

set.seed(20)
d = expand.grid(x = seq(0, 10, len = 100), y = seq(0, 10, len = 100))
d = transform(d, z =
                1e-4 * ((x - 2)^2 + (2*y - 4)^2 + 10*rnorm(nrow(d)))^2)

# The 'distiller' palette outside of the scale,
# we need this to generate `length(d$z)` number of colours.
pal <- gradient_n_pal(brewer_pal(palette = "Spectral", direction = -1)(7))

ggplot(d) +
  geom_raster(aes(x, y, fill = z)) +
  scale_fill_gradientn(
    colours = pal(c(0, rescale(seq_along(d$z)), 1)), # <- extra 0, 1 for out-of-bounds
    limits = c(0, 12), breaks = 0:12,
    values = c(0, rescale(sort(d$z), from = c(0, 12)), 1) # <- extra 0, 1 again
  ) +
  theme(legend.key.height = unit(10, "mm"))

Created on 2021-03-31 by the reprex package (v1.0.0)

You can use the values argument for the scale_fill_distiller() function. The distiller scales extend brewer to continuous scales by interpolating 7 colors from any palette. By default, the scaling is linearly applied from 0 (lowest value on the scale) to 1 (highest value on the scale). You can recreate this mapping via: scales::rescale(1:7). If you supply a new vector to the values argument, you can remap each of the 7 colors to a new location. You do not have to supply 7 values - the rest are interpolated linearly - just as long as you specify the max at 1 (or you'll cut the scale).

Best way is to play around with it - I've tried mapping to specific functions before, but honestly it tends to work for me the best when I just mess with the numbers until I get something I like:

ggplot(d) +
  geom_raster(aes(x, y, fill = z)) +
  scale_fill_distiller(palette = "Spectral", values = c(0,0.05,0.1, 0.5,1)) +
  theme(legend.key.height = unit(20, "mm"))

enter image description here

Related