Skip to content

Commit 5d2e83f

Browse files
committed
WIP: Make + and - zoom in and out, respectively
This adds a zoom function to the PCanvas and implements for the two current backends. For vispy, the zoom happens under the cursor, like how the mouse wheel behaves. But for pygfx, I did not yet figure out how to do that. Do we need to translate the focused point to the origin, then change zoom, then translate it back?
1 parent 10abe13 commit 5d2e83f

File tree

4 files changed

+45
-0
lines changed

4 files changed

+45
-0
lines changed

src/ndv/viewer/_backends/_protocols.py

+5
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ def set_range(
9797
z: tuple[float, float] | None = None,
9898
margin: float = ...,
9999
) -> None: ...
100+
def zoom(
101+
self,
102+
factor: float | tuple,
103+
center: tuple | None = None,
104+
) -> None: ...
100105
def refresh(self) -> None: ...
101106
def qwidget(self) -> QWidget: ...
102107
def add_image(

src/ndv/viewer/_backends/_pygfx.py

+17
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,23 @@ def set_range(
561561
cam.zoom = 1 - margin
562562
self.refresh()
563563

564+
def zoom(self, factor: float | tuple, center: tuple | None = None):
565+
"""
566+
Zoom in (or out) at the given center.
567+
568+
Parameters
569+
----------
570+
factor : float or tuple
571+
Fraction by which the scene should be zoomed (e.g. a factor of 2
572+
causes the scene to appear twice as large).
573+
center : tuple of 2-4 elements
574+
The center of the view. If not given or None, use the
575+
current center.
576+
"""
577+
cam = self._camera
578+
cam.zoom *= factor
579+
self.refresh()
580+
564581
def refresh(self) -> None:
565582
self._canvas.update()
566583
self._canvas.request_draw(self._animate)

src/ndv/viewer/_backends/_vispy.py

+15
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,21 @@ def set_range(
580580
max_size = max(x[1], y[1], z[1])
581581
self._camera.scale_factor = max_size + 6
582582

583+
def zoom(self, factor: float | tuple, center: tuple | None = None):
584+
"""
585+
Zoom in (or out) at the given center.
586+
587+
Parameters
588+
----------
589+
factor : float or tuple
590+
Fraction by which the scene should be zoomed (e.g. a factor of 2
591+
causes the scene to appear twice as large).
592+
center : tuple of 2-4 elements
593+
The center of the view. If not given or None, use the
594+
current center.
595+
"""
596+
return self._camera.zoom(factor=factor, center=center)
597+
583598
def canvas_to_world(
584599
self, pos_xy: tuple[float, float]
585600
) -> tuple[float, float, float]:

src/ndv/viewer/_viewer.py

+8
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ def __init__(
153153
# ROI
154154
self._roi: PRoiHandle | None = None
155155

156+
# closest data point under the mouse
157+
self._data_coords: tuple[int, int] = (0, 0)
158+
156159
# WIDGETS ----------------------------------------------------
157160

158161
# the button that controls the display mode of the channels
@@ -731,6 +734,7 @@ def _update_hover_info(self, event: QMouseEvent) -> bool:
731734

732735
x = int(x)
733736
y = int(y)
737+
self._data_coords = (x, y)
734738
text = f"[{y}, {x}]"
735739
# TODO: Can we use self._canvas.elements_at?
736740
for n, handles in enumerate(self._img_handles.values()):
@@ -765,6 +769,10 @@ def keyPressEvent(self, a0: QKeyEvent | None) -> None:
765769
if a0.key() == Qt.Key.Key_Delete and self._selection is not None:
766770
self._selection.remove()
767771
self._selection = None
772+
elif a0.key() in [Qt.Key.Key_Plus, Qt.Key.Key_Equal]:
773+
self._canvas.zoom(factor=0.667, center=self._data_coords)
774+
elif a0.key() in [Qt.Key.Key_Minus, Qt.Key.Key_Underscore]:
775+
self._canvas.zoom(factor=1.5, center=self._data_coords)
768776

769777
def _update_roi_button(self, event: QMouseEvent) -> bool:
770778
if self._add_roi_btn.isChecked():

0 commit comments

Comments
 (0)