I have a plot to which I add images that can be dragged by adding event handlers for button press, release and motion events
My issue is that if Zoom or Pan are selected in the toolbar, then the Zoom/Pan callbacks and the drag callbacks are executed when attempting to drag the image . The result is a confusing interaction for the end user.
I have considered two approaches to solving this. First, attempting to disable the Zoom/Pan function in the NavBar when the draggable artist is selected. Second, preventing the event mouse events from propagating to the NavBar by adding a processed attribute or similar.
I have not found an effective way of temporarily disabling the Navbar although I have tried various combinations of grabbing the widgetlock and changing the NavBar mode, none of my attempts have been successful.
The issue with preventing propagation is that the Navbar callbacks are called first because they were added with mpl_connect first.
Has anyone got a working solution to this issue or a recommended path forward?
It’s a little bit tricky where you have to put the locks and releases but I think the following should work (I’ve marked added lines with # NEW):
in _on_click
if event.button == 1 and event.inaxes in [self._axes]:
if not self._figure.canvas.widgetlock.available(self): # NEW
return # NEW
point = self._find_neighbor_point(event)
if point:
self._dragging_point = point
self._figure.canvas.widgetlock(self) # NEW
add the same return to _on_motion
if not self._figure.canvas.widgetlock.available(self): # NEW
return # NEW
and finally make sure to release the lock when you are done with it by modifying _on_release as follows:
if event.button == 1 and event.inaxes in [self._axes] and self._dragging_point:
self._dragging_point = None
self._update_plot()
self._figure.canvas.widgetlock.release(self) #NEW
with these changes i can use both the zoom and pan buttons as expected without influencing the points, and when they are not activated I can click and drag poitns as expected.
If you want zooming indepdent of clicking (by scrolling) then you might consider using mpl-pan-zoom — mpl-pan-zoom
Thank you @ianhi. I have made the changes you propose but it does not work for me. Perhaps another change that you made that is missing?
In the meantime I have worked out a different approach, which I will post below. If I can get your working it is probably cleaner, but at least I have a working solution for now.
As referenced above, I have found a working solution by subclassing the NavBar as follows:
class NavigationToolbar(NavigationToolbar2Tk):
def enable(self):
self.enabled = True
def disable(self):
self.enabled = False
#Only pass on events when enabled and delay requests to Navbar so that the graphics overlays can handle them first
def _zoom_pan_handler(self, event):
event.requeued = False if not hasattr(event, 'requeued') else True
if not hasattr(self, 'enabled'):
self.enabled = True
if not self.enabled:
return
if event.name == 'button_press_event' and not event.requeued:
event.requeued = True
self.after(100, self._zoom_pan_handler, event)
else:
super()._zoom_pan_handler(event)
This works by taking click events received by the navbar and requeueing the using tk.after. This gives over artists the chance to handle the event first. In turn, those artists can enable or disable the navbar processing in their own event handling.
I actually think a better solution all around would be if mpl_connect accepted a priority parameter and Events had a propagate attribute. Callbacks would then be called in priority order. and any handler could set propagate to False on an event to prevent additional handlers from being called.