Standardize creation of FigureManager from a given FigureCanvas class. by anntzer · Pull Request #18854 · matplotlib/matplotlib · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions lib/matplotlib/backend_bases.py
76 changes: 37 additions & 39 deletions lib/matplotlib/backends/_backend_tk.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,43 @@ def _update_device_pixel_ratio(self, event=None):
w, h = self.get_width_height(physical=True)
self._tkcanvas.configure(width=w, height=h)

@classmethod
def new_manager(cls, figure, num):
# docstring inherited
with _restore_foreground_window_at_end():
if cbook._get_running_interactive_framework() is None:
cbook._setup_new_guiapp()
_c_internal_utils.Win32_SetProcessDpiAwareness_max()
window = tk.Tk(className="matplotlib")
window.withdraw()

# Put a Matplotlib icon on the window rather than the default tk
# icon. See https://www.tcl.tk/man/tcl/TkCmd/wm.html#M50
#
# `ImageTk` can be replaced with `tk` whenever the minimum
# supported Tk version is increased to 8.6, as Tk 8.6+ natively
# supports PNG images.
icon_fname = str(cbook._get_data_path(
'images/matplotlib.png'))
icon_img = ImageTk.PhotoImage(file=icon_fname, master=window)

icon_fname_large = str(cbook._get_data_path(
'images/matplotlib_large.png'))
icon_img_large = ImageTk.PhotoImage(
file=icon_fname_large, master=window)
try:
window.iconphoto(False, icon_img_large, icon_img)
except Exception as exc:
# log the failure (due e.g. to Tk version), but carry on
_log.info('Could not load matplotlib icon: %s', exc)

canvas = cls(figure, master=window)
manager = FigureManagerTk(canvas, num, window)
if mpl.is_interactive():
manager.show()
canvas.draw_idle()
return manager

def resize(self, event):
width, height = event.width, event.height

Expand Down Expand Up @@ -958,45 +995,6 @@ def trigger(self, *args):
class _BackendTk(_Backend):
FigureManager = FigureManagerTk

@classmethod
def new_figure_manager_given_figure(cls, num, figure):
"""
Create a new figure manager instance for the given figure.
"""
with _restore_foreground_window_at_end():
if cbook._get_running_interactive_framework() is None:
cbook._setup_new_guiapp()
_c_internal_utils.Win32_SetProcessDpiAwareness_max()
window = tk.Tk(className="matplotlib")
window.withdraw()

# Put a Matplotlib icon on the window rather than the default tk
# icon. See https://www.tcl.tk/man/tcl/TkCmd/wm.html#M50
#
# `ImageTk` can be replaced with `tk` whenever the minimum
# supported Tk version is increased to 8.6, as Tk 8.6+ natively
# supports PNG images.
icon_fname = str(cbook._get_data_path(
'images/matplotlib.png'))
icon_img = ImageTk.PhotoImage(file=icon_fname, master=window)

icon_fname_large = str(cbook._get_data_path(
'images/matplotlib_large.png'))
icon_img_large = ImageTk.PhotoImage(
file=icon_fname_large, master=window)
try:
window.iconphoto(False, icon_img_large, icon_img)
except Exception as exc:
# log the failure (due e.g. to Tk version), but carry on
_log.info('Could not load matplotlib icon: %s', exc)

canvas = cls.FigureCanvas(figure, master=window)
manager = cls.FigureManager(canvas, num, window)
if mpl.is_interactive():
manager.show()
canvas.draw_idle()
return manager

@staticmethod
def mainloop():
managers = Gcf.get_all_fig_managers()
Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ def __init__(self, figure=None):
style_ctx.add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
style_ctx.add_class("matplotlib-canvas")

@classmethod
def new_manager(cls, figure, num):
# docstring inherited
return FigureManagerGTK3(cls(figure), num)

@timhoffm timhoffm Apr 19, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I find it a bit odd that we code the relation between the canvas and the matching manager into a class method on the canvas. Also the hierarchy feels unclear.

Two alternative suggestions:

  1. How about storing this information either in a canvas class attribute, or in global dict? That way, we could have a standalone function new_manager(canvas_class, figure, num), which feels a bit better than having a method on the canvas that creates a manager.

  2. Or one goes the alternative route and considers the manager to be an optional part of the canvas.

    canvas = [make the canvas instance]
    canvas.attach_manager()
    

    Possibly there is a better name than attach. But the point here is that you amend an existing canvas instance. The original new_manager proposal has the canvas class as entry point, but OTOH you get the manager, that holds a reference to the canvas. In my view this mixes the priority/hierarchy. In the first part, the canvas seems to be the leading object, whereas in the second the manager seems to take over.


def destroy(self):
self.close_event()

Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/backends/backend_gtk4.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ def __init__(self, figure=None):
style_ctx.add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
style_ctx.add_class("matplotlib-canvas")

@classmethod
def new_manager(cls, figure, num):
# docstring inherited
return FigureManagerGTK4(cls(figure), num)

def pick(self, mouseevent):
# GtkWidget defines pick in GTK4, so we need to override here to work
# with the base implementation we want.
Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/backends/backend_macosx.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def __init__(self, figure):
self._draw_pending = False
self._is_drawing = False

@classmethod
def new_manager(cls, figure, num):
# docstring inherited
return FigureManagerMac(cls(figure), num)

def set_cursor(self, cursor):
# docstring inherited
_macosx.set_cursor(cursor)
Expand Down
30 changes: 14 additions & 16 deletions lib/matplotlib/backends/backend_nbagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,20 @@ def remove_comm(self, comm_id):


class FigureCanvasNbAgg(FigureCanvasWebAggCore):
pass
@classmethod
def new_manager(cls, figure, num):
canvas = cls(figure)
manager = FigureManagerNbAgg(canvas, num)
if is_interactive():
manager.show()
figure.canvas.draw_idle()

def destroy(event):
canvas.mpl_disconnect(cid)
Gcf.destroy(manager)

cid = canvas.mpl_connect('close_event', destroy)
return manager


class CommSocket:
Expand Down Expand Up @@ -228,21 +241,6 @@ class _BackendNbAgg(_Backend):
FigureCanvas = FigureCanvasNbAgg
FigureManager = FigureManagerNbAgg

@staticmethod
def new_figure_manager_given_figure(num, figure):
canvas = FigureCanvasNbAgg(figure)
manager = FigureManagerNbAgg(canvas, num)
if is_interactive():
manager.show()
figure.canvas.draw_idle()

def destroy(event):
canvas.mpl_disconnect(cid)
Gcf.destroy(manager)

cid = canvas.mpl_connect('close_event', destroy)
return manager

@staticmethod
def show(block=None):
## TODO: something to do when keyword block==False ?
Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/backends/backend_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ def __init__(self, figure=None):
palette = QtGui.QPalette(QtGui.QColor("white"))
self.setPalette(palette)

@classmethod
def new_manager(cls, figure, num):
# docstring inherited
return FigureManagerQT(cls(figure), num)

def _update_pixel_ratio(self):
if self._set_device_pixel_ratio(_devicePixelRatioF(self)):
# The easiest way to resize the canvas is to emit a resizeEvent
Expand Down
5 changes: 4 additions & 1 deletion lib/matplotlib/backends/backend_webagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def run(self):


class FigureCanvasWebAgg(core.FigureCanvasWebAggCore):
pass
@classmethod
def new_manager(cls, figure, num):
# docstring inherited
return core.FigureManagerWebAgg(cls(figure), num)


class FigureManagerWebAgg(core.FigureManagerWebAgg):
Expand Down
38 changes: 20 additions & 18 deletions lib/matplotlib/backends/backend_wx.py