Visual connection between different axes in Makie.jl

Hello,

I have a figure generated with CairoMakie.jl. This figure contains two axes, something like


fig = Figure(size=(380, 220))

ax1 = Axis(fig[1, 1])
ax2 = Axis(fig[2, 1])


Now, I want to draw some lines (or even better a polygon with a filled color) between a point in ax1 and a point in ax2, but I don’t know how to do it.

Moreover, is it possible to directly link a point in ax1 to the upper corners of ax2?

There may be an easier way that I am not aware of, but when I did something similar in the past, I calculated the pixel-space coordinates of all involved points and plotted to the scene underlying the figure.
If noone else chimes in I can share my code in the coming days.
For your second question: what do mean by linking? Drawing a connecting line?

Yes, I mean just a simple line. More or less like an inset zoom effect, where the zoomed area is plotted in another axis

You can embed your two axes in a bigger axis like so:

using GLMakie 

fig = Figure()
gl = GridLayout(fig[1,1])
ax = Axis(fig[1,1])
xlims!(ax, 0, 1)
ylims!(ax, 0, 1)
ax1 = Axis(gl[1,1])
ax2 = Axis(gl[2,1])
hidedecorations!(ax)
hidespines!(ax)
scatterlines!(ax, [0.2,0.7] , [0.2,0.7]; overdraw = true)
fig

overdraw

I’ve never quite been able to figure out how to get all the coordinates of the subaxes without manually specifying them upfront though.

Ok this is a way. But I need the precise position of the end points. The first one has to be one point in ax1 and the second one has to be the upper-left corner of ax2 for example.

I did something similar here: Beautiful Makie

with

function posFig(ax, x; yoff=100, ylow = 15)
    o = ax.scene.viewport[].origin - Point2f(0, yoff)
    return Makie.project(ax.scene, Point2f(x, ylow)) + o
end

maybe you will need to do some minor changes.

2 Likes

Thanks!

I was able to do it by changing a bit your function and plotting the lines in the fig.scene, just like the link you gave me.

glad to read, please consider contributing here with a MWE of your solution, so that others can benefit in the future!


using CairoMakie

function posFig(ax, x, y)
    o = ax.scene.viewport[].origin
    return Makie.project(ax.scene, Point2f(x, y)) + o
end

fig = Figure()
ax1 = Axis(fig[1, 1:3])
ax2 = Axis(fig[2, 1])
ax3 = Axis(fig[2, 2])
ax4 = Axis(fig[2, 3])

pts1 = [posFig(ax1, 5, 2), posFig(ax2, 5, 5)]
pts2 = [posFig(ax1, 5, 2), posFig(ax3, 5, 5)]
pts3 = [posFig(ax1, 5, 2), posFig(ax4, 5, 5)]

lines!(fig.scene, getindex.(pts1, 1), getindex.(pts1, 2))
lines!(fig.scene, getindex.(pts2, 1), getindex.(pts2, 2))
lines!(fig.scene, getindex.(pts3, 1), getindex.(pts3, 2))

fig

4 Likes

Is there any way to make this work for GLMakie? In the initial plot it works, but once I zoom in on one of the axes the lines stay in screen-space and are thus no longer in the correct location.

The following version (incremented from the previous solutions)
updates interactively, using observables:

using GLMakie

function pos_fig_obs(ax, x, y)
	lift(ax.scene.viewport, ax.finallimits) do vp, lims
		Makie.project(ax.scene, Point2f(x, y)) + vp.origin
	end
end

let
	fig = Figure()
	ax1 = Axis(fig[1, 1]; xticks = 0:10, yticks=0:10)
	ax2 = Axis(fig[2, 1]; xticks = 0:10, yticks=0:10)

	pos_fig_1 = pos_fig_obs(ax1, 1.0, 2.0)
	pos_fig_2 = pos_fig_obs(ax2, 3.0, 4.0)
	pts = @lift [$pos_fig_1, $pos_fig_2]

	lines!(fig.scene, pts)

	fig
end

But the positions can be a bit off if the mouse moves too fast
Here is an extreme case (after playing with zoom and pan):
Screenshot_20240907_221338

This is probably because Makie.project is called a bit later,
and thus may get observable values that are inconsistent with vp.origin.

As a workaround, slowing down the mouse at the end of the movement helps.