# Plotting matrix (heatmap/pcolor/imshow) with log scales

Hi, I’m struggling with plotting a simple matrix with log-scales for x, y and/or color-axis.
Consider e.g. a random 5*5 matrix `a`, with associated values for `x` and `y` axis (these values are always regularly spaced, either in linear or log space).

``````a = 10 .^(3*randn(5,5))
x = 10 .^(1:5)
y = 1:5
``````

So given these values, it makes sense to use logarithmic x- and color-scales.
I tried with Plots.jl, first with GR backend:

``````heatmap(10 .^(1:5), 1:5, a, xscale=:log10)
``````

which produces:

here the xticks are perfect, but not the rendering of the matrix…
Without the `xscale=:log10` option, the xticks are wrong… but the cells sizes well rendered:

Now, independently from this xticks behavior, let’s check the colors (these are probably two different problems, but I need to find a solution fixing the two together).
The `zscale=:log10` seems to be ineffective, so I tried to play with the color gradient to yield a good color rendering:

``````heatmap(10 .^(1:5), 1:5, a, cgrad=(scale=:log10))
``````

I don’t quite understand how the transformation is computed, but it looks worse than before, so I also tried:

``````heatmap(10 .^(1:5), 1:5, a, cgrad=(scale=:exp))
``````

which is better, but still not what I expect:

This should be compared to the range of colors you get when manually computing the log:

``````heatmap(10 .^(1:5), 1:5, log10.(a))
``````

which produces:

The colors are much better, but the colorbar is now useless.

Now I also tried with PyPlot backend and `xscale=:log10`:

The matrix display is great, but same problem with the colors.

Then I tried to use directly PyPlot, without Plots. I used the keyword argument `norm=matplotlib[:colors][:LogNorm]` which solves the color problem, but I can’t get the ticks to render correcly.
First with `imshow`, I have trouble with the xaxis, even when using `xscale("log")`:

``````PyPlot.imshow(a, cmap=:RdBu, norm=matplotlib[:colors][:LogNorm](),extent=[1, 10^5, 1, 5])
``````

Then with `pcolor`, I have a bug with the first column, and also the ticks are now aligned to the begin/end of the axis, but they should be aligned with the centers of the bins. With a lin-scale I could use that and play manually with xticks, but with a log-scale it us much more annoying.

``````PyPlot.pcolor(10 .^(1:6),1:6, a, cmap=:RdBu, norm=matplotlib[:colors][:LogNorm]())
``````

So none of these options works properly to me, does anyone know how to get that working?

I’m using julia 1.1.0, Plots 0.25.3, PyPlot 2.8.1.

1 Like

log scaling seems to work fine in plain GR, so it can probably easily be fixed in Plots, too. Will check this later.

``````using GR
heatmap(randn(5,4), xlog=true)
``````

Thanks. With plain GR, how would you specify the values on the x axis ? because your code does indeed produces a logscale for x and does aligns the xticks properly, but on the values 1:5, and as a consequence the bins have different sizes. But the GR heatmap function does not take `x` and `y` arguments like Plots does. Also, is there a way to solve the colormap problem in plain GR? I had a quick look at the doc, but no success so far.

OK, below is the workaround I came with using PyPlot, and the output. I had trouble with my first toy example where the range for z was too high and the ticks not displaying, but it was actually unrelated to the log-scale, it works when `norm=…` is passed to `pcolor`. Regarding x- and y- ticks, I do manually compute them.

If someone knows if it is possible to achieve this behavior with Plots, I would be grateful.

Also, after thinking a bit more, the `scale=:log10` option of `cgrad` cannot possibly solve the problem as we need to use the range of z when creating the gradient. So there should be another `zscale` option to use here, I probably didn’t understand correctly how to use that, or maybe it is a limitation of Plots?

``````a = 10 .^(1*randn(5,5));
custom_heatmap(10 .^(1:5), 1:5, a, xscale=:log10, zscale=:log10)
``````

``````# ===== Manually extending ticks values for pcolor =====

