# Makie: nonlinear color levels in Colorbar

**URL:** https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056
**Category:** Visualization
**Tags:** question, makie, colorbar
**Created:** [August 11, 2024, 2:24pm UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056 "2024-08-11T14:24:39Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![ryofurue](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ryofurue/32/24531_2.png) [@ryofurue](https://discourse.julialang.org/u/ryofurue)
#### Post date: [August 11, 2024, 2:24pm UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056/1 "2024-08-11T14:24:39Z")

</div>

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.

```julia
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:

```julia
# 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.

 ![tmp](https://global.discourse-cdn.com/julialang/original/3X/e/7/e79eb9a48d83b372d5b8ac0260c388b451a5fb15.png)

 ![tmp2](https://global.discourse-cdn.com/julialang/original/3X/9/2/92770d30562dc44ddfef2f858704beeaafe3510b.png)

---

<div class="post-metadata">

### Author: ![Datseris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/datseris/32/13406_2.png) [@Datseris](https://discourse.julialang.org/u/Datseris)
#### Post date: [August 11, 2024, 3:25pm UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056/2 "2024-08-11T15:25:16Z")

</div>

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

> [@Custom divergent colormap with unequal sides](https://discourse.julialang.org/t/custom-divergent-colormap-with-unequal-sides/115484):
>
> 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?

---

<div class="post-metadata">

### Author: ![ryofurue](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ryofurue/32/24531_2.png) [@ryofurue](https://discourse.julialang.org/u/ryofurue)
#### Post date: [August 12, 2024, 2:55am UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056/3 "2024-08-12T02:55:58Z")

</div>

> [@Datseris](#):
>
> The replies I’ve gotten at this post will help you make a nonlinear / nonuniform color scale I believe 🙂

Thanks! [this answer](https://discourse.julialang.org/t/custom-divergent-colormap-with-unequal-sides/115484/8) of @jules’s is closest to what I want to achieve: _You scale the color scheme_ using the parameter

```julie
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.

```julia
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)

```

 ![tmp2](https://global.discourse-cdn.com/julialang/original/3X/c/a/ca9cdf28f2941708d736be08fd113c91df88f6af.png)  
j

---

<div class="post-metadata">

### Author: ![ryofurue](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ryofurue/32/24531_2.png) [@ryofurue](https://discourse.julialang.org/u/ryofurue)
#### Post date: [August 12, 2024, 3:02am UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056/4 "2024-08-12T03:02:07Z")

</div>

> [@ryofurue](#):
>
> It would be nice if the tickmarks of `Colorbar` tried to follow the levels specified as `levels=[. . .]`.
> 
> I’ll investigate.

I’ve found a solution!

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

```

 ![tmp2](https://global.discourse-cdn.com/julialang/original/3X/6/4/64e66a112b57ec72ab6af9670b48dd4b1e51a7e1.png)

---

<div class="post-metadata">

### Author: ![ryofurue](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ryofurue/32/24531_2.png) [@ryofurue](https://discourse.julialang.org/u/ryofurue)
#### Post date: [August 12, 2024, 1:37pm UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056/5 "2024-08-12T13:37:24Z")

</div>

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:

```julia
"""
 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

```julia
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)

```

 ![tmp](https://global.discourse-cdn.com/julialang/original/3X/3/2/32fc0446a15936b2ee53a4cc17ce58f7b372218c.png)

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.

---

<div class="post-metadata">

### Author: ![briochemc](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/briochemc/32/4209_2.png) [@briochemc](https://discourse.julialang.org/u/briochemc)
#### Post date: [August 18, 2025, 1:44am UTC](https://discourse.julialang.org/t/makie-nonlinear-color-levels-in-colorbar/118056/6 "2025-08-18T01:44:13Z")

</div>

> [@ryofurue](#):
>
> 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.

which has since been resolved on the Makie side ([Some of the `levels` are missing from `Colorbar` · Issue #4121 · MakieOrg/Makie.jl · GitHub](https://github.com/MakieOrg/Makie.jl/issues/4121))
