Using Makie for Allotaxonometry

Hi,

As part of a small project of mine, I want to replicate a figure in a paper using Makie (this paper and these figures). The idea of allotaxonometry plots is that you compare two systems that are zipf distributed (in the linked figures, twitter at time t and t+1) to see the relative use of, say, terms. The key challenge is to work on a rotated scene, which I found was not too difficult using Makie (once you understand quaternions; see below for my current attempt). To do this, I simply create a heatmap and draw a background, which I then rotate using rotate!(), e.g

f = Figure()
ax = Axis(f[1,1])

# custom function to distinguish between the 2 systems  
draw_background2!(counts_rank)
heatmap!(counts_rank, colormap = Reverse(cgrad(:magma)))

q = qrotation(SVector(1, -0.415, 0), deg2rad(180))
rotate!(f.scene.children[1], q

# find the new relevant limits   
limits!(-54, 54, -108, 0)

display(f)

Now, the tricky part is that I want to (i) annotate squares far away from the axis, which represent terms that have changed the most during that time period (text annotation are now messy to work with, due to the rotation), and (ii) rotate the axis so that I can properly label them (again, see linked figures to see what I mean). Any help of how to best achieve these goals would be greatly appreciated! I think this type of plot could be a great addition to the Makie gallery.

There was some discussion about a rotated inset axis recently, might be interesting for you:

If text! doesn’t work correctly with rotate! (which I don’t think it does) you should still be able to do things manually. If you rotate the whole scene (i.e. ax.scene or f.scene.children[1]) you can get the pixel position within that scene for a data position with px_pos = project(ax.scene, position). You can use that to plot text at the correct position by plotting to f.scene, which is in pixel units. You need to add the offset from ax.scene though, so it becomes

positions = Point2f[(1, 1), (2, 1), (3, 1)]

fig, ax, p = scatter(positions, color = [:red, :green, :blue])
rotate!(ax.scene, Vec3f(0,0,1), pi/4)
xlims!(ax, -0.5, 2.0)
ylims!(ax, 1, 3)

px_positions = map(positions) do pos
    Makie.project(ax.scene, pos) + minimum(ax.scene.px_area[])
end
text!(
    fig.scene, ["First", "Second", "Third"], position = px_positions, 
    color = [:red, :green, :blue], rotation = [0.0, pi/4, -pi/4]
)

fig

or

px_positions = map(ax.scene.camera.projectionview, ax.scene.px_area) do _, px_area
    map(positions) do pos
        Makie.project(ax.scene, pos) + minimum(px_area)
    end
end

to make the text positions update automatically.

1 Like

Would love to see the finished version, once you are happy with it.

I will definitely share, hopefully as a small library that sits on Makie. With Makie’s flexibility, I have good hope that the project will work out!