# For n values (ticks) corresponding to the centers of n equally-spaced bins,
# returns n+1 ticks corresponding to the borders before/between/after the bins.
function extend_lin(x)
n = length(x)
m, M = minimum(x), maximum(x)
halfbin = (M - m )/(2(n-1))
return range(m - halfbin, M + halfbin, length = n+1)
end
extend_log10(x) = 10 .^(extend_lin(log10.(x)))

function extend_from_spacing(x, sp)
if sp == :lin
extend_lin(x)
elseif sp == :log10
extend_log10(x)
else error("Unsupported spacing '\$sp'.") end
end

# ===== Detecting lin- or log-spaced data =====

lindiff(x) = [x[i+1] - x[i] for i in 1:(length(x)-1)]
logdiff(x) = [x[i+1]/x[i] for i in 1:(length(x)-1)]
function findspacing(x)
@assert length(x)>2
lgd = unique(logdiff(x)) 	# TODO catch zeros
if all(lgd[1] .≈ lgd)
if lgd[1] ≈ 10
return :log10
elseif lgd[1] ≈ exp(1)
return :log
end
end
lid = unique(lindiff(x))
if all(lid[1] .≈ lid)
return :lin
end
@warn "Could not detect lin/log regular spacing. Using lin without any guarantees."
:lin
end

# ===== Plotting =====

function custom_heatmap(x, y, z;
clear = true,
xscale = :lin,
yscale = :lin,
zscale = :lin,
kwargs...)

clear && clf()	# Don't know how to avoid multiple colorbars otherwise when replotting
@assert all([s in [:lin, :log10] for s in [xscale, yscale, zscale]])
xsp = findspacing(x)
ysp = findspacing(y)
nx = extend_from_spacing(x, xsp)
ny = extend_from_spacing(y, ysp)
xscale == :log10 && PyPlot.xscale("log")
yscale == :log10 && PyPlot.yscale("log")
extra_args = Dict{Symbol, Any}()
if zscale == :log10
extra_args[:norm] = matplotlib[:colors][:LogNorm]()
end
p = PyPlot.pcolor(nx, ny, z; merge(extra_args, kwargs)...)
c = colorbar(p)
return p
end
``````
1 Like

In GR, the above plot can only be created using low-level functions, not with a single call. You will have to set scales, draw axes and the histogram separately - probably not what you want.

May be, Plots ha a recipe for this - I don’t know.

This might be an relatively “old” post, but I am still suffering from getting a working log color scale heatmap:

``````using Plots
gr()
x = LinRange(-2, 2, 40)
y = LinRange(0, pi, 20)
z = abs.(50 .* sin.(x') .+ cos.(y))
``````

Besides, I haven’t found any related documenation in either GR.jl or GRUtils.jl.

It does not seem to work in gr() but one can always heatmap log10 of the data:
`heatmap(log10.(z))`

Yes, but I am wondering why the `scale` keyword in `cgrad` doesn’t do anything, even though it exists. Perhaps it would be clearer to look at the source code then, but that takes more time and effort.

Ok, I know this is not done with Plots, but if your are up to try Gnuplot.jl then the following works pretty well in every dimension, colors, x and y.

``````using Gnuplot
@gp exp10.(2*rand(100,100)) "w image pixels not" "set logscale cb"
@gp :- "set auto fix" "set size square" palette(:plasma)
@gp :- "set logscale x" "set logscale y" xrange = (1,100) yrange = (1,100)
save(term="pngcairo size 640,600", output="FigHeatmap_logscales.png")
``````

4 Likes

Well… I guess this might be a bug.

Hi all,
I have to refresh this post and really need your insights as I still have a problem plotting heatmap with log colorbar even with Gnuplot.jl. I currently use the following code

``````@gp P_save "w image notit" "set logscale cb"
``````

which generates the following figure

The problem is I need to replace y axis with its real value (using another vector called `Depth` )rather than using index from 0 to 150. And `Depth` has Non-uniform grid.
Do the same for x axis too.

P_save is 150×731 Matrix{Float64}:

I am very new to both Gnuplot.jl and gnuplot.

Thank you so much!

If I understood well, with heatmap from plots.jl you can change the colums with a string array