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.
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