Skip to content

Conversation

@AdwaithBatchu
Copy link

@AdwaithBatchu AdwaithBatchu commented Jan 16, 2026

PR summary

closes #23544
This PR introduces a feature that adds context menu on 3d Axes triggered by right-click of the mouse.

  1. Added context_menu() in Figure Manager that takes arguments, a list of labels and a list of corresponding functions to execute upon selection.

  2. Modified _button_release() to call canvas.manager.context_menu() with functions for setting orthographic views when the right-click is released on the mouse without moving it significantly. Mouse movement is handled in _on_move() using a small threshold because previously I observed trackpad (on macOS) reported micro-movements during a static click, which falsely flagged the action as "drag" and blocked the menu in specific backends.

Backends

  • TkAgg: tk.Menu implementation.
  • QtAgg: QtWidgets.QMenu implementation.
  • WxAgg: Uses wx.Menu and PopupMenu.
  • Gtk3Agg: Uses Gtk.Menu.
  • Gtk4Agg: Uses Gtk.PopoverMenu and Gio.Menu.
  • MacOSX: Yet to be implemented
  • WebAgg: Yet to be implemented
  • NbAgg: Yet to be implemented
import matplotlib

# matplotlib.use("TkAgg") # Change to QtAgg, GTK4Agg, MacOSX, WxAgg, etc.
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.plot([0, 1, 2], [0, 1, 0], [0, 1, 0])

plt.show()

PR checklist

@AdwaithBatchu AdwaithBatchu changed the title prototype for right click context menu Context Menu for snapping view to primary axis planes in 3D plots Jan 16, 2026
Copy link
Contributor

@scottshambaugh scottshambaugh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot more cleanly implemented than I thought it would require!

I can see context menus being useful in quite a few use cases, we should talk on the dev team about designing these with some thought to it. It may also be worth implementing a base class for this, but I think this is fine for now as a starting point.

@timhoffm in the original issue mentioned compatibility with our event architecture, which I don't know enough to comment on.

I played around with this locally on a qtagg backend, and things work largely as expected:

  • The menu works and each option correctly snaps to the expected view
  • Long presses on a touchscreen work the same as a mouse right-click to bring up the menu
  • With multiple subplots, the action is only applied to the subplot that was clicked on
  • Right clicking in a 2D subplot has no effect
  • Rotating, panning, and zooming still all work as expected with either the mouse or toolbar buttons

One minor issue that would be nice to fix but isn't blocking, is that if you bring up the menu and then drag the figure window to a new position, the popup does not follow the window.

Image

For documentation, this behavior should get a doc/release/next_whats_new entry, as well as explanation with a screenshot in https://matplotlib.org/stable/users/explain/figure/interactive.html

We should also add tests for this. Ideally for selecting each option, but at the least for opening the menu on each backend.

self.view_init(elev=elev, azim=azim)
canvas.draw_idle()

canvas.manager.context_menu(
Copy link
Contributor

@scottshambaugh scottshambaugh Jan 16, 2026

Choose a reason for hiding this comment

The 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=...)

rect.width = 1
rect.height = 1
popover.set_pointing_to(rect)
popover.popup()
Copy link
Contributor

Choose a reason for hiding this comment

The 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()

menu.append(item)
item.connect('activate', lambda _, a=action: a())
item.show()
menu.popup_at_pointer(event.guiEvent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly for gtk3:

...
menu.connect('selection-done', lambda m: m.destroy())
menu.popup_at_pointer(event.guiEvent)

@anntzer
Copy link
Contributor

anntzer commented Jan 16, 2026

If matplotlib itself starts to use context menus, then the design needs to be careful to allow developers that embed matplotlib widgets into their own GUIs to add their own context actions (something that I do fairly regularly, and which I would have to redesign if it started to conflict with builtin menus). There's likely many ways to design the API, but based on my own usage it could e.g. look like

canvas.add_context_menu_entry("group", "name", callback, condition=lambda e: True)

where "name" is what gets displayed in the menu, "group" allows grouping entries (with menu separators? or with submenus?), callback is the callback (taking the button_press_event as parameter) and condition is a function taking the button_press_event as argument and returning whether this entry should be displayed (e.g., for the use case here it would allow displaying the menu entries only when over a 3D axes).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENH]: Add ability to snap view to primary axis planes in 3D plots

3 participants