Nonorthogonal axes in Makie (or maybe in other plotting package)

Is it possible to make a plot (both in 2d and 3d) using non-orthogonal axes? Something similar to this (link)


or this (link)

(though here simple image transformation is not enough for me; I want the axes transformed as well).

These types of plots appear in analysis of crystals where the atoms are arranged in a non-orthogonal lattice.

With Plots and ImageTransformations.jl you can transform the original image through a shear transformation or rotation:

using Plots, CoordinateTransformations, ImageTransformations, Images


n=2^8
I =[i for i in 0:n-1, j in 0:n-1]
h=heatmap(xor.(I, I' ), c=:plasma,  xlim=(-25, 255+25), ylim=(-25, 255+25), 
                      colorbar=false, size=(350, 350))

savefig(h, "original.png")
img=load("original.png")
α = pi/9
M = [1 tan(α); 0 1]
tr = LinearMap(M) #shear transformation; 
imgw= warp(img, tr; fillvalue=1)

Rotation:

using Rotations
rot = recenter(RotMatrix(pi/4), ImageTransformations.center(img));
imgr = warp(img, rot, fillvalue=1)

sheared-image
rotated

If none of the two types of transformations meet your requirements, then you can define a more general linear transformation, that maps the basis vectors to the direction you want:
if

T([1,0]) = v₁
T([0,1]) = v₂

then define: inv(M)=hcat(v₁, v₂).
Note that the transformation passed to warp is the inverse transformation to be applied to the original image, because warp implements the backward geometric transformation from image processing.

Thank you, but it is not exactly what I need. Of cause, as soon as you have an image, you can transform it as you like. Instead, I would like to have the original plot in these coordinates. It is especially important in 3D case, where I want to inspect the data by zooming, rotating, etc.

In makie you can play with perspective in Axis3, I guess in Plots too (the contrary would be surprising). In a way this renders non orthogonal axes.
Regarding your demand related to crystals, you could apply a back-transformation of the non-orthogonal crystal shape to an orthogonal one, plot it, then re-apply the transformation to get both axes and data in the desired shape.

A side note : pgfplots can handle such transformations (at least in 2d) see pgfplots - Minkowski diagram non-orthogonal axis - TeX - LaTeX Stack Exchange. Since its accessible in julia using Home · PGFPlotsX.jl you could try that.

In makie you can play with perspective in Axis3

As with the image transformations, this approach is not dictated by the data one wish to plot.

Regarding your demand related to crystals, you could apply a back-transformation of the non-orthogonal crystal shape to an orthogonal one

Yes, this I can do.

then re-apply the transformation to get both axes and data in the desired shape

But how? I can re-apply the transformation, but the standard plotting functions, like heatmap(x, y, values) accept x and y coordinates assuming that they are defined in orthogonal Cartesian axes.

pgfplots can handle such transformations (at least in 2d)

Yes, that is what I need. But I need it in an interactive regime to explore the data.

If you map the data to cartesian space, then you can use heatmap normally.

Another solution could be to not use heatmap or image altogether, but poly and lines… This way you can plot any group of polygons, here distorted pixels of diamond shape with their colors, as well as pseudo-axis lines. Anything of higher-level would not achieve what you want I believe.

For Makie, you can do it but it’s fully manual. An axis is just made out of plot objects, so you can draw whatever you want, including an oblique axis. But I assume your question was less about pure possibility, but if there’s something already implemented. To that, not yet, no.

If I will have to use some low level functions it is fine.
Can you share any example with me?

It’s a very basic example but it shows you how to show an image as a non rectangular shape using poly

using GLMakie, FileIO

# load image, see Makie `image`
img = load(assetpath("cow.png"))
# Define pixel dimensions
wh = 1
ht = 1
# Define pixel shape here a rectangle
refdiamond = Point2f[(0,0), (wh,0), (wh,ht),(0,ht)]

# Start figure and axis
f =  Figure()
ax = Axis(f[1,1])
# Compute all pixel positions
diamonds = [refdiamond .+ Point2f((i-1+j)*wh,(j-1)*ht) for i in axes(img,2) for j in axes(img,1)]
# Display the pixels using the colors in img
poly!(ax,diamonds,color = vec(img))
# Hide axes
hidedecorations!(ax)
f

Perfect! Thank you!
color = vec(img) - this is kind of surprising

Well an image is a matrix of color values, one per pixel, so vecing it just allows to retrieve a vector or colors, 1 per diamond in diamonds

It’s probably also possible to do the shearing with a model transform instead of manually calculating it and using polys for each pixel.

The adapted code for arbitrary guiding vectors and with pixel shape correction:

import GLMakie as mak


# Data to plot:
xmin, xmax, Nx = -3, 3, 100
ymin, ymax, Ny = -3, 3, 200
x = range(xmin, xmax, length=Nx)
y = range(ymin, ymax, length=Ny)
img = @. exp(-(x * y')^2)


# Axes guiding vectors:
# a1, a2 = mak.Point2f(1,0), mak.Point2f(0,1)
# a1, a2 = mak.Point2f(2,1), mak.Point2f(1,2)
a1, a2 = mak.Point2f(1,-0.5), mak.Point2f(-0.5,1)

# normalize axes vectors:
a1 = a1 / sqrt(sum(abs2, a1))
a2 = a2 / sqrt(sum(abs2, a2))


# Define pixel:
dx = step(x)
dy = step(y)
dp = mak.Point2f(sqrt(dx*dy), sqrt(dx*dy))

# pixel = mak.Point2f[(0,0), (dp[1],0), (dp[1],dp[2]), (0,dp[2])]   # square pixel
pixel = [mak.Point2f(0,0), dp*a1, dp*(a1+a2), dp*a2]   # skew pixel


# Compute all pixel positions:
pixels = [
    pixel .+ ((i-1) * dp[1] * a1 + (j-1) * dp[2] * a2) for j=1:Ny for i=1:Nx
]


# Figure and axis:
fig = mak.Figure()
ax = mak.Axis(fig[1,1])
mak.hidedecorations!(ax)

# draw new axes:
xlen = 1.1 * (dp[1] * Nx)
ylen = 1.1 * (dp[2] * Ny)
O = mak.Point2f(0,0)
mak.lines!(ax, [O, O + xlen*a1]; color=:black)
mak.lines!(ax, [O, O + ylen*a2]; color=:black)
mak.text!(ax, mak.L"a_1"; position=O+xlen*a1, color=:black)
mak.text!(ax, mak.L"a_2"; position=O+ylen*a2, color=:black)

# draw the resulting heatmap:
mak.poly!(ax, pixels, color=vec(img))

fig



:grinning:

1 Like

Now, can it be done in 3D in a similar manner for plotting of 3D arrays? What should be the corresponding 3D pixel?

You’d need to use mesh for that directly, instead of poly, because that one uses an algorithm to decompose to a mesh that works only in 2D.