# Makie: nonlinear color levels in Colorbar

In a `contourf` map, I sometimes need to show the structure of the field at small absolute values as well as showing large absolute values. At the same time, I’d like to enable the reader to “read” approximate values off the map. For example, the first map near the bottom of this message is the sea-surface temperature anomaly of January from the annual average. The contour levels are -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2 as the color bar shows. You can easily sea which region has which temperature value range.

On the other hand, the following sample program generates the second attached plot. The contour intervals are exactly as intended, but the colorbar remains “linear” unlike in the first plot.

``````using CairoMakie

xs = 0.0:1.0:10.0
ys = 0.0:1.0:7.0

func(x,y) = exp((x^2 + y^2)/20)
vals = func.(xs, ys')
levels = [2, 5, 10, 20, 50, 100, 200]

fig = Figure()
ax = Axis(fig[1,1])
c = contourf!(ax, xs, ys, vals,
levels=levels, extendlow=:auto, extendhigh=:auto)
Colorbar(fig[1,2],c)
save("tmp2.png", fig)
``````

It’s not easy to see which value range each color represents.

Is there an option to Makie to achieve a nonlinear colorbar easily?

The method I’ve thought of so far is very tedious:

``````# Take log of the field for negative values and positive values
logtemp = (x -> (x > eps) ? log(x) : (x < -eps) ? log(-x) : 0.0).(temp)
# Plot log(temp)
c = contourf!( . . . , logtemp, levels = . . . )
# Manually put labels like "-0.2" on the colorbar
Colormap(  . . . )
``````

The log part would be easy, but calculating the labels on the colorbar would be tedious.

The replies I’ve gotten at this post will help you make a nonlinear / nonuniform color scale I believe

1 Like

Thanks! this answer of @jules’s is closest to what I want to achieve: You scale the color scheme using the parameter

``````colorscale=ReversibleScale(conversion_func, inverse_conversion_func)
``````

to `contourf`. I paste a my sample code after incorporating the idea.

The only remaining issue is the labeling of the colorbar. The labels are still placed at a linear interval (it’s 50 in the following example), missing finer contour levels.

It would be nice if the tickmarks of `Colorbar` tried to follow the levels specified as `levels=[ . . .]`.

I’ll investigate.

``````using CairoMakie

xs = 0.0:1.0:10.0
ys = 0.0:1.0:7.0

func(x,y) = exp((x^2 + y^2)/20)
vals = func.(xs, ys')
levels = [2, 5, 10, 20, 50, 100, 200]

scaling = let thr = 2.0, lthr = log(thr)
function scale(x)
(x > thr) ? log(x) : ((x < -thr) ? -log(-x) : (x/thr)*lthr)
end
function invscale(x)
(x > lthr) ? exp(x) : ((x < -lthr) ? -exp(-x) : (x/lthr)*thr)
end
CairoMakie.ReversibleScale(scale, invscale)
end

fig = Figure()
ax = Axis(fig[1,1])
c = contourf!(ax, xs, ys, vals; colorscale=scaling
,levels = levels
,extendlow=:auto, extendhigh=:auto)
Colorbar(fig[1,2],c)
save("tmp2.png", fig)
``````

j

I’ve found a solution!

``````Colorbar(fig[1,2],c; ticks=levels)
``````

My last solution was fine, but the intervals in the colorbar was uneven.

In this method, you map your original data “v” using a monotonic function “u = f(v)” only for shading purposes: `colorscale = `. The contour interval is linear in the “u” space. To get a uniform interval for `levels = [-5, -2, -1, -0.5, . . . ]`, therefore, you have to invent a map such that `f.([-5,-2,-1, . . .])` is linear. In my last attempt, this property didn’t hold, which was the reason for the uneven intervals on the colorbar.

So, I’ve written a function to create such a map and its inverse:

``````"""
To be used as `colorscale` of Makie's `contourf` and `heatmap`.
Picewise linear mapping such that
func.([v1,v2, . . . , vn]) == [0, 1, . . . , n-1].
Outside the range [v1, vn], it's the simple lienar extrapolation.
vs = [v1,v2, . . . , vn] must be strictly increasing.
"""
function mk_piecewise_linear(vs)
@assert length(vs) > 1
function is_increasing(ss)
prev = ss[1]
for s in ss[2:end]
(s <= prev) && return false
prev = s
end
return true
end
@assert is_increasing(vs)
d1 = vs[2] - vs[1]
d2 = vs[end] - vs[end-1]
un = size(vs,1) - 1
function piecewise_linear(v)
if v <= vs[1]
(v - vs[1])/d1
elseif v >= vs[end]
(v - vs[end])/d2 + un
else
i = findfirst(q -> v < q, vs) - 1
d = vs[i+1] - vs[i]
(v - vs[i])/d + i-1
end
end
function its_inverse(u)
if u <= 0.0
u*d1 + vs[1]
elseif u >= un
(u - un)*d2 + vs[end]
else
iu = floor(Int,u)
i = iu + 1
d = vs[i+1] - vs[i]
(u - iu)*d + vs[i]
end
end
return CairoMakie.ReversibleScale(piecewise_linear, its_inverse)
end
``````

Using this function, you can finally use arbitrary contour levels like

``````using CairoMakie

func(x,y) = x

levels = [-5, -2, -1, -0.5, 0.5, 1, 2, 5]

include("mk_piecewise_linear.jl")
scaling = mk_piecewise_linear(levels)

xs = -7.0:1.0:7.0
ys = 0.0:1.0:7.0
vals = func.(xs, ys')
fig = Figure()
ax = Axis(fig[1,1])
c = contourf!(ax, xs, ys, vals
,colorscale=scaling
,levels = levels
,extendlow=:auto, extendhigh=:auto)
Colorbar(fig[1,2],c; ticks=levels)
save("tmp.png", fig)
``````

This is the result I wanted, except the levels `-2.0` and `2.0` are missing from the colorbar, which I think is an unrelated bug in Makie, which I’ve already submitted a report for.