From 44b8922ed689db53b5e47be082aff2c437424446 Mon Sep 17 00:00:00 2001 From: Marcelo Zoccoler Date: Mon, 18 Nov 2024 09:43:41 +0100 Subject: [PATCH] enables functionality of holding SHIFT to draw circle or square This implementation is not done via matplotlib itself because it does not work if the canvas is used inside napari (napari may overwrite some default hotkeys) --- src/biaplotter/selectors.py | 40 +++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/biaplotter/selectors.py b/src/biaplotter/selectors.py index b2f9827..5491651 100644 --- a/src/biaplotter/selectors.py +++ b/src/biaplotter/selectors.py @@ -7,6 +7,8 @@ from abc import ABC, abstractmethod from matplotlib.path import Path as mplPath from matplotlib.widgets import LassoSelector, RectangleSelector, EllipseSelector +from qtpy.QtGui import QGuiApplication +from qtpy.QtCore import Qt if TYPE_CHECKING: @@ -64,6 +66,23 @@ def remove(self): self._selector = None +class MplRectangleSelector(RectangleSelector): + """Custom rectangle selector class. + + Sub-class of matplotlib RectangleSelector to ensure, via 'qtpy', the option to draw a square when holding the SHIFT key. + + Note: matplotlib RectangleSelector already has this functionality via the 'state_modifier_keys' argument, but it doesn't work if the canvas is used inside napari. + """ + def _onmove(self, event): + modifiers = QGuiApplication.keyboardModifiers() + if modifiers == Qt.ShiftModifier: + self.add_state('square') + else: + if 'square' in self._state: + self._state.remove('square') + super()._onmove(event) + + class BaseRectangleSelector(Selector): """Base class for creating a rectangle selector. @@ -131,11 +150,28 @@ def create_selector(self): Interactive is set to True to allow for interaction. Drag from anywhere is set to True to allow for drawing from any point. """ - self._selector = RectangleSelector(self.ax, self.on_select, useblit=True, button=[ + self._selector = MplRectangleSelector(self.ax, self.on_select, useblit=True, button=[ 1], minspanx=5, minspany=5, spancoords='pixels', interactive=True, drag_from_anywhere=True, props=dict(facecolor='#00c18c', edgecolor='#00c18c', alpha=0.3, fill=True, linewidth=2.5, linestyle='--')) +class MplEllipseSelector(EllipseSelector): + """Custom ellipse selector class. + + Sub-class of matplotlib EllipseSelector to ensure, via 'qtpy', the option to draw a circle when holding the SHIFT key. + + Note: matplotlib EllipeseSelector already has this functionality via the 'state_modifier_keys' argument, but it doesn't work if the canvas is used inside napari. + """ + def _onmove(self, event): + modifiers = QGuiApplication.keyboardModifiers() + if modifiers == Qt.ShiftModifier: + self.add_state('square') + else: + if 'square' in self._state: + self._state.remove('square') + super()._onmove(event) + + class BaseEllipseSelector(Selector): """Base class for creating an ellipse selector. @@ -204,7 +240,7 @@ def create_selector(self): Interactive is set to True to allow for interaction. Drag from anywhere is set to True to allow for drawing from any point. """ - self._selector = EllipseSelector(self.ax, self.on_select, useblit=True, button=[ + self._selector = MplEllipseSelector(self.ax, self.on_select, useblit=True, button=[ 1], minspanx=5, minspany=5, spancoords='pixels', interactive=True, drag_from_anywhere=True, props=dict(facecolor='#00c18c', edgecolor='#00c18c', alpha=0.3, fill=True, linewidth=2.5, linestyle='--'))