# Makie max pan zoom

**URL:** <https://discourse.julialang.org/t/makie-max-pan-zoom/114105>\
**Category:** Visualization\
**Tags:** makie\
**Created:** [May 10, 2024, 12:45pm UTC](https://discourse.julialang.org/t/makie-max-pan-zoom/114105 "2024-05-10T12:45:01Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![bjarthur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bjarthur/32/9638_2.png) [@bjarthur](https://discourse.julialang.org/u/bjarthur)\
**Post date:** [May 10, 2024, 12:45pm UTC](https://discourse.julialang.org/t/makie-max-pan-zoom/114105/1 "2024-05-10T12:45:01Z")

</div>

i’d like to put a maximum on how much a user can pan or zoom out on a plot. but i could not find a function in the makie docs to set an upper bound on the axis limits. so i hacked it like this:

```julia
using GLMakie
f,a,s = scatter([1,2,3])
on(a.finallimits) do x
    if x.origin[2]>5 
        a.finallimits[] = GLMakie.GeometryBasics.HyperRectangle{2, Float32}([x.origin[1], 5], x.widths)
    end
end

```

try panning upwards. you can’t go above the lower y-value being 5 now.

but is there a more cannonical way to do this? did i just miss a function to do this for you? seems like it would be a nice feature to have.

---

<div class="post-metadata">

**Author:** ![bjarthur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bjarthur/32/9638_2.png) [@bjarthur](https://discourse.julialang.org/u/bjarthur)\
**Post date:** [May 14, 2024, 12:23am UTC](https://discourse.julialang.org/t/makie-max-pan-zoom/114105/2 "2024-05-14T00:23:53Z")

</div>

small correction-- `targetlimits` should be modified not `finallimits`.

so here then is an example which prevents the user from zooming out further than the limits of the data:

```julia
using GLMakie
x, y = rand(10), rand(10)
f,a,s = scatter(x,y)
on(a.finallimits) do fl
    o1 = max(fl.origin[1], minimum(x))
    o2 = max(fl.origin[2], minimum(y))
    w1 = min(fl.widths[1]+fl.origin[1], maximum(x))-o1
    w2 = min(fl.widths[2]+fl.origin[2], maximum(y))-o2
    a.targetlimits[] = GLMakie.GeometryBasics.HyperRectangle{2, Float32}([o1,o2],[w1,w2])
end

```

i believe an optional feature like this is genuinely useful.

one way to build this into Makie would be to add a `bounds` variable to the [Axis struct](https://github.com/MakieOrg/Makie.jl/blob/master/src/makielayout/types.jl#L578), and then insert a `max` and `min` in the [ScrollZoom interaction](https://github.com/MakieOrg/Makie.jl/blob/master/src/makielayout/interactions.jl#L275) to enforce it. i didn’t go to the trouble of submitting a PR due to the lackluster response this post created.

alternatively, one could `deactivate_interaction!` the default, and add your own, which included these modifications.

---

<div class="post-metadata">

**Author:** ![bjarthur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bjarthur/32/9638_2.png) [@bjarthur](https://discourse.julialang.org/u/bjarthur)\
**Post date:** [July 18, 2025, 9:20pm UTC](https://discourse.julialang.org/t/makie-max-pan-zoom/114105/3 "2025-07-18T21:20:09Z")

</div>

here is a custom ScrollZoomLimit interaction, which permits zooming when scrolling like the default ScrollZoom interaction for Axis, but if ax.limits is set to something other than nothing, then you can’t zoom out further than those limits. the code is a very small modification to that in the makie source, just another 16 lines.

i would submit a PR, but a mechanism to also limit how much one can zoom in would be more generally useful, and would require some sort of additional state to specify those minimum limits.

```julia
import Base: RefValue
import Makie: Automatic, timed_ticklabelspace_reset

mutable struct ScrollZoomLimit
    speed::Float32
    reset_timer::RefValue{Union{Nothing, Timer}}
    prev_xticklabelspace::RefValue{Union{Automatic, Symbol, Float64}}
    prev_yticklabelspace::RefValue{Union{Automatic, Symbol, Float64}}
    reset_delay::Float32
end

function ScrollZoomLimit(speed, reset_delay)
    return ScrollZoomLimit(speed, RefValue{Union{Nothing, Timer}}(nothing), RefValue{Union{Automatic, Symbol, Float64}}(0.0), RefValue{Union{Automatic, Symbol, Float64}}(0.0), reset_delay)
end

function Makie.process_interaction(s::ScrollZoomLimit, event::ScrollEvent, ax::Axis)
    # use vertical zoom
    zoom = event.y

    tlimits = ax.targetlimits

    xlimit = zoom < 0 && all(.!isnothing.(ax.limits[][1]))
    ylimit = zoom < 0 && all(.!isnothing.(ax.limits[][2]))

    xzoomlock = ax.xzoomlock
    yzoomlock = ax.yzoomlock
    xzoomkey = ax.xzoomkey
    yzoomkey = ax.yzoomkey

    scene = ax.scene
    e = events(scene)
    cam = camera(scene)

    ispressed(scene, ax.zoombutton[]) || return Consume(false)

    if zoom != 0
        pa = viewport(scene)[]

        z = (1.0 - s.speed)^zoom

        mp_axscene = Vec4d((e.mouseposition[] .- pa.origin)..., 0, 1)

        # first to normal -1..1 space
        mp_axfraction = (cam.pixel_space[] * mp_axscene)[Vec(1, 2)] .*
            # now to 1..-1 if an axis is reversed to correct zoom point
            (-2 .* ((ax.xreversed[], ax.yreversed[])) .+ 1) .*
            # now to 0..1
            0.5 .+ 0.5

        xscale = ax.xscale[]
        yscale = ax.yscale[]

        transf = (xscale, yscale)
        tlimits_trans = Makie.apply_transform(transf, tlimits[])

        xorigin = tlimits_trans.origin[1]
        yorigin = tlimits_trans.origin[2]

        xwidth = tlimits_trans.widths[1]
        ywidth = tlimits_trans.widths[2]

        newxwidth = xzoomlock[] ? xwidth : xwidth * z
        newywidth = yzoomlock[] ? ywidth : ywidth * z

        newxorigin = xzoomlock[] ? xorigin : xorigin + mp_axfraction[1] * (xwidth - newxwidth)
        newyorigin = yzoomlock[] ? yorigin : yorigin + mp_axfraction[2] * (ywidth - newywidth)

        if xlimit
            xlimwidth = ax.limits[][1][2] - ax.limits[][1][1]
            newxwidth = min(newxwidth, xlimwidth)
            if newxwidth == xlimwidth && newxorigin < ax.limits[][1][1]
                newxorigin = min(ax.limits[][1][1], newxorigin + 2 * (xorigin - newxorigin))
            end
        end
        if ylimit
            ylimwidth = ax.limits[][2][2] - ax.limits[][2][1]
            newywidth = min(newywidth, ylimwidth)
            if newywidth == ylimwidth && newyorigin < ax.limits[][2][1]
                newyorigin = min(ax.limits[][2][1], newyorigin + 2 * (yorigin - newyorigin))
            end
        end

        timed_ticklabelspace_reset(ax, s.reset_timer, s.prev_xticklabelspace, s.prev_yticklabelspace, s.reset_delay)

        newrect_trans = if ispressed(scene, xzoomkey[])
            Rectd(newxorigin, yorigin, newxwidth, ywidth)
        elseif ispressed(scene, yzoomkey[])
            Rectd(xorigin, newyorigin, xwidth, newywidth)
        else
            Rectd(newxorigin, newyorigin, newxwidth, newywidth)
        end
        inv_transf = Makie.inverse_transform(transf)
        tlimits[] = Makie.apply_transform(inv_transf, newrect_trans)
    end

    # NOTE this might be problematic if if we add scrolling to something like Menu
    return Consume(true)
end

```

---

<div class="post-metadata">

**Author:** ![asinghvi17](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asinghvi17/32/8272_2.png) [@asinghvi17](https://discourse.julialang.org/u/asinghvi17)\
**Post date:** [July 20, 2025, 6:31pm UTC](https://discourse.julialang.org/t/makie-max-pan-zoom/114105/4 "2025-07-20T18:31:13Z")

</div>

Ooh I should also do that for GeoMakie…
