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).