1

I didn't find any answer in the web.

I have this plot:

a = data.frame(x=c(1,2,3,3,4,5,3,2,3,4), 
               y=c(1,2,3,6,4,5,3,2,3,2), 
               z=c(-4,-3.5,-2,-1,0,0.2,0.45,0.6,0.9,1))

ggplot()+
 geom_point(data=a, aes(x,y,fill=z),
            colour = "black", pch = 21, size = 3, stroke = 0.1)+
 scale_fill_gradientn(colors = c("blue","white","red"), 
                      breaks=c(-5,-4,-3,-2,-1,0,0.2,0.4,0.6,0.8,1),
                      limits=c(-5,1), 
                      guide = guide_colorbar(barwidth = 0.8, barheight = 18),
                      oob = scales::squish, 
                      trans = "identity")

Which gives me this scale bar:

Plot with linear scale bar

Of course this is what I expect, because the scale is linear and I defined those breaks.

However, I would like to know how I can obtain a scale bar which looks like this:

Desired plot with non-linear scale bar

Z.Lin
  • 28,055
  • 6
  • 54
  • 94
  • How tied are you to the idea of this being a continuous gradient? It would be pretty easy to do if you don't mind cutting color into discrete breaks (which many data viz geeks argue makes differences more legible anyway) – camille May 25 '18 at 14:17
  • Can you show me how? – Andrea Neri May 25 '18 at 14:28

2 Answers2

4

One possible workaround is to rescale the z values before mapping them to the fill parameter, so that the values correspond to a linear colour bar, then label the legend according to the pre-scaled values:

a$z.rescaled <- ifelse(a$z < 0, a$z / 5, a$z)

> a
   x y     z z.rescaled
1  1 1 -4.00      -0.80
2  2 2 -3.50      -0.70
3  3 3 -2.00      -0.40
4  3 6 -1.00      -0.20
5  4 4  0.00       0.00
6  5 5  0.20       0.20
7  3 3  0.45       0.45
8  2 2  0.60       0.60
9  3 3  0.90       0.90
10 4 2  1.00       1.00

ggplot()+
  geom_point(data = a, 
             aes(x, y, fill = z.rescaled),
             colour = "black", pch = 21, size = 3, stroke = 0.1) +
  scale_fill_gradientn(name = "",
                       colors = c("blue", "white", "red"),
                       breaks = seq(-1, 1, 0.2),
                       labels = c(seq(-5, 0, 1), seq(0.2, 1, 0.2)),
                       limits = c(-1, 1),
                       guide = guide_colorbar(barwidth = 0.8, barheight = 18),
                       oob = scales::squish)

plot

Z.Lin
  • 28,055
  • 6
  • 54
  • 94
  • Thank you @Z.Lin, that works fine and I will use this method for my real plots. However I was wondering if there is a way to achieve the same result without creating a support variable. – Andrea Neri May 25 '18 at 14:28
  • @AndreaNeri I can't think of any way that doesn't require a support variable. Maybe someone else here can. :) – Z.Lin May 25 '18 at 14:32
2

You can break your continuous z variable into a discrete variable using cut and manually defined breaks, then give it a diverging color scale, either manually or with a predefined palette.

The only tricky part is cleaning up the labels, which I did with a regex pattern to extract the last number from each range. Technically, these labels aren't totally accurate, because each color represents a range of values, not just a single value, but I was trying to mimic your example. You could skip the labeling function I put in scale_color_brewer and just use the labels as they come, which is of the format (0.2, 0.4].

library(tidyverse)

a = data.frame(x=c(1,2,3,3,4,5,3,2,3,4), 
               y=c(1,2,3,6,4,5,3,2,3,2), 
               z=c(-4,-3.5,-2,-1,0,0.2,0.45,0.6,0.9,1))

a$brk <- cut(a$z, breaks = c(-6:0, seq(0.2, 1, by = 0.2)), include.lowest = T)

ggplot(a, aes(x = x, y = y, fill = brk)) +
  geom_point(shape = 21, size = 3, stroke = 0.1, color = "black") +
  scale_fill_brewer(palette = "RdBu", 
                    guide = guide_legend(reverse = T), 
                    direction = -1, drop = F, 
                    labels = function(x) str_extract(x, "(?<=,).+(?=\\])"))

One issue is that this gives a legend with circles in it, because it takes the shape of the geom_point. I messed around with adding guide = guide_legend(override.aes = list(shape = 22)) and other shapes to make squares there instead, but wanted it to look more like a regular legend. You get a more traditional legend from the fill of a geom_col, though, so I used a trick I picked up from this answer to make an invisible geom_col, and turn off the legend for the geom_point and only use the legend created by geom_col.

ggplot(a, aes(x = x, y = y)) +
  geom_point(aes(fill = brk), shape = 21, size = 3, stroke = 0.1, color = "black", show.legend = F) +
  geom_col(aes(fill = brk), alpha = 0) +
  scale_fill_brewer(palette = "RdBu", 
                    guide = guide_legend(reverse = T, override.aes = list(alpha = 1)), 
                    direction = -1, drop = F, 
                    labels = function(x) str_extract(x, "(?<=,).+(?=\\])"))

Created on 2018-05-25 by the reprex package (v0.2.0).

camille
  • 16,432
  • 18
  • 38
  • 60