Customizing Makie rectangle zoom mouse interaction and axis limits

I am trying to create a Makie dashboard where several axes (e.g. x-y, t-z) with the same data points will update simultaneously based on the mouse selection of data in any of the axes.

I made an observable, selection[], a Bool array of length(points) indicating which points to show. When this changes, it will again plot the data, in an animated (stepwise) manner, colored by time. This part works.

Now I would like to use the existing mouse rectangle zoom. As default, it only magnifies data by adjusting the axis limits - except that when I check ax.limits, they are the initial ones. Where can I find the actual shown limits or the result of the mouse rectangle, in data space (not pixels)?

I see the result of the mouse rectangle in interactions(ax):

  :rectanglezoom => (1, RectangleZoom(#1269, Observable(false), false, false, Float32[262.727, 14.6552], Float32[282.068, 0.0], Observable(HyperRec…

I am not sure how to retrieve it from this Dict or from some axis parameter. With these limits I want to return a new selection[] of the data within the rectangle limits and trigger the update in all axes.

What is the best way to do it?

ax.finallimits is the observable that holds the limits the axis actually shows at a given time

1 Like

Thanks for pointing me to ax.finallimits, it appears not documented at all in the Makie documentation.

My current issue is that when I trigger on the ax.finallimits change, and update my selection[] observable, and empty! the axes (keeps finallimits)… it (1) does not actually clear the axes at all, and (2) resets the finallimits to ax.limits when I try to plot the new data:

on(ax0.finallimits) do lims
    x1 = Float64(minimum(lims)[1])
    x2 = Float64(maximum(lims)[1])
    println(x1)
    println(x2)
    empty!(ax0)
    empty!(ax1)
    poly!(ax1, countries; color=(:black,0.0), strokecolor = :black, strokewidth = 2.0, overdraw = true)
    selection[] = lma.qua .&& (x1 .< timv .<= x2)
    return selection
end

on(selection) do sel
    nrsteps = 100
    stepparam = timv[sel]
    stepsize = (maximum(stepparam) - minimum(stepparam)) / nrsteps
    binnr,bindex,bincount = discretize(stepparam, minimum(stepparam):stepsize:maximum(stepparam))
    for i in 1:nrsteps
        step = bindex[:,i]
        coloring = norm(stepparam)
        s0 = scatter!(ax0,tz[sel][step]; marker=:rect,markersize=10,strokewidth=0.01,color=coloring[step],colorrange=(0,1),colormap=:turbo)    
        s1 = scatter!(ax1,xy[sel][step]; marker=:rect,markersize=10,strokewidth=0.01,color=coloring[step],colorrange=(0,1),colormap=:turbo)    
        sleep(0.03) # controls animation speed
    end
end

Edit: It does empty the axes when I do not include the update to selection[]. It seems as if selection[] is actually updated before I empty the axes.

yeah ax.finallimits is currently internal, that’s why it’s not documented. to avoid the axis limits resizing when you plot something, you have to set ax.limits to something specific, like (1, 2, 3, 4). Otherwise reset_limits!, which will be called when something is plotted into a currently visible axis, will reset to automatically determined limits. ax.limits is what changes as well when you use xlims! etc.

1 Like

Okay, I think I can work with ax.limits taking x1 and x2, but the issue remains that axes do not get emptied despite the empty! statements placed before selection[] = . Perhaps I am not handling the observables well with the on() do statements?

Continuing from 4 days ago. It now partially works:

on(ax0.finallimits) do lims
    x1 = Float64(minimum(lims)[1])
    x2 = Float64(maximum(lims)[1])
    empty!(ax0)
    empty!(ax1)
    xlims!(ax0,x1,x2)
    poly!(ax1, countries; color=(:black,0.0), strokecolor = :black, strokewidth = 2.0, overdraw = true)
    selection[] = lma.qua .&& (x1 .< timv .<= x2)
end

This works properly when I trigger ax0.finallimits by giving a manual xlims! command. It clears the axes and animates the plotting of points as intended.

But when I drag the mouse to cause an ax0.finallimits change, the plot stays frozen, with mouse selection gray mask visible, for the time the now invisible animation should take (6 seconds). Then, it reveals the new plot all at once. So it triggered, but it somehow is hiding the animation.

Any thoughts are very welcome!

I solved it after watching a Youtube video on Makie interactivity.
The animation now runs as intended by using @async for i in 1:nrsteps
Apparently, @async has not made it into docs.makie.org yet.

Hm yes good point. Maybe because I don’t fully understand when you need it and when not. Like I would assume that sleep yields but it doesn’t seem to play well with the renderloop necessarily

Just a small addition. Now I am using:

on(ax0.finallimits) do tlims
    if (now() - change[]) > Millisecond(2000)
        x1 = Float32(minimum(tlims)[1])
        x2 = Float32(maximum(tlims)[1])
        println([x1,x2])
        ax0.limits.val = ((x1,x2),ax0.limits.val[2])
        change[] = now()
        inside_timeinterval[] = (x1 .< tim .<= x2)
    end
end

instead of using xlims! or limits! I first replaced those functions by updating ax.limits.val directly. However, it still printed multiple times. For a similar function selecting a latitude-longitude area it updated like 8 times as apparently every of the 4 limits updates a tuple of 2 inside, and I also suspected the mouse action to cause more than one trigger. It was noticeably laggy. Also, inputting Float64 generated extra triggers from conversion to Float32, this is why I make them Float32 before.
My solution was to create a change = Observable(Dates.now()) to prevent the loop from executing more than once in 2 seconds. I hope this can be useful for someone else as well.