Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Makie.jl linked axis set limits! bug #3689

Open
bryaan opened this issue Mar 8, 2024 · 1 comment
Open

Makie.jl linked axis set limits! bug #3689

bryaan opened this issue Mar 8, 2024 · 1 comment
Assignees
Labels

Comments

@bryaan
Copy link

bryaan commented Mar 8, 2024

I have a minimal example reproducing a bug with linked axis and setting limits on them.

In this example when you click the focus button both plot series are searched for the min and max values in y that are within the current view's xlimits.

To show the bug you have to do some panning by simply click-and-drag on the top plot, then click focus button. Repeat that once or twice and you will see that the xlimits jump back to previous values which seems to be because the ax.limits[] are not properly being updated for the linked x axis.

If you instead drag on the lower plot and focus, everything works fine.

https://gist.github.com/bryaan/0bf7d34c69729b2d7145b5e8028fa90e

using Dates

using GLMakie
GLMakie.activate!()
Makie.inline!(false)  # Probably unnecessary?

using Observables

mutable struct MouseHandlerState
    pos
    xmin
    xmax
    ymin
    ymax
    was_inside_plot::Bool
    focus::Bool
    fast_forward::Bool
    function MouseHandlerState()
        new(0, 0, 0, 0, 0, false, false, false)
    end
end

function get_xlims(ax)
    limits = ax.limits[]
    if limits[1] === nothing
        return nothing, nothing
    end
    xmin, xmax = limits[1][1], limits[1][2]
end

function register_mouse_handlers(fig, ax, plot, focus)
    # Remove the rectangle zoom on left-click drag.
    deregister_interaction!(ax, :rectanglezoom)

    mouseevents = addmouseevents!(fig.scene)

    state = MouseHandlerState()

    if focus !== nothing
        on(focus) do f
            if f == true
                xmin, xmax = get_xlims(ax)

                if xmin === nothing || xmax === nothing
                    return
                end

                _ymin1, _ymax1 = get_minmax_within_xrange(timestamps, y, xmin, xmax)
                _ymin2, _ymax2 = get_minmax_within_xrange(timestamps, y2, xmin, xmax)
                ymin = min(_ymin1, _ymin2)
                ymax = max(_ymax1, _ymax2)

                if ymin == ymax || ymin == Inf || ymax == -Inf
                    C.focus[] = false
                    return
                end

                limits!(ax, xmin, xmax, ymin, ymax)
            end
        end
    end

    on(events(ax.scene).scroll) do event
        if focus !== nothing
            focus[] = false
        end
    end

    onmouseleftdrag(mouseevents) do event
        if focus !== nothing
            focus[] = false
        end
        if state.was_inside_plot
            px_delta = event.px - state.pos
            # data_delta = event.data - event.prev_data

            scale = extrema(ax.scene.px_area[])

            dx = px_delta[1] * (state.xmax - state.xmin) / scale[2][1]
            dy = -px_delta[2] * (state.ymax - state.ymin) / scale[2][2]

            # Update the axis limits to achieve panning
            limits!(ax, state.xmin - dx, state.xmax - dx, state.ymin + dy, state.ymax + dy)
        end
    end

    onmouseleftdragstart(mouseevents) do event
        state.was_inside_plot = is_mouseinside(plot)
        state.pos = event.px

        limits = extrema(ax.finallimits[])
        state.xmin, state.xmax = limits[1][1], limits[2][1]
        state.ymin, state.ymax = limits[1][2], limits[2][2]

        # limits = C.ax.limits[]
        # state.xmin, state.xmax = limits[1][1], limits[1][2]
        # state.ymin, state.ymax = limits[2][1], limits[2][2]
    end

    onmouseleftdragstop(mouseevents) do event
        state.was_inside_plot = false
    end
end



function get_minmax_within_xrange(timestamps, values, xmin, xmax)
    _start = searchsortedfirst(timestamps[], xmin)
    _end = searchsortedfirst(timestamps[], xmax)

    if _start === nothing
        _start = 1
    end
    if _end === nothing
        _end = length(values[])
    end

    _start = max(_start, 1)
    _end = min(_end, length(values[]))
    if _start == _end
        return 0, 1
    end

    @show _start, _end

    ymin = minimum(@view values[][_start:_end])
    ymax = maximum(@view values[][_start:_end])
    ymin, ymax
end


function create_focus_button(fig, area)
    focus = Observable{Bool}(false)
    buttongrid = GridLayout(area, tellwidth=false)
    btn = Button(fig, label=@lift($focus ? "_" : "FOCUS"))
    buttongrid[1, 1] = [btn]
    on(btn.clicks) do _
        focus[] = !focus[]
    end
    focus
end


fig = Figure()

fig[1, 1] = chartgrid = GridLayout(tellwidth=false)

focus = create_focus_button(fig, chartgrid[1, 1])

ts = collect(1:100)
timestamps = Observable(ts)

ax = Axis(chartgrid[2, 1])
y = Observable(sin.(ts))
lineplot = lines!(ax, timestamps, y, color=:blue)

sub_ax = Axis(chartgrid[3, 1])
y2 = Observable(sin.(ts))
sub_lineplot = lines!(sub_ax, timestamps, y2, color=:red)

linkxaxes!(sub_ax, ax)

# Pan functionality
register_mouse_handlers(fig, ax, lineplot, focus)
register_mouse_handlers(fig, sub_ax, sub_lineplot, focus)

# xlims!(ax, Dates.value(Millisecond(Minute(0))), Dates.value(Millisecond(Minute(60 * 12))))
# ylims!(ax, -100, 100)

screen = display(fig)
wait(screen)
@bryaan
Copy link
Author

bryaan commented Jun 3, 2024

Any progress on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants