Makie.jl
Makie.jl copied to clipboard
Heatmap with log scale colorbar (cscale) ?
Reposted from discourse, with updated CairoMakie, figures and link to documentation.
Makie is awesome, looks like everything is doable :slightly_smiling_face:, for instance, this heatmap with log scale color axis (exactly what I wanted):

Code for the above figure
let
# cf. https://github.com/JuliaPlots/Makie.jl/issues/822#issuecomment-769684652
# with scale argument that is required now
struct LogMinorTicks end
function MakieLayout.get_minor_tickvalues(
::LogMinorTicks, scale, tickvalues, vmin, vmax
)
vals = Float64[]
extended_tickvalues = [
tickvalues[1] - (tickvalues[2] - tickvalues[1]);
tickvalues;
tickvalues[end] + (tickvalues[end] - tickvalues[end-1]);
]
for (lo, hi) in zip(
@view(extended_tickvalues[1:end-1]),
@view(extended_tickvalues[2:end])
)
interval = hi-lo
steps = log10.(LinRange(10^lo, 10^hi, 11))
append!(vals, steps[2:end-1])
end
return filter(x -> vmin < x < vmax, vals)
end
custom_formatter(values) = map(
v -> "10" * Makie.UnicodeFun.to_superscript(round(Int64, v)),
values
)
x = 10.0.^(1:0.1:4)
y = 1.0:0.1:5.0
data = x .* ones(Float64, 1, length(y))
clims = (1e1, 1e4) # natural display
fig = Figure()
ax, hm = heatmap(fig[1, 1], x, y, log10.(data), colorrange=log10.(clims),
axis=(;xscale=log10,
xminorticksvisible=true,
xminorticks=IntervalsBetween(9))
)
cb = Colorbar(fig[1, 2], hm;
tickformat=custom_formatter,
minorticksvisible=true,
minorticks=LogMinorTicks()
)
fig
end
But it was a bit involved for a newcomer. Here is how I would have expected it to work:
x = 10.0.^(1:0.1:4)
y = 1.0:0.1:5.0
data = x .* ones(Float64, 1, length(y))
fig = Figure()
ax, hm = heatmap(fig[1, 1], x, y, data,
axis=(;xscale=log10,
xminorticksvisible=true, xminorticks=IntervalsBetween(9),
cscale=log10)
)
Colorbar(fig[1, 2], hm)
Currently (CairoMakie v0.6.2) the cscale argument is just ignored:

also tried zscale and read the documentation of course, in particular
https://makie.juliaplots.org/stable/examples/layoutables/axis/index.html#log_scales_and_other_axis_scales
Would it make sense to add this cscale argument, for symetry with xscale or yscale ?
It has exactly the same meaning:
actually plot log10(axis value) on an underlying linear axis,
and tweak ticks, minorticks and tick labels.
Following https://discourse.julialang.org/t/heatmap-with-log-scale-colorbar-cscale/63018/2,
scale=log10 does not work as expected:
let
x = 10.0.^(1:0.1:4)
y = 1.0:0.1:5.0
data = x .* ones(Float64, 1, length(y))
fig = Figure()
cmap = cgrad(:viridis, scale=:log10)
ax, hm = heatmap(fig[1, 1], x, y, data; colormap=cmap,
axis=(;xscale=log10,
xminorticksvisible=true,
xminorticks=IntervalsBetween(9))
)
cb = Colorbar(fig[1, 2], hm;
minorticksvisible=true,
minorticks=IntervalsBetween(9),
scale=log10
)
fig
end

The issue is that the colormap is not the regular one. For instance, the blue area is smaller in the scaled colormap, and larger in the heatmap. With this test data, chosen especially to emphasize this issue, the gradient should look like the colormap.
Any progress here?
You're applying the scale twice AFAICT. Creatimg the colorbar without scale should lead to the "correct" colormap showing up.
@asinghvi17 creating the colorbar without scale yields a colorbar without "log scale" ticks, and the colors are not as in the goal image (the first one):

code
let
x = 10.0.^(1:0.1:4)
y = 1.0:0.1:5.0
data = x .* ones(Float64, 1, length(y))
fig = Figure()
cmap = cgrad(:viridis, scale=:log10)
ax, hm = heatmap(fig[1, 1], x, y, data; colormap=cmap,
axis=(;xscale=log10,
xminorticksvisible=true,
xminorticks=IntervalsBetween(9))
)
cb = Colorbar(fig[1, 2], hm;
minorticksvisible=true,
minorticks=IntervalsBetween(9),
#scale=log10 # without this, the colorbar ticks are not log spaced
)
fig
end
Oops, I guess I misinterpreted where the bug was. Which Makie versions are you on? There was a bug which was fixed recently (Makie 0.17.4 afaik)...
The latest: CairoMakie v0.8.5 and Makie 0.17.5 in the manifest.
A workaround is to modify the scale and limits of the colorbar axis after creating it. This seems to re-label the axis as desired without also transforming the actual color bar.
x = 10.0.^(1:0.1:4)
y = 1.0:0.1:5.0
data = x .* ones(Float64, 1, length(y))
fig = Figure()
ax, hm = heatmap(fig[1, 1], x, y, log10.(data))
ax.xscale = log10
cb = Colorbar(fig[1, 2], hm)
cb.axis.attributes[:scale][] = log10
cb.axis.attributes[:limits][] = exp10.(cb.axis.attributes[:limits][])

Unfortunately the workaround doesn't work when values start below 10:
x = 10.0.^(-1:0.1:3)
y = -1.0:0.1:3.0
data = x .* ones(Float64, 1, length(y))
fig = Figure()
ax, hm = heatmap(fig[1, 1], x, y, log10.(data))
ax.xscale = log10
cb = Colorbar(fig[1, 2], hm)
cb.axis.attributes[:scale][] = log10
cb.axis.attributes[:limits][] = exp10.(cb.axis.attributes[:limits][])
fig
reports error:
DomainError with -1.0:
log10 will only return a complex result if called with a complex argument. Try log10(Complex(x)).
Stacktrace:
[1] throw_complex_domainerror(f::Symbol, x::Float64)
@ Base.Math ./math.jl:33
[2] _log(x::Float64, base::Val{10}, func::Symbol)
@ Base.Math ./special/log.jl:301
[3] log10
@ ./special/log.jl:268 [inlined]
[4] _broadcast_getindex_evalf
@ ./broadcast.jl:670 [inlined]
[5] _broadcast_getindex
@ ./broadcast.jl:643 [inlined]
[6] getindex
@ ./broadcast.jl:597 [inlined]
[7] macro expansion
@ ./broadcast.jl:961 [inlined]
[8] macro expansion
@ ./simdloop.jl:77 [inlined]
[9] copyto!
@ ./broadcast.jl:960 [inlined]
[10] copyto!
@ ./broadcast.jl:913 [inlined]
[11] copy
I encountered the same issue. Is there a fix?
https://github.com/MakieOrg/Makie.jl/pull/2493 would fix this.
Great, I"ll wait then until merged.
Can be closed (fixed by https://github.com/MakieOrg/Makie.jl/pull/2900).