How to plot a 2D rotated array

I am trying to plot a heatmap (or contourf, or similar) of a 2D array as function of rotated x and y coordinates. Here a code snippet of what I am trying to achieve:

using Plots
α      = 10.0
lx, ly = 300.0, 100.0
nx, ny = 61, 21
(xc, yc ) = (LinRange(-lx/2, lx/2, nx), LinRange(-ly/2, ly/2, ny))
(Xc2,Yc2) = ([x for x=xc,y=yc], [y for x=xc,y=yc])
(X2r,Y2r) = (Xc2*cosd(α) - sind(α)*Yc2, Xc2*sind(α) + cosd(α)*Yc2)
display(heatmap(X2r, Y2r, rand(nx,ny)'))

Because of the rotation, the coordinate passed to the plotting function must be 2D array as well. This seems to be a problem in Plots.jl as the 2D plotting routines only accept coordinate vectors. Thus I get the following error:

ERROR: MethodError: no method matching heatmap_edges(::Surface{Array{Float64,2}}, ::Symbol)

The above exemple would work if I do following display(heatmap(xc, yc, rand(nx,ny)')) but then I do not get rotation. Any hints how to solve this issue are welcome !

1 Like

You mean, something like

using GMT

julia> G = GMT.peaks();

julia> imshow(G, view=(45,90), xlabel="X", ylabel="Y", fmt=:png)

1 Like

Yes, thanks @joa-quim for the example using GMT. Do you know if this could be achieved using Plots.jl (or similar) as well ?

Sorry, I guess it does it too but I don’t use plots so unable to help you there.

GeoArrays.jl seems to have that capability using Plots, see bottom of https://github.com/evetion/GeoArrays.jl. Maybe you can check their source?

Can you plot in cartesian coordinations with a function that transforms x and y?

α = pi/3

# make some data
sz = (40, 80)
dat = reshape(1:prod(sz), sz)
x, y = axes(dat)

# performs rotation about center (or about c if provided)
# rounds and clamps the return value so it's a valid index
function rotate_coord(x, y, α, c = sz .÷ 2)
    x -= c[1]; y -= c[2]
    sα, cα = sincos(α)

    new_pt = (x*cα - y*sα, x*sα + y*cα) .+ c
    return clamp.(round.(Int, new_pt), 1, sz)
end 

# plot heatmap(x, y, f(x, y)).
heatmap(x, y, (x, y) -> dat[rotate_coord(x, y, α)...])

Thanks @tomerarnon for your suggestion. From testing it, it looks like it rotates the data but not the plot axes.

1 Like

Thanks for suggesting. However the example on git does not rotate the figure for plotting as depicted on the README…

An alternative working solution is to use pcolor() available within PyPlot.jl to produce a heatmap-like output for a 2D data array as function of 2D coordinate fields (which can be rotated or not):

using PyPlot
α      = 10.0
lx, ly = 300.0, 100.0
nx, ny = 61, 21
(xc, yc ) = (LinRange(-lx/2, lx/2, nx), LinRange(-ly/2, ly/2, ny))
(Xc2,Yc2) = ([x for x=xc,y=yc], [y for x=xc,y=yc])
(X2r,Y2r) = (Xc2*cosd(α) - sind(α)*Yc2, Xc2*sind(α) + cosd(α)*Yc2)
pcolor(X2r, Y2r, rand(nx,ny))

which will produce following output

Would be nice to have such capability within the Plots.jl environment. Any suggestion welcome.

2 Likes

One suggestion using Plots.jl, is to display the data via colored rotated rectangles. The result looks like a rotated heatmap for practical purposes:

Plots.jl gr() code (no bells & whistles)
using ColorSchemes, Plots; gr(dpi=600)
import ColorSchemes.viridis

rotx(x,y,α) = x*cosd(α) - y*sind(α)
roty(x,y,α) = x*sind(α) + y*cosd(α)

function rectangle(x, y, w, h, α)
    X, Y = [0,w,w,0], [0,0,h,h]
    Shape(x .+ rotx.(X,Y,α), y .+ roty.(X,Y,α))
end

# 1 - INPUT DATA
α        = 45.0
x1, x2   = -150, 150
y1, y2   = -80, 80
w, h     = 4.0, 2.0
xc, yc   = -w/2 .+ range(x1, x2, step=w), -h/2 .+ range(y1, y2, step=h)
cp       = Iterators.product(xc,yc)
Xc, Yc   = first.(cp), last.(cp)
Xr, Yr   = rotx.(Xc,Yc,α), roty.(Xc,Yc,α)
Z        = sin.(hypot.(Xr,Yr)/10)                    # some function of (x,y)


# 2-  PLOT ROTATED HEATMAP
zc       = (Z .- minimum(Z))/(maximum(Z) - minimum(Z))    # normalize extrema to (0,1)
Cz       = get.(Ref(viridis), zc)                         # compute RGB colors
clims    = extrema(Z)
heatmap(xc, yc, NaN*Z, ratio=1, clims=clims, frame=:none, legend=:outerright)
for (x, y, c) in zip(Xr,Yr,Cz)
    plot!(rectangle(x, y, w, h, α), c=c, lw=0.1, lc=c);
end

xaxis    = [-w/2 + x1, w/2 + x2]
yaxis    = [-h/2 + y1, h/2 + y2]
xt, yt   = LinRange(x1, x2, 9), LinRange(y1, y2, 9)
Xxt, Yxt = rotx.(xt, yaxis[1] .+ zero(xt), α), roty.(xt, yaxis[1] .+ zero(xt), α)
Xyt, Yyt = rotx.(xaxis[1] .+ zero(yt), yt, α), roty.(xaxis[1] .+ zero(yt), yt, α)
Xx, Xy   = rotx.(xaxis, yaxis[1] .+ [0,0], α), roty.(xaxis, yaxis[1] .+ [0,0], α)
Yx, Yy   = rotx.(xaxis[1] .+ [0,0], yaxis, α), roty.(xaxis[1] .+ [0,0], yaxis, α)
c        = (y2-y1)/(x2-x1)
dX, dY   = c*[diff(Xx)[], diff(Xy)[]]/10,  [diff(Yx)[], diff(Yy)[]]/10
for (x,y) in zip(Xxt,Yxt)
    plot!([x, x - dY[1]/3], [y, y - dY[2]/3], lc=:black)
end
for (x,y) in zip(Xyt,Yyt)
    plot!([x, x - dX[1]/3], [y, y - dX[2]/3], lc=:black)
end
plot!(Xx, Xy, lc=:black, lw=1)
plot!(Yx, Yy, lc=:black, lw=1)
annotate!(Xxt .- dY[1], Yxt .- dY[2], text.(string.(round.(xt, digits=1)), 5, rotation=α, "Computer Modern"))
annotate!(Xyt .- dX[1], Yyt .- dX[2], text.(string.(round.(yt, digits=1)), :right, 5, rotation=α, "Computer Modern"))
Plots.current()
2 Likes

Another version using ImageTransformations.jl and Plots.jl:

using ImageTransformations,  Rotations, OffsetArrays, CoordinateTransformations, Plots
s=0.05
yl = range(-5, stop= 5, step=s)
xl = range(-5, stop= 10, step=s)
x = [xe for ye in yl, xe in xl]
y = [ye for ye in yl, xe in xl]
z = @. sin(x)^10 + cos(10 + y*x) + cos(x) + 0.2*y + 0.1*x
trfm = CoordinateTransformations.recenter(RotMatrix(-pi/4), ImageTransformations.center(z));
rotatedz = warp(z, trfm; fillvalue=NaN); 
m, n = size(rotatedz)
newz = OffsetArray(rotatedz, 1:m, 1:n)
heatmap(newz, c=:curl, size=(450, 360))

rotated-heatmap

@empet, your heatmap en pète, but if we use few cells we see they are not rotated:

Your pixelated heatmap is associated to an usual matrix (newz), hence it is expected to have squares of sides parallel to axes.
This mehod, based on ImageTransformations.jl, works for “continuous” heatmaps, because the function warp acts on non-pixelated images.

If I turn my lunch box, I don’t want it to come out bitten.

Wasn’t it a OP’s condition that the axes are rotated too? Only one posted solution fulfills it.

Look again.

1 Like

Ok, Ok, 2. (but that colorbar, tch,tch, it should join the group too :slight_smile: )

using   Images, Plots
s=0.05
yl = range(-5, stop= 5, step=s)
xl = range(-5, stop= 10, step=s)
x = [xe for ye in yl, xe in xl]
y = [ye for ye in yl, xe in xl]
z = @. sin(x)^10 + cos(10 + y*x) + cos(x) + 0.2*y + 0.1*x
pl= heatmap(z, c=:curl, size=(500, 300))
png("heatmap")
img= load("heatmap.png")
newimg = imrotate(img, pi/4, fillvalue=1)