# 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


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)

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ā¦ 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!

2 Likes