Custom divergent colormap with unequal sides

I am working with Makie.jl and I wish to create a custom colormap. Taking any divergent colormap provided by Makie, such as :balance for example, I want to make a colormap that has the same colors as :balance or :delta, but the left side spans the range of values 0-1, and the right side spans the range of values 1-4 (instead of what the symmetric 1-2 would dictate).

Is that possible? If yes, how?

Can’t you use a standard diverging colormap for the range [-3,3], and then apply it to values v - 1 \in [-1, 3] (so that the left side is truncated)?

I am sorry, I did not understand the advice. I’ve set up a MWE which I should have done so already in my first post:

using CairoMakie

x = 1:10
y = 1:10
c = rand(10,10)

cmap = :delta
fig, ax, hm = heatmap(x, y, c; colormap = cmap)
cb = Colorbar(fig[1,2], hm)
fig

what I want to achieve is that the “blue” part of the color map covers the range (e.g.,) 0-0.25, and the “green” part covers the range 0.25-1.0, instead of the currently symmetric case of covering each 50-50. I want to be able to utlize all colors in the color map, not cut off e.g., the bottom deep blue color.

Maybe just pass your data through the map:

distort(x) = x < 0.25 ? -3x : x - 0.25

which maps it to [-0.75, 0.75], and then apply a standard diverging colormap centered at zero.

Of course, this distorts the data, which is something you have to be cautious about for visualization. Effectively, your colormap is no longer a “perceptually uniform” representation of your data, which is something that the colormap designers have worked hard to avoid.

1 Like

Yeah, I guess that’s the only way. I wanted to avoid transforming my own data, because this requires me to re-label all ticks in the colorbar. But it’s not a big deal so I’ll go ahead with this way of remapping data.

Yes, I understand. Thankfully in my particular use case it is not a big problem!

You can distort the colormap rather than the data. The following code is a bit clumsy, but shows the general idea.

using GLMakie
using ColorSchemes


function get_color_val(data_val, data_min, data_max, data_crit, color_val_crit)

    is_low = data_val ≤ data_crit
    prop_val = is_low ? (data_val-data_min)/(data_crit - data_min) : (data_val-data_crit)/(data_max-data_crit)
    color_val = is_low ? prop_val*color_val_crit : color_val_crit + prop_val*(1-color_val_crit)

end 

X = rand(20,20)
data_min, data_max = 0.0, 1.0
data_crit = 0.25

cmap = [ get(ColorSchemes.RdBu, get_color_val(x, data_min, data_max, data_crit, 0.5)) for x ∈ range(0,1;length = 512)]


fig = Figure()
ax = Axis(fig[1,1])
hm = heatmap!(ax, X; colormap = cmap)
Colorbar(fig[1,2], hm)
fig

crit_025
Note that the white midpoint of the colorscheme has been reset to 0.25

1 Like

Makie.jl also has some tools to make divergent colormaps, for example Makie.diverging_palette(0, 230; mid=0.4)
image
Here, 0 and 230 are hues in a color wheel from 0 to 360 and mid is just a fraction to bias the point where the colors diverge.

1 Like

I’ve also seen plots where people use an uneven scale on a symmetrical colormap. So you don’t distort the map but the scale. Here’s an example I cooked up a while ago, adjusted to your numbers:

data = rand(10, 10) .* 4

f = Figure()
ax = Axis(f[1, 1])

upper = 4
lower = 0
midpoint = 1
ratio = (upper - midpoint) / (midpoint - lower)

sc = ReversibleScale(
    x -> x > midpoint ? (x - midpoint) / ratio + midpoint : x,
    x -> x > midpoint ? (x - midpoint) * ratio + midpoint : x
)

hm = heatmap!(ax, data, colorscale = sc, colorrange = (lower, upper), colormap = :delta)
Colorbar(f[1, 2], hm, ticks = lower:upper)
f

Here’s a variant with the upper part going to 10, to show the effect even more:

upper = 10
lower = 0
midpoint = 1

data = rand(10, 10) .* (upper - lower) .+ lower

f = Figure()
ax = Axis(f[1, 1])


ratio = (upper - midpoint) / (midpoint - lower)

sc = ReversibleScale(
    x -> x > midpoint ? (x - midpoint) / ratio + midpoint : x,
    x -> x > midpoint ? (x - midpoint) * ratio + midpoint : x
)

hm = heatmap!(ax, data, colorscale = sc, colorrange = (lower, upper), colormap = :RdBu)
Colorbar(f[1, 2], hm, ticks = lower:upper)
f

Might be confusing to some readers though.

Edit: hmm seems like the rendering of the colors in the heatmap is a bit off though with this

Edit2: I just had forgot to scale the input data with the larger limits, I think

1 Like

Thanks @jules , the differently scaled colorscale is exactly what I was looking for!

I’m glad you got a good solution! I started to write an answer, but was interrupted, so this is now superfluous to requirements… :slight_smile: But for the record, my idea was to concatenate colorschemes:

cs = ColorSchemes.delta
cs1 = ColorScheme(get(cs, range(0, 0.5, length=50))) * 
      ColorScheme(get(cs, range(0.5, 1.0, length=200)))

I was going to explore some of the tools in ColorSchemeTools.jl such as weighted colorschemes to see if they helped, but no need to, now! :joy:

2 Likes