Wrong heatmap orientation with Plots.jl

I do not understand what I am doing wrong plotting a 2D function. The resulted heatmap is rotated 90 deg.

using Plots

rosenbrock(x, y; a=1, b=100) = (a - x)^2 + b*(y - x^2)^2

# Define the range for x and y
x = -2:0.01:2
y = -1:0.01:3

# Create a grid of (x, y) points
z = [rosenbrock(xi, yi) for xi in x, yi in y];

# Plot the function with contour lines and log scale heatmap using Plots.jl
heatmap(x, y, log10.(z), xlabel="x", ylabel="y", title="Rosenbrock Function (Log Scale)", color=:viridis)

I get the following image


which is wrong. With Makie I get the right orientation:

fig = Figure(resolution = (800, 600))
Axis(fig[1, 1], title = "Rosenbrock Function (Log Scale)", xlabel = "x", ylabel = "y")
heatmap!(x, y, log10.(z), colormap = :viridis)

“which is wrong” - based on what?
Makie and Plots simply have two different ways of defining the default transposition of a heatmat.
Makie uses the idea that a heatmap is a matrix, so uses the default way of displaying matrices with the first dimension being rows going down.
Plots uses the idea that any plot always plots x values on the x axis, y values on the y axis, with values rising upwards and towards the left.

None of these concepts are inherently more correct. And in fact plotting packages in many languages all do this differently. The Plots way has the advantage of intuitively adding e.g. x,y scatter points on top of a heatmap in the correct location.

You can always transpose the matrix and possibly set yflip = true to get the other behaviour.

Yes

1 Like

Thanks. Wrong in the sense that I didn’t get the expected result, and I order to get it I need to explicitly ‘transpose’ the matrix.

1 Like

I actually hate plotting interfaces for heatmaps, pcolor, contours etc where the coordinates can be specified by vectors for exactly that reason: there is no commonly accepted standard and particularly if you have the same vector length (and a square matrix) you will not even see if your interpretation is wrong in a lot of cases. I would really have preferred (even if that costs some memory) that plotting tools accept only coordinates x, and y which are also matrices and of the same size as the matrix to plot. Then you are sure that any discrete point [i,j] will be at position x[i,j],y[i,j] with value z[i,j]. And it also very naturally opens the way for curvilinear plots etc.

1 Like

On the other hand, images/photos do not require transposing and in Plots.jl we can do: heatmap(rgb_image, framestyle=:none)

1 Like

Instead of transposing the matrix you also have a transpose = true keyword. It seems reasonable to have a keyword flick between different conventions.
This is also discussed in Axes swapped in heatmap ¡ Issue #273 ¡ JuliaPlots/Plots.jl ¡ GitHub and in Resolved: Heatmap transposes matrices ¡ Issue #205 ¡ MakieOrg/Makie.jl ¡ GitHub

That’s not true, Makie uses first dim → x, second dim → y for everything, which is why images are not correctly oriented by default.

julia> heatmap([
           1 2
           3 4
       ], colormap = [:red, :green, :blue, :orange])

To get the expected orientation for images, you have to do something like this:

julia> heatmap([
           1 2
           3 4
       ]', colormap = [:red, :green, :blue, :orange], axis = (; yreversed = true))

1 Like

To summarise as to why you have to transpose:

If you look at the axes in the plots you see that the x-axis has “increasing while going left”, y is “incurring while going up”. We usually would put these coordinates as (x,y)

But if you consider a matrix (which contains the pixels/ samples for your contour map colors)

A = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}

You notice:

  • the first index is the rows, but that is the vertical one (previously y)
  • the second one is the horizontal one
  • the vertical axis is the opposite direction as the y above, it is “increasing while going down”

So, we have to (1) transport the matrix, which switches the roles of the indeces, so that the first one is the horizontal one, the second the vertical, and “flip” the vertical one.

2 Likes

Sorry, I must have misremembered the discussion then. I just quoted it from memory, which was not careful enough. I can also read in the issue I linked that I actually changed my mind on this after thinking about it, and suggested changing Plots’ behaviour to match Makie’s.

Apparently 6 years is enough to forget something I felt strongly about at the time…

No problem, many people are actually confused when they plot images with Makie because they expect the right orientation by default, but it’s tricky as explained above and I think the current default makes the most sense overall. Maybe in the future there’ll be an alternative for easier image plotting

2 Likes

On the other hand, if we include the function in the heatmap, this problem disappears (i.e. both x and y are respected, and there is no need to transpose):

heatmap(x, y, log10 ∘ rosenbrock, xlabel="x", ylabel="y", color=:viridis)
1 Like

Thanks, very good suggestion.

Yes, if you have a function then this is nice and unambiguous.

That future is almost upon us:

image(Makie.logo())

image(Makie.logo(), uv_transform=:transpose)

We added the uv_transform keyword as part of a patch release, so it’s not the default yet, but we should make it the default soon!

1 Like

The problem is that this is actually not a transpose, I discussed this with @ffreyer the other day. It’s because the uvs of the underlying Rect are weird, probably chosen such that images are shown in correct orientation more easily.

Plus, even if it wasn’t buggy, for most images you would still want to count pixels from top to bottom, so you can’t really get around the yreversed = true.

To deal with this issue, the GMT.jl grid and image types have a layout field that describe the memory layout. It’s a three (or four for images) chars string with T(op)|B(ot) R(ow)|C(ol) B(and)|P(ixel), like TRBm saying if the array is Top-Bot, Row or Column major, and Band or Pixel interleaved. This last makes sense only to images.