ggplot2
ggplot2 copied to clipboard
Feature request: midpoint argument in scale_color_gradientn()
Since there are now divergent continuous color palettes available in different packages, it would be nice to be able to specify a midpoint without manually having to calculate the limits.
related issue: https://github.com/thomasp85/scico/issues/6
It's not clear to me that adding a mid
argument to scale_color_gradientn()
is the right approach, versus adding a new scale function, e.g. scale_color_gradientn_div()
. Sequential and diverging scales are quite different conceptually, and I think it's important to emphasize this in the API rather than muddle things together. (If you think about how you'd implement the requested feature, you'd probably end up with a parameter mid
with default NULL
and would write two entirely different function bodies depending on whether mid
is NULL
or not.)
In the same vein, maybe scale_color_gradient2()
should be deprecated in favor of something like scale_color_gradient_div()
.
Just a thought on your last idea. I don’t think gradient2
is used for diverging scales as these usually have a midpoint colour, not just two extremes. I see no reason to deprecate it despite adding a sequential scale type (which I think is the right approach to this)
scale_*_gradient()
sets up a sequential scale between two extremes:
https://github.com/tidyverse/ggplot2/blob/214f3148d8a9a25ce80859645dbef38f9632b4fa/R/scale-gradient.r#L74-L78
scale_*_gradient2()
sets up a diverging scale between two extremes, with a midpoint:
https://github.com/tidyverse/ggplot2/blob/214f3148d8a9a25ce80859645dbef38f9632b4fa/R/scale-gradient.r#L93-L99
You just made my case for renaming scale_*_gradient2()
:-)
Haha - I guess I have been starring at code for too long
As it happens, I'm trying to get my head around using scales::rescale()
for this purpose (a warming stripes plot.
rescale_div <- function(x) {
scales::rescale(x,
from = c(-max(abs(x), na.rm = TRUE), max(abs(x), na.rm = TRUE)))
}
This seems to scale my vector correctly, but I'm having trouble figuring out how I pop it into ggplot2:
library(tidyverse)
#> Warning: package 'ggplot2' was built under R version 3.6.1
library(lubridate)
#>
#> Attaching package: 'lubridate'
#> The following object is masked from 'package:base':
#>
#> date
library(scales)
#>
#> Attaching package: 'scales'
#> The following object is masked from 'package:purrr':
#>
#> discard
#> The following object is masked from 'package:readr':
#>
#> col_factor
# import data (set years to july 1)
aus_temps <-
read_table(
'http://www.bom.gov.au/web01/ncc/www/cli_chg/timeseries/tmean/0112/aus/latest.txt',
col_names = c('year', 'anomaly'), col_types = 'cd') %>%
mutate(year = ymd(substr(year, 1, 8)) + months(6) - days(18))
rescale_div <- function(x) {
scales::rescale(x,
from = c(-max(abs(x), na.rm = TRUE), max(abs(x), na.rm = TRUE)))
}
ggplot(aus_temps) +
geom_tile(
aes(x = year, y = 1, fill = anomaly)) +
scale_fill_gradientn(
rescaler = rescale_div,
colours =
c('#2166ac', '#4393c3', '#92c5de', '#d1e5f0',
'#ffffff', '#fddbc7', '#f4a582', '#d6604d', '#b2182b'))
#> Error in f(...): unused argument (from = limits)
Created on 2020-01-23 by the reprex package (v0.3.0)
scale_fill_gradient2()
is probably the better solution in my specific case, but since I'm trying to nail the warming stripes look, sometimes it's easier to just push a larger vector of colours in.
@rensa Your comment doesn't really belong into this issue, nor into the ggplot2 issue tracker more generally. In general, I suggest you ask such questions on the RStudio forums or on stackoverflow.com. The issue tracker is for genuine bugs in the code and for feature requests.
In your specific case, the problem is that your function rescale_div()
doesn't accept arguments to
and from
.
No worries! Sorry about that :)
Here's how I approached this problem, following the example from this related Stack Overflow question:
There's an internal function in ggplot2, mid_rescaler()
that I find to be very useful - it's visible in @clauswilke's post above. I have adapted it a bit here:
library("ggplot2")
mid_rescaler <- function(mid = 0) {
function(x, to = c(0, 1), from = range(x, na.rm = TRUE)) {
scales::rescale_mid(x, to, from, mid)
}
}
grid <- expand.grid(lon = seq(0, 360, by = 2), lat = seq(-90, 0, by = 2))
grid$z <- with(grid, cos(lat*pi/180) - .7)
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = z)) +
scale_fill_distiller(palette = "RdBu", rescaler = mid_rescaler())
Created on 2020-02-08 by the reprex package (v0.3.0)
As you know, the ...
in color- and fill-scale functions get passed on to discrete_scale()
or continuous_scale()
. The continuous_scale()
function has a rescaler
argument which, itself, expects a function.
The mid_rescaler()
function lets me specify the midpoint to build a rescaler
function. To me, it seems like a minimally-disruptive solution, which ggplot2 is itself already using.
Could it be useful to make such a function available, publicly, in ggplot2?
Also thanks to @dpseidel, who listened very patiently as I waved my hands trying to describe this at rstudio::conf, while the right approach was to make a reprex.
Hi, say I built a plot where the Y axis maps a numerical variable and the X axis a factor variable of 4 levels (i.e. 4 groups). Then I decide to map the data with a geom_jitter and colour the plot with a divergent gradient. My question is: is there a way for the midpoint of the divergent scale colour to start exactly at the median of each level (for this of course the legend would not make sense but as a way of visualising medians with colours).
Because the rescaler
argument accepts lambda syntax, one doesn't even need to write a new function for this.
library("ggplot2")
grid <- expand.grid(lon = seq(0, 360, by = 2), lat = seq(-90, 0, by = 2))
grid$z <- with(grid, cos(lat*pi/180) - .7)
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = z)) +
scale_fill_distiller(
palette = "RdBu",
rescaler = ~ scales::rescale_mid(.x, mid = 0)
)
Created on 2022-12-03 by the reprex package (v2.0.1)