Center colorbar ticks in a categorical colorbar in Makie.jl

MWE:

using GLMakie

xg = range(0, 10; length = 100)
yg = range(0, 10; length = 100)
bsn = rand(1:5, (100, 100))

fig = Figure(resolution = (700,600))
display(fig)

colors = [:black, :blue, :red, :teal, :yellow]

cmap = cgrad(colors, 5; categorical = true)
# cmap = cgrad(COLORSCHEME[1:length(att)]; categorical = true)

ax = fig[1,1] = Axis(fig)
heatob = Observable(bsn[:, :, 1])
hm = heatmap!(ax, xg, yg, heatob; colormap = cmap)
hm.colorrange = (1, 5)
cbar = fig[:, 2] = Colorbar(fig, hm, label = "attractor #")

The question is: how to make the ticks of the colorbar centered exactly at the middle of each color segment. Example:

image

The problem is that you have 5 distinct values for a 5 value colormap, but you choose a colorrange from 1 to 5. A 5 value colormap has 6 edges, so your edges are [1.0, 1.8, 2.6, 3.4, 4.2, 5.0]. If you have categorical values, make them the centers of each colormap bin. So choose the colorrange as (0.5, 5.5), then your edges will be [0.5, 1.5, 2.5, 3.5, 4.5, 5.5] with the centers 1 to 5.

2 Likes

Thanks, this is already helpful.

I may have found a bug when animating such a colormap. It seems that if I set the ticks manually, they are still updated during animating. MWE:

using GLMakie

xg = range(0, 10; length = 100)
yg = range(0, 10; length = 100)
bsn = rand(1:3, (100, 100))
bsn2 = rand(3:5, (100, 100))
bsn = cat(bsn, bsn2; dims = 3)

fig = Figure(resolution = (700,600))
display(fig)

colors = [:black, :blue, :red, :teal, :yellow]
cmap = cgrad(colors, 5; categorical = true)

ax = fig[1,1] = Axis(fig)
heatob = Observable(bsn[:, :, 1])
hm = heatmap!(ax, xg, yg, heatob; colormap = cmap)
hm.colorrange = (1, 5)
cbar = fig[:, 2] = Colorbar(fig, hm, label = "attractor #")
hm.colorrange = (0.5, 5.5)
cbar.ticks = 1:5

record(fig, "test.mp4", 1:size(bsn)[3]; profile = "high") do i
    heatob[] = bsn[:, :, i]
    tit[] = "Slice $i"
end

once you produce this you will see that one frame has ticks 1,2,3 and the other 3,4,5. If I add the hm.colorrange = (1, 5) and cbar.ticks = 1:5 inside the animation loop it’s okay, but I would expect that if I manually set tics/ranges, then they wouldn’t subsequently change.

This seems to work for me, maybe the hm.colorrange isn’t picked up correctly when it’s done after the fact? That would be a bug I think.

       xg = range(0, 10; length = 100)
       yg = range(0, 10; length = 100)
       bsn = rand(1:3, (100, 100))
       bsn2 = rand(3:5, (100, 100))
       bsn = cat(bsn, bsn2; dims = 3)

       fig = Figure(resolution = (700,600))
       display(fig)

       colors = [:black, :blue, :red, :teal, :yellow]
       cmap = cgrad(colors, 5; categorical = true)

       tit = Node("title")
       ax = fig[1,1] = Axis(fig, title = tit)
       heatob = Observable(bsn[:, :, 1])
       hm = heatmap!(ax, xg, yg, heatob; colormap = cmap, colorrange = (0.5, 5.5))

       cbar = fig[:, 2] = Colorbar(fig, hm, label = "attractor #")
       cbar.ticks = 1:5

       record(fig, "test.mp4", 1:size(bsn)[3]; profile = "high") do i
           heatob[] = bsn[:, :, i]
           tit[] = "Slice $i"
       end

Can confirm: if I give the colorrange at the creation point, it works.