From 4a3d27f78de88e01332a859bb58f337cab56790e Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Thu, 18 Dec 2025 21:20:00 +0530 Subject: [PATCH 01/11] Prototype for qt --- lib/matplotlib/backends/backend_qt.py | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 0b0240c90310..280feb0ff57e 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -328,10 +328,31 @@ def leaveEvent(self, event): def mousePressEvent(self, event): button = self.buttond.get(event.button()) if button is not None and self.figure is not None: - MouseEvent("button_press_event", self, - *self.mouseEventCoords(event), button, - modifiers=self._mpl_modifiers(), - guiEvent=event)._process() + mpl_event = MouseEvent("button_press_event", self, + *self.mouseEventCoords(event), button, + modifiers=self._mpl_modifiers(), + guiEvent=event) + if button == MouseButton.RIGHT: + for ax in self.figure.get_axes(): + if ax.contains(mpl_event)[0] and ax.name == '3d': + self.showContextMenu(event, ax) + return + + mpl_event._process() + + def showContextMenu(self, event, ax): + menu = QtWidgets.QMenu(self) + menu.setTitle("3D View Options") + + def set_view(elev, azim): + ax.view_init(elev=elev, azim=azim, roll=0) + self.draw_idle() + + menu.addAction("Top View (XY)").triggered.connect(lambda: set_view(90, -90)) + menu.addAction("Side View (YZ)").triggered.connect(lambda: set_view(0, 0)) + menu.addAction("Front View (XZ)").triggered.connect(lambda: set_view(0, -90)) + + menu.exec(QtGui.QCursor.pos()) def mouseDoubleClickEvent(self, event): button = self.buttond.get(event.button()) From 2086384287bfbeb6566a95dd815f796cecef394e Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Tue, 30 Dec 2025 11:25:29 +0530 Subject: [PATCH 02/11] check for mouse movement --- lib/matplotlib/backends/backend_qt.py | 37 +++++++++------------------ lib/mpl_toolkits/mplot3d/axes3d.py | 18 +++++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 280feb0ff57e..5add60a30b47 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -328,31 +328,10 @@ def leaveEvent(self, event): def mousePressEvent(self, event): button = self.buttond.get(event.button()) if button is not None and self.figure is not None: - mpl_event = MouseEvent("button_press_event", self, - *self.mouseEventCoords(event), button, - modifiers=self._mpl_modifiers(), - guiEvent=event) - if button == MouseButton.RIGHT: - for ax in self.figure.get_axes(): - if ax.contains(mpl_event)[0] and ax.name == '3d': - self.showContextMenu(event, ax) - return - - mpl_event._process() - - def showContextMenu(self, event, ax): - menu = QtWidgets.QMenu(self) - menu.setTitle("3D View Options") - - def set_view(elev, azim): - ax.view_init(elev=elev, azim=azim, roll=0) - self.draw_idle() - - menu.addAction("Top View (XY)").triggered.connect(lambda: set_view(90, -90)) - menu.addAction("Side View (YZ)").triggered.connect(lambda: set_view(0, 0)) - menu.addAction("Front View (XZ)").triggered.connect(lambda: set_view(0, -90)) - - menu.exec(QtGui.QCursor.pos()) + MouseEvent("button_press_event", self, + *self.mouseEventCoords(event), button, + modifiers=self._mpl_modifiers(), + guiEvent=event)._process() def mouseDoubleClickEvent(self, event): button = self.buttond.get(event.button()) @@ -648,6 +627,14 @@ def full_screen_toggle(self): else: self.window.showFullScreen() + def context_menu(self, event, labels=None, actions=None): + if labels is None or actions is None: + return + menu = QtWidgets.QMenu(self.window) + for label, action in zip(labels, actions): + menu.addAction(label).triggered.connect(action) + menu.exec(QtGui.QCursor.pos()) + def _widgetclosed(self): CloseEvent("close_event", self.canvas)._process() if self.window._destroying: diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 32da8dfde7aa..36007b4b9e0c 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -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._right_click_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._right_click_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,19 @@ 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._right_click_moved: + canvas = self.get_figure(root=True).canvas + canvas.manager.context_menu( + event, + labels=["XY", "YZ", "XZ"], + actions=[functools.partial(self.view_init, elev=90, azim=-90), + functools.partial(self.view_init, elev=0, azim=0), + functools.partial(self.view_init, elev=0, azim=-90)], + ) + canvas.draw_idle() + 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 @@ -1621,6 +1638,7 @@ def _on_move(self, event): # Zoom elif self.button_pressed in self._zoom_btn: # zoom view (dragging down zooms in) + self._right_click_moved = True scale = h/(h - dy) self._scale_axis_limits(scale, scale, scale) From 2fea63e66eec690b318ac49851b62e81483fd066 Mon Sep 17 00:00:00 2001 From: Anton <138380708+r3kste@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:21:47 +0530 Subject: [PATCH 03/11] Add context menu in FigureManagerTk --- lib/matplotlib/backends/_backend_tk.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 42782b2f00e1..a9a2b3a5d7a0 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -646,6 +646,14 @@ def full_screen_toggle(self): is_fullscreen = bool(self.window.attributes('-fullscreen')) self.window.attributes('-fullscreen', not is_fullscreen) + def context_menu(self, event, labels=None, actions=None): + if labels is None or actions is None: + return + menu = tk.Menu(self.window, tearoff=0) + for label, action in zip(labels, actions): + menu.add_command(label=label, command=action) + menu.tk_popup(event.guiEvent.x_root, event.guiEvent.y_root) + class NavigationToolbar2Tk(NavigationToolbar2, tk.Frame): def __init__(self, canvas, window=None, *, pack_toolbar=True): From 90e40a1642117d95fffdc33a85cd3645efae2587 Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Wed, 31 Dec 2025 11:34:05 +0530 Subject: [PATCH 04/11] gtk3&4 --- lib/matplotlib/backends/backend_gtk3.py | 14 +++++++++++++ lib/matplotlib/backends/backend_gtk4.py | 28 +++++++++++++++++++++++++ lib/mpl_toolkits/mplot3d/axes3d.py | 8 +++---- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 20a1a3c8f0a9..2fea6f2ca7a7 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -591,6 +591,20 @@ class FigureManagerGTK3(_FigureManagerGTK): _toolbar2_class = NavigationToolbar2GTK3 _toolmanager_toolbar_class = ToolbarGTK3 + def context_menu(self, event, labels=None, actions=None): + if labels is None or actions is None: + return + menu = Gtk.Menu() + for label, action in zip(labels, actions): + item = Gtk.MenuItem(label=label) + menu.append(item) + def draw_lambda(_w, a=action): + a() + self.canvas.draw() + item.connect('activate', draw_lambda) + item.show() + menu.popup_at_pointer(event.guiEvent) + @_BackendGTK.export class _BackendGTK3(_BackendGTK): diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 95b116e9a6ba..ae154634df02 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -637,6 +637,34 @@ class FigureManagerGTK4(_FigureManagerGTK): _toolbar2_class = NavigationToolbar2GTK4 _toolmanager_toolbar_class = ToolbarGTK4 + def context_menu(self, event, labels=None, actions=None): + if labels is None or actions is None: + return + popover = Gtk.Popover() + popover.set_parent(self.window) + popover.set_has_arrow(False) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + popover.set_child(box) + for label, action in zip(labels, actions): + btn = Gtk.Button(label=label) + def draw_lambda(_btn, a=action, p=popover): + a() + self.canvas.draw() + p.popdown() + btn.connect('clicked', draw_lambda) + box.append(btn) + scale = self.canvas.get_scale_factor() + x_canvas = (event.x / scale) + y_canvas = ((self.canvas.get_height() * scale - event.y) / scale) + x_win,y_win = self.canvas.translate_coordinates(self.window, x_canvas, y_canvas) + rect = Gdk.Rectangle() + rect.x = x_win + rect.y = y_win + rect.width = 1 + rect.height = 1 + popover.set_pointing_to(rect) + popover.popup() + @_BackendGTK.export class _BackendGTK4(_BackendGTK): diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 36007b4b9e0c..489ad4392dc6 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -188,7 +188,7 @@ 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._right_click_moved = False + self._mouse_moved = False # mplot3d currently manages its own spines and needs these turned off # for bounding box calculations @@ -1360,7 +1360,7 @@ def clear(self): def _button_press(self, event): if event.inaxes == self: - self._right_click_moved = False + 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 @@ -1373,7 +1373,7 @@ def _button_release(self, event): self.button_pressed = None if event.button in self._zoom_btn and event.inaxes == self \ - and not self._right_click_moved: + and not self._mouse_moved: canvas = self.get_figure(root=True).canvas canvas.manager.context_menu( event, @@ -1638,7 +1638,7 @@ def _on_move(self, event): # Zoom elif self.button_pressed in self._zoom_btn: # zoom view (dragging down zooms in) - self._right_click_moved = True + self._mouse_moved = True scale = h/(h - dy) self._scale_axis_limits(scale, scale, scale) From bb18c3223899146bb1325b0530286d97211175e8 Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Thu, 1 Jan 2026 11:38:36 +0530 Subject: [PATCH 05/11] macosx&wx backends and edit in axes3d.py due to gtk4 --- lib/matplotlib/backends/backend_macosx.py | 29 +++++++++++++++++++++++ lib/matplotlib/backends/backend_wx.py | 10 ++++++++ lib/mpl_toolkits/mplot3d/axes3d.py | 3 ++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 6ea437a90ca1..c7cb206d2089 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -8,6 +8,9 @@ from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, ResizeEvent, TimerBase, _allow_interrupt) +from Foundation import NSObject +import AppKit +import objc class TimerMac(_macosx.Timer, TimerBase): @@ -161,6 +164,22 @@ def __init__(self, canvas, num): self.show() self.canvas.draw_idle() + def context_menu(self, event, labels=None, actions=None): + if labels is None or actions is None: + return + menu = AppKit.NSMenu.alloc().init() + self._menu_callbacks = [] + for label, action in zip(labels, actions): + target = MenuCallback.alloc().initWithCallback_(action) + self._menu_callbacks.append(target) + item = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( + label, "action:", "" + ) + item.setTarget_(target) + menu.addItem_(item) + mouse_loc = AppKit.NSEvent.mouseLocation() + menu.popUpMenuPositioningItem_atLocation_inView_(None, mouse_loc, None) + def _close_button_pressed(self): Gcf.destroy(self) self.canvas.flush_events() @@ -189,6 +208,16 @@ def show(self): self._raise() +class MenuCallback(NSObject): + def initWithCallback_(self, callback): + self = objc.super(MenuCallback, self).init() + self.callback = callback + return self + def action_(self, sender): + if hasattr(self, 'callback') and self.callback: + self.callback() + + @_Backend.export class _BackendMac(_Backend): FigureCanvas = FigureCanvasMac diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 0acb4499ed87..5990c69be146 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1018,6 +1018,16 @@ def full_screen_toggle(self): # docstring inherited self.frame.ShowFullScreen(not self.frame.IsFullScreen()) + def context_menu(self, event, labels=None, actions=None): + if labels is None or actions is None: + return + menu = wx.Menu() + for label, action in zip(labels, actions): + item = menu.Append(wx.NewIdRef(), label) + menu.Bind(wx.EVT_MENU, lambda _, a=action: a(), item) + self.canvas.PopupMenu(menu) + menu.Destroy() + def get_window_title(self): # docstring inherited return self.window.GetTitle() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 489ad4392dc6..210adb2c9691 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1638,7 +1638,8 @@ def _on_move(self, event): # Zoom elif self.button_pressed in self._zoom_btn: # zoom view (dragging down zooms in) - self._mouse_moved = True + if (dx**2 + dy**2) > 5: + self._mouse_moved = True scale = h/(h - dy) self._scale_axis_limits(scale, scale, scale) From 87fdb0a65d2516dbd32da49b0b1216bb862607d9 Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Fri, 2 Jan 2026 17:38:27 +0530 Subject: [PATCH 06/11] simplified Gtk4 and minor change to position of setting _mouse_moved to true --- lib/matplotlib/backends/backend_gtk4.py | 14 ++++++-------- lib/mpl_toolkits/mplot3d/axes3d.py | 5 +++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index ae154634df02..50506d4d315d 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -638,12 +638,12 @@ class FigureManagerGTK4(_FigureManagerGTK): _toolmanager_toolbar_class = ToolbarGTK4 def context_menu(self, event, labels=None, actions=None): - if labels is None or actions is None: + if not labels or not actions: return popover = Gtk.Popover() - popover.set_parent(self.window) + popover.set_parent(self.canvas) popover.set_has_arrow(False) - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) popover.set_child(box) for label, action in zip(labels, actions): btn = Gtk.Button(label=label) @@ -654,12 +654,10 @@ def draw_lambda(_btn, a=action, p=popover): btn.connect('clicked', draw_lambda) box.append(btn) scale = self.canvas.get_scale_factor() - x_canvas = (event.x / scale) - y_canvas = ((self.canvas.get_height() * scale - event.y) / scale) - x_win,y_win = self.canvas.translate_coordinates(self.window, x_canvas, y_canvas) + height = self.canvas.get_height() rect = Gdk.Rectangle() - rect.x = x_win - rect.y = y_win + rect.x = int(event.x / scale) + rect.y = int(height - (event.y / scale)) rect.width = 1 rect.height = 1 popover.set_pointing_to(rect) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 210adb2c9691..36c01ce60ba6 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1580,6 +1580,9 @@ def _on_move(self, event): w = self._pseudo_w h = self._pseudo_h + if (dx**2 + dy**2) > 5: + self._mouse_moved = True + # Rotation if self.button_pressed in self._rotate_btn: # rotate viewing point @@ -1638,8 +1641,6 @@ def _on_move(self, event): # Zoom elif self.button_pressed in self._zoom_btn: # zoom view (dragging down zooms in) - if (dx**2 + dy**2) > 5: - self._mouse_moved = True scale = h/(h - dy) self._scale_axis_limits(scale, scale, scale) From 84f7111d46ccc185bc4c7b207a84294fb2e0c6ac Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Mon, 5 Jan 2026 09:24:07 +0530 Subject: [PATCH 07/11] minor fix --- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 36c01ce60ba6..4e5991933e23 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1580,7 +1580,7 @@ def _on_move(self, event): w = self._pseudo_w h = self._pseudo_h - if (dx**2 + dy**2) > 5: + if (dx**2 + dy**2) > 1e-5: self._mouse_moved = True # Rotation From 0a8d87a50123fb1da2215a78ee47bcaf464a167a Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Mon, 5 Jan 2026 10:11:41 +0530 Subject: [PATCH 08/11] simplified context_menu in backend_macosx.py --- lib/matplotlib/backends/backend_macosx.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index c7cb206d2089..5d1609aaf48a 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -10,7 +10,6 @@ ResizeEvent, TimerBase, _allow_interrupt) from Foundation import NSObject import AppKit -import objc class TimerMac(_macosx.Timer, TimerBase): @@ -148,6 +147,12 @@ def save_figure(self, *args): return filename +class MenuCallback(NSObject): + def action_(self, sender): + if hasattr(self, 'callback'): + self.callback() + + class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): _toolbar2_class = NavigationToolbar2Mac @@ -170,7 +175,8 @@ def context_menu(self, event, labels=None, actions=None): menu = AppKit.NSMenu.alloc().init() self._menu_callbacks = [] for label, action in zip(labels, actions): - target = MenuCallback.alloc().initWithCallback_(action) + target = MenuCallback.alloc().init() + target.callback = action self._menu_callbacks.append(target) item = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( label, "action:", "" @@ -208,16 +214,6 @@ def show(self): self._raise() -class MenuCallback(NSObject): - def initWithCallback_(self, callback): - self = objc.super(MenuCallback, self).init() - self.callback = callback - return self - def action_(self, sender): - if hasattr(self, 'callback') and self.callback: - self.callback() - - @_Backend.export class _BackendMac(_Backend): FigureCanvas = FigureCanvasMac From 4a60e5921b93eb4986c1b8dcfad6ec5b88fd0f4a Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Mon, 5 Jan 2026 11:24:54 +0530 Subject: [PATCH 09/11] changed gtk4 implementation --- lib/matplotlib/backends/backend_gtk3.py | 5 +---- lib/matplotlib/backends/backend_gtk4.py | 22 +++++++++++----------- lib/mpl_toolkits/mplot3d/axes3d.py | 11 ++++++++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 2fea6f2ca7a7..4270fec91b92 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -598,10 +598,7 @@ def context_menu(self, event, labels=None, actions=None): for label, action in zip(labels, actions): item = Gtk.MenuItem(label=label) menu.append(item) - def draw_lambda(_w, a=action): - a() - self.canvas.draw() - item.connect('activate', draw_lambda) + item.connect('activate', lambda w, a=action: a()) item.show() menu.popup_at_pointer(event.guiEvent) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 50506d4d315d..dadf92478b91 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -640,19 +640,19 @@ class FigureManagerGTK4(_FigureManagerGTK): def context_menu(self, event, labels=None, actions=None): if not labels or not actions: return - popover = Gtk.Popover() + menu = Gio.Menu() + action_group = Gio.SimpleActionGroup() + for i, (label, action) in enumerate(zip(labels, actions)): + action_name = f"ctx_{i}" + g_action = Gio.SimpleAction.new(action_name, None) + g_action.connect("activate", lambda a, b, act=action: act()) + 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) - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - popover.set_child(box) - for label, action in zip(labels, actions): - btn = Gtk.Button(label=label) - def draw_lambda(_btn, a=action, p=popover): - a() - self.canvas.draw() - p.popdown() - btn.connect('clicked', draw_lambda) - box.append(btn) + popover.set_halign(Gtk.Align.START) scale = self.canvas.get_scale_factor() height = self.canvas.get_height() rect = Gdk.Rectangle() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 4e5991933e23..4abb8671e110 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1375,12 +1375,17 @@ def _button_release(self, event): 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( event, labels=["XY", "YZ", "XZ"], - actions=[functools.partial(self.view_init, elev=90, azim=-90), - functools.partial(self.view_init, elev=0, azim=0), - functools.partial(self.view_init, elev=0, azim=-90)], + 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)], ) canvas.draw_idle() From 45b30efef861aa62973fb826bd60c00c2f3addf5 Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Tue, 6 Jan 2026 08:59:29 +0530 Subject: [PATCH 10/11] minor changes --- lib/matplotlib/backends/backend_gtk3.py | 2 +- lib/matplotlib/backends/backend_gtk4.py | 4 ++-- lib/matplotlib/backends/backend_qt.py | 2 +- lib/matplotlib/backends/backend_wx.py | 2 +- lib/mpl_toolkits/mplot3d/axes3d.py | 1 - 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 4270fec91b92..156234f888c6 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -598,7 +598,7 @@ def context_menu(self, event, labels=None, actions=None): for label, action in zip(labels, actions): item = Gtk.MenuItem(label=label) menu.append(item) - item.connect('activate', lambda w, a=action: a()) + item.connect('activate', lambda _, a=action: a()) item.show() menu.popup_at_pointer(event.guiEvent) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index dadf92478b91..13c2cfdfb71b 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -643,9 +643,9 @@ def context_menu(self, event, labels=None, actions=None): menu = Gio.Menu() action_group = Gio.SimpleActionGroup() for i, (label, action) in enumerate(zip(labels, actions)): - action_name = f"ctx_{i}" + action_name = f"{label}" g_action = Gio.SimpleAction.new(action_name, None) - g_action.connect("activate", lambda a, b, act=action: act()) + 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) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 5add60a30b47..d8738fc5ad15 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -633,7 +633,7 @@ def context_menu(self, event, labels=None, actions=None): menu = QtWidgets.QMenu(self.window) for label, action in zip(labels, actions): menu.addAction(label).triggered.connect(action) - menu.exec(QtGui.QCursor.pos()) + menu.exec(event.guiEvent.globalPosition().toPoint()) def _widgetclosed(self): CloseEvent("close_event", self.canvas)._process() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 5990c69be146..631e82090539 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1025,7 +1025,7 @@ def context_menu(self, event, labels=None, actions=None): for label, action in zip(labels, actions): item = menu.Append(wx.NewIdRef(), label) menu.Bind(wx.EVT_MENU, lambda _, a=action: a(), item) - self.canvas.PopupMenu(menu) + self.canvas.PopupMenu(menu, event.guiEvent.GetPosition()) menu.Destroy() def get_window_title(self): diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 4abb8671e110..7add5f0e01fb 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1387,7 +1387,6 @@ def draw_lambda(elev, azim): functools.partial(draw_lambda, elev=0, azim=0), functools.partial(draw_lambda, elev=0, azim=-90)], ) - canvas.draw_idle() toolbar = self.get_figure(root=True).canvas.toolbar # backend_bases.release_zoom and backend_bases.release_pan call From 5ed834ca2ea61bb003d1483330039b0078f65030 Mon Sep 17 00:00:00 2001 From: AdwaithBatchu Date: Wed, 7 Jan 2026 09:10:12 +0530 Subject: [PATCH 11/11] changed threshold value --- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 7add5f0e01fb..fb9f0113ed08 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1584,7 +1584,7 @@ def _on_move(self, event): w = self._pseudo_w h = self._pseudo_h - if (dx**2 + dy**2) > 1e-5: + if (dx**2 + dy**2) > 1e-6: self._mouse_moved = True # Rotation