Repository navigation
Context Menu for snapping view to primary axis planes in 3D plots #30976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -637,6 +637,34 @@ class FigureManagerGTK4(_FigureManagerGTK): | |
| _toolbar2_class = NavigationToolbar2GTK4 | ||
| _toolmanager_toolbar_class = ToolbarGTK4 | ||
|
|
||
| def context_menu(self, event, labels=None, actions=None): | ||
| if not labels or not actions: | ||
| return | ||
| menu = Gio.Menu() | ||
| action_group = Gio.SimpleActionGroup() | ||
| for label, action in zip(labels, actions): | ||
| action_name = label.replace(" ", "_") | ||
| g_action = Gio.SimpleAction.new(action_name, None) | ||
| g_action.connect("activate", lambda *_, a=action: a()) | ||
| action_group.add_action(g_action) | ||
| menu.append(label, f"win.{action_name}") | ||
|
|
||
| self.canvas.insert_action_group("win", action_group) | ||
| popover = Gtk.PopoverMenu.new_from_model(menu) | ||
| popover.set_parent(self.canvas) | ||
| popover.set_has_arrow(False) | ||
| popover.set_halign(Gtk.Align.START) | ||
|
|
||
| scale = self.canvas.get_scale_factor() | ||
| height = self.canvas.get_height() | ||
| rect = Gdk.Rectangle() | ||
| rect.x = int(event.x / scale) | ||
| rect.y = int(height - (event.y / scale)) | ||
| rect.width = 1 | ||
| rect.height = 1 | ||
| popover.set_pointing_to(rect) | ||
| popover.popup() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My GTK is rusty, but I believe this should be marked for cleanup when closed: ...
popover.connect('closed', lambda p: p.unparent())
popover.popup() |
||
|
|
||
|
|
||
| @_BackendGTK.export | ||
| class _BackendGTK4(_BackendGTK): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| import textwrap | ||
| import warnings | ||
|
|
||
| import functools | ||
| import numpy as np | ||
|
|
||
| import matplotlib as mpl | ||
|
|
@@ -187,6 +188,8 @@ def __init__( | |
| pseudo_bbox = self.transLimits.inverted().transform([(0, 0), (1, 1)]) | ||
| self._pseudo_w, self._pseudo_h = pseudo_bbox[1] - pseudo_bbox[0] | ||
|
|
||
| self._mouse_moved = False | ||
|
|
||
| # mplot3d currently manages its own spines and needs these turned off | ||
| # for bounding box calculations | ||
| self.spines[:].set_visible(False) | ||
|
|
@@ -1357,6 +1360,7 @@ def clear(self): | |
|
|
||
| def _button_press(self, event): | ||
| if event.inaxes == self: | ||
| self._mouse_moved = False | ||
| self.button_pressed = event.button | ||
| self._sx, self._sy = event.xdata, event.ydata | ||
| toolbar = self.get_figure(root=True).canvas.toolbar | ||
|
|
@@ -1367,6 +1371,23 @@ def _button_press(self, event): | |
|
|
||
| def _button_release(self, event): | ||
| self.button_pressed = None | ||
|
|
||
| if event.button in self._zoom_btn and event.inaxes == self \ | ||
| and not self._mouse_moved: | ||
| canvas = self.get_figure(root=True).canvas | ||
|
|
||
| def draw_lambda(elev, azim): | ||
| self.view_init(elev=elev, azim=azim) | ||
| canvas.draw_idle() | ||
|
|
||
| canvas.manager.context_menu( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should check for the existence of the context_menu for the backend to avoid errors: if hasattr(canvas.manager, 'context_menu'):
canvas.manager.context_menu(event, labels=..., actions=...) |
||
| event, | ||
| labels=["Go to X-Y view", "Go to Y-Z view", "Go to X-Z view"], | ||
| actions=[functools.partial(draw_lambda, elev=90, azim=-90), | ||
| functools.partial(draw_lambda, elev=0, azim=0), | ||
| functools.partial(draw_lambda, elev=0, azim=-90)], | ||
| ) | ||
|
|
||
| toolbar = self.get_figure(root=True).canvas.toolbar | ||
| # backend_bases.release_zoom and backend_bases.release_pan call | ||
| # push_current, so check the navigation mode so we don't call it twice | ||
|
|
@@ -1546,11 +1567,6 @@ def _on_move(self, event): | |
| if not self.button_pressed: | ||
| return | ||
|
|
||
| if self.get_navigate_mode() is not None: | ||
| # we don't want to rotate if we are zooming/panning | ||
| # from the toolbar | ||
| return | ||
|
|
||
| if self.M is None: | ||
| return | ||
|
|
||
|
|
@@ -1563,6 +1579,14 @@ def _on_move(self, event): | |
| w = self._pseudo_w | ||
| h = self._pseudo_h | ||
|
|
||
| if (dx**2 + dy**2) > 1e-6: | ||
| self._mouse_moved = True | ||
|
|
||
| if self.get_navigate_mode() is not None: | ||
| # we don't want to rotate if we are zooming/panning | ||
| # from the toolbar | ||
| return | ||
|
|
||
| # Rotation | ||
| if self.button_pressed in self._rotate_btn: | ||
| # rotate viewing point | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly for gtk3: