diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e7edb0e7448f..af41bdbd8b14 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2903,6 +2903,7 @@ class NavigationToolbar2: ('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous view', 'back', 'back'), ('Forward', 'Forward to next view', 'forward', 'forward'), + ('Views', 'Snapping to views', 'matplotlib', 'view_snap'), (None, None, None, None), ('Pan', 'Left button pans, Right button zooms\n' @@ -3366,6 +3367,9 @@ def save_figure(self, *args): """ raise NotImplementedError + def view_snap(self): + pass + def update(self): """Reset the Axes stack.""" self._nav_stack.clear() diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 42782b2f00e1..30d8ff6b38de 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -947,6 +947,23 @@ def save_figure(self, *args): except Exception as e: tkinter.messagebox.showerror("Error saving file", str(e)) + def view_snap(self, *args): + btn = self._buttons['Views'] + self._view_menu = tk.Menu(self, tearoff=0) + def set_view(elev, azim): + ax = self.canvas.figure.gca() + if hasattr(ax, 'view_init'): + ax.view_init(elev=elev, azim=azim) + self.canvas.draw_idle() + self._view_menu.add_command(label="XY Plane", command=lambda: set_view(90, -90)) + self._view_menu.add_command(label="XZ Plane", command=lambda: set_view(0, -90)) + self._view_menu.add_command(label="YZ Plane", command=lambda: set_view(0, 0)) + self._view_menu.update_idletasks() + x = btn.winfo_rootx() + y = btn.winfo_rooty() - self._view_menu.winfo_reqheight() + self._view_menu.tk_popup(x, y) + self._view_menu.grab_release() + def set_history_buttons(self): state_map = {True: tk.NORMAL, False: tk.DISABLED} can_back = self._nav_stack._pos > 0 diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 20a1a3c8f0a9..0a4f78c88a9c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -400,6 +400,22 @@ def on_notify_filter(*args): dialog.run() dialog.destroy() + def view_snap(self, *args): + widget = args[0] + self._view_menu = Gtk.Menu() + def draw_lambda(elev, azim): + ax = self.canvas.figure.gca() + ax.view_init(elev=elev, azim=azim) + self.canvas.draw() + views = [("XY Plane", 90, -90), ("XZ Plane", 0, -90), ("YZ Plane", 0, 0)] + for name, el, az in views: + item = Gtk.MenuItem(label=name) + item.connect("activate", lambda x, e=el, a=az: draw_lambda(e, a)) + self._view_menu.append(item) + self._view_menu.show_all() + self._view_menu.popup_at_widget(widget, Gdk.Gravity.NORTH, + Gdk.Gravity.SOUTH, None) + class ToolbarGTK3(ToolContainerBase, Gtk.Box): _icon_extension = '-symbolic.svg' diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 95b116e9a6ba..77552988693b 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -435,6 +435,27 @@ def on_response(dialog, response): dialog.show() return self.UNKNOWN_SAVED_STATUS + def view_snap(self, *args): + widget = args[0] + self._view_menu = Gtk.Popover() + menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + self._view_menu.set_has_arrow(False) + self._view_menu.set_position(Gtk.PositionType.TOP) + self._view_menu.set_child(menu_box) + def draw_lambda(elev, azim): + ax = self.canvas.figure.gca() + ax.view_init(elev=elev, azim=azim) + self.canvas.draw() + self._view_menu.popdown() + views = [("XY Plane", 90, -90), ("XZ Plane", 0, -90), ("YZ Plane", 0, 0)] + for name, el, az in views: + item = Gtk.Button(label=name) + item.set_has_frame(False) + item.connect("clicked", lambda x, e=el, a=az: draw_lambda(e, a)) + menu_box.append(item) + self._view_menu.set_parent(widget) + self._view_menu.popup() + class ToolbarGTK4(ToolContainerBase, Gtk.Box): _icon_extension = '-symbolic.svg' diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 0b0240c90310..6c6eee8db523 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -968,6 +968,20 @@ def save_figure(self, *args): QtWidgets.QMessageBox.StandardButton.NoButton) return fname + def view_snap(self, *args): + action = self._actions.get('view_snap') + btn = self.widgetForAction(action) + self._view_menu = QtWidgets.QMenu(self) + def draw_lambda(elev, azim): + ax = self.canvas.figure.gca() + ax.view_init(elev=elev, azim=azim) + self.canvas.draw() + self._view_menu.addAction("XY Plane", lambda: draw_lambda(90, -90)) + self._view_menu.addAction("XZ Plane", lambda: draw_lambda(0, -90)) + self._view_menu.addAction("YZ Plane", lambda: draw_lambda(0, 0)) + point = btn.mapToGlobal(QtCore.QPoint(0, btn.height())) + self._view_menu.exec(point) + def set_history_buttons(self): can_backward = self._nav_stack._pos > 0 can_forward = self._nav_stack._pos < len(self._nav_stack) - 1 diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 32da8dfde7aa..8a1fb60eced0 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1546,11 +1546,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 +1558,11 @@ def _on_move(self, event): w = self._pseudo_w h = self._pseudo_h + 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