Skip to content

Commit 04368ed

Browse files
committed
Standardize creation of FigureManager from a given FigureCanvas class.
The `new_manager` classmethod may appear to belong more naturally to the FigureManager class rather than the FigureCanvas class, but putting it on FigureCanvas has multiple advantages: - One may want to create managers at times where all one has is a FigureCanvas instance, which may not even have a corresponding manager (e.g. `subplot_tool`). - A given FigureManager class can be associated with many different FigureCanvas classes (indeed, FigureManagerQT can manage both FigureCanvasQTAgg and FigureCanvasQTCairo and also the mplcairo Qt canvas classes), whereas we don't have multiple FigureManager classes for a given FigureCanvas class.
1 parent eda1404 commit 04368ed

9 files changed

+109
-81
lines changed

lib/matplotlib/backend_bases.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,16 @@ def _fix_ipython_backend2gui(cls):
16601660
if _is_non_interactive_terminal_ipython(ip):
16611661
ip.enable_gui(backend2gui_rif)
16621662

1663+
@classmethod
1664+
def new_manager(cls, figure, num):
1665+
"""
1666+
Create a new figure manager for *figure*, using this canvas class.
1667+
1668+
Backends should override this method to instantiate the correct figure
1669+
manager subclass, and perform any additional setup that may be needed.
1670+
"""
1671+
return FigureManagerBase(cls(figure), num)
1672+
16631673
@contextmanager
16641674
def _idle_draw_cntx(self):
16651675
self._is_idle_drawing = True
@@ -3223,11 +3233,10 @@ def configure_subplots(self, *args):
32233233
if hasattr(self, "subplot_tool"):
32243234
self.subplot_tool.figure.canvas.manager.show()
32253235
return
3226-
plt = _safe_pyplot_import()
3236+
# This import needs to happen here due to circular imports.
3237+
from matplotlib.figure import Figure
32273238
with mpl.rc_context({"toolbar": "none"}): # No navbar for the toolfig.
3228-
# Use new_figure_manager() instead of figure() so that the figure
3229-
# doesn't get registered with pyplot.
3230-
manager = plt.new_figure_manager(-1, (6, 3))
3239+
manager = type(self.canvas).new_manager(Figure(figsize=(6, 3)), -1)
32313240
manager.set_window_title("Subplot configuration tool")
32323241
tool_fig = manager.canvas.figure
32333242
tool_fig.subplots_adjust(top=0.9)
@@ -3455,9 +3464,7 @@ def new_figure_manager(cls, num, *args, **kwargs):
34553464
@classmethod
34563465
def new_figure_manager_given_figure(cls, num, figure):
34573466
"""Create a new figure manager instance for the given figure."""
3458-
canvas = cls.FigureCanvas(figure)
3459-
manager = cls.FigureManager(canvas, num)
3460-
return manager
3467+
return cls.FigureCanvas.new_manager(figure, num)
34613468

34623469
@classmethod
34633470
def draw_if_interactive(cls):

lib/matplotlib/backends/_backend_tk.py

+37-39
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,43 @@ def _update_device_pixel_ratio(self, event=None):
227227
w, h = self.get_width_height(physical=True)
228228
self._tkcanvas.configure(width=w, height=h)
229229

230+
@classmethod
231+
def new_manager(cls, figure, num):
232+
# docstring inherited
233+
with _restore_foreground_window_at_end():
234+
if cbook._get_running_interactive_framework() is None:
235+
cbook._setup_new_guiapp()
236+
_c_internal_utils.Win32_SetProcessDpiAwareness_max()
237+
window = tk.Tk(className="matplotlib")
238+
window.withdraw()
239+
240+
# Put a Matplotlib icon on the window rather than the default tk
241+
# icon. See https://www.tcl.tk/man/tcl/TkCmd/wm.html#M50
242+
#
243+
# `ImageTk` can be replaced with `tk` whenever the minimum
244+
# supported Tk version is increased to 8.6, as Tk 8.6+ natively
245+
# supports PNG images.
246+
icon_fname = str(cbook._get_data_path(
247+
'images/matplotlib.png'))
248+
icon_img = ImageTk.PhotoImage(file=icon_fname, master=window)
249+
250+
icon_fname_large = str(cbook._get_data_path(
251+
'images/matplotlib_large.png'))
252+
icon_img_large = ImageTk.PhotoImage(
253+
file=icon_fname_large, master=window)
254+
try:
255+
window.iconphoto(False, icon_img_large, icon_img)
256+
except Exception as exc:
257+
# log the failure (due e.g. to Tk version), but carry on
258+
_log.info('Could not load matplotlib icon: %s', exc)
259+
260+
canvas = cls.FigureCanvas(figure, master=window)
261+
manager = cls.FigureManager(canvas, num, window)
262+
if mpl.is_interactive():
263+
manager.show()
264+
canvas.draw_idle()
265+
return manager
266+
230267
def resize(self, event):
231268
width, height = event.width, event.height
232269
if self._resize_callback is not None:
@@ -910,45 +947,6 @@ def trigger(self, *args):
910947
class _BackendTk(_Backend):
911948
FigureManager = FigureManagerTk
912949

913-
@classmethod
914-
def new_figure_manager_given_figure(cls, num, figure):
915-
"""
916-
Create a new figure manager instance for the given figure.
917-
"""
918-
with _restore_foreground_window_at_end():
919-
if cbook._get_running_interactive_framework() is None:
920-
cbook._setup_new_guiapp()
921-
_c_internal_utils.Win32_SetProcessDpiAwareness_max()
922-
window = tk.Tk(className="matplotlib")
923-
window.withdraw()
924-
925-
# Put a Matplotlib icon on the window rather than the default tk
926-
# icon. See https://www.tcl.tk/man/tcl/TkCmd/wm.html#M50
927-
#
928-
# `ImageTk` can be replaced with `tk` whenever the minimum
929-
# supported Tk version is increased to 8.6, as Tk 8.6+ natively
930-
# supports PNG images.
931-
icon_fname = str(cbook._get_data_path(
932-
'images/matplotlib.png'))
933-
icon_img = ImageTk.PhotoImage(file=icon_fname, master=window)
934-
935-
icon_fname_large = str(cbook._get_data_path(
936-
'images/matplotlib_large.png'))
937-
icon_img_large = ImageTk.PhotoImage(
938-
file=icon_fname_large, master=window)
939-
try:
940-
window.iconphoto(False, icon_img_large, icon_img)
941-
except Exception as exc:
942-
# log the failure (due e.g. to Tk version), but carry on
943-
_log.info('Could not load matplotlib icon: %s', exc)
944-
945-
canvas = cls.FigureCanvas(figure, master=window)
946-
manager = cls.FigureManager(canvas, num, window)
947-
if mpl.is_interactive():
948-
manager.show()
949-
canvas.draw_idle()
950-
return manager
951-
952950
@staticmethod
953951
def mainloop():
954952
managers = Gcf.get_all_fig_managers()

lib/matplotlib/backends/backend_gtk3.py

+5
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ def __init__(self, figure=None):
117117
style_ctx.add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
118118
style_ctx.add_class("matplotlib-canvas")
119119

120+
@classmethod
121+
def new_manager(cls, figure, num):
122+
# docstring inherited
123+
return FigureManagerGTK3(cls(figure), num)
124+
120125
def destroy(self):
121126
self.close_event()
122127

lib/matplotlib/backends/backend_gtk4.py

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ def __init__(self, figure=None):
8080
style_ctx.add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
8181
style_ctx.add_class("matplotlib-canvas")
8282

83+
@classmethod
84+
def new_manager(cls, figure, num):
85+
# docstring inherited
86+
return FigureManagerGTK4(cls(figure), num)
87+
8388
def pick(self, mouseevent):
8489
# GtkWidget defines pick in GTK4, so we need to override here to work
8590
# with the base implementation we want.

lib/matplotlib/backends/backend_macosx.py

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ def __init__(self, figure):
3131
width, height = self.get_width_height()
3232
_macosx.FigureCanvas.__init__(self, width, height)
3333

34+
@classmethod
35+
def new_manager(cls, figure, num):
36+
# docstring inherited
37+
return FigureManagerMac(cls(figure), num)
38+
3439
def set_cursor(self, cursor):
3540
# docstring inherited
3641
_macosx.set_cursor(cursor)

lib/matplotlib/backends/backend_nbagg.py

+14-16
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,20 @@ def remove_comm(self, comm_id):
143143

144144

145145
class FigureCanvasNbAgg(FigureCanvasWebAggCore):
146-
pass
146+
@classmethod
147+
def new_manager(cls, figure, num):
148+
canvas = cls(figure)
149+
manager = FigureManagerNbAgg(canvas, num)
150+
if is_interactive():
151+
manager.show()
152+
figure.canvas.draw_idle()
153+
154+
def destroy(event):
155+
canvas.mpl_disconnect(cid)
156+
Gcf.destroy(manager)
157+
158+
cid = canvas.mpl_connect('close_event', destroy)
159+
return manager
147160

148161

149162
class CommSocket:
@@ -228,21 +241,6 @@ class _BackendNbAgg(_Backend):
228241
FigureCanvas = FigureCanvasNbAgg
229242
FigureManager = FigureManagerNbAgg
230243

231-
@staticmethod
232-
def new_figure_manager_given_figure(num, figure):
233-
canvas = FigureCanvasNbAgg(figure)
234-
manager = FigureManagerNbAgg(canvas, num)
235-
if is_interactive():
236-
manager.show()
237-
figure.canvas.draw_idle()
238-
239-
def destroy(event):
240-
canvas.mpl_disconnect(cid)
241-
Gcf.destroy(manager)
242-
243-
cid = canvas.mpl_connect('close_event', destroy)
244-
return manager
245-
246244
@staticmethod
247245
def show(block=None):
248246
## TODO: something to do when keyword block==False ?

lib/matplotlib/backends/backend_qt.py

+5
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ def __init__(self, figure=None):
235235
palette = QtGui.QPalette(QtGui.QColor("white"))
236236
self.setPalette(palette)
237237

238+
@classmethod
239+
def new_manager(cls, figure, num):
240+
# docstring inherited
241+
return FigureManagerQT(cls(figure), num)
242+
238243
def _update_pixel_ratio(self):
239244
if self._set_device_pixel_ratio(_devicePixelRatioF(self)):
240245
# The easiest way to resize the canvas is to emit a resizeEvent

lib/matplotlib/backends/backend_webagg.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ def run(self):
4848

4949

5050
class FigureCanvasWebAgg(core.FigureCanvasWebAggCore):
51-
pass
51+
@classmethod
52+
def new_manager(cls, figure, num):
53+
# docstring inherited
54+
return core.FigureManagerWebAgg(cls(figure), num)
5255

5356

5457
class WebAggApplication(tornado.web.Application):

lib/matplotlib/backends/backend_wx.py

+20-18
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ def error_msg_wx(msg, parent=None):
6868
return None
6969

7070

71+
# lru_cache holds a reference to the App and prevents it from being gc'ed.
72+
@functools.lru_cache(1)
73+
def _create_wxapp():
74+
wxapp = wx.App(False)
75+
wxapp.SetExitOnFrameDelete(True)
76+
cbook._setup_new_guiapp()
77+
return wxapp
78+
79+
7180
class TimerWx(TimerBase):
7281
"""Subclass of `.TimerBase` using wx.Timer events."""
7382

@@ -533,6 +542,17 @@ def __init__(self, parent, id, figure=None):
533542
self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # Reduce flicker.
534543
self.SetBackgroundColour(wx.WHITE)
535544

545+
@classmethod
546+
def new_manager(cls, figure, num):
547+
# docstring inherited
548+
wxapp = wx.GetApp() or _create_wxapp()
549+
frame = FigureFrameWx(num, figure, canvas_class=cls)
550+
figmgr = frame.get_figure_manager()
551+
if mpl.is_interactive():
552+
figmgr.frame.Show()
553+
figure.canvas.draw_idle()
554+
return figmgr
555+
536556
def Copy_to_Clipboard(self, event=None):
537557
"""Copy bitmap of canvas to system clipboard."""
538558
bmp_obj = wx.BitmapDataObject()
@@ -1355,24 +1375,6 @@ class _BackendWx(_Backend):
13551375
FigureCanvas = FigureCanvasWx
13561376
FigureManager = FigureManagerWx
13571377

1358-
@classmethod
1359-
def new_figure_manager_given_figure(cls, num, figure):
1360-
# Create a wx.App instance if it has not been created so far.
1361-
wxapp = wx.GetApp()
1362-
if wxapp is None:
1363-
wxapp = wx.App()
1364-
wxapp.SetExitOnFrameDelete(True)
1365-
cbook._setup_new_guiapp()
1366-
# Retain a reference to the app object so that it does not get
1367-
# garbage collected.
1368-
_BackendWx._theWxApp = wxapp
1369-
# Attaches figure.canvas, figure.canvas.manager.
1370-
frame = FigureFrameWx(num, figure, canvas_class=cls.FigureCanvas)
1371-
if mpl.is_interactive():
1372-
frame.Show()
1373-
figure.canvas.draw_idle()
1374-
return figure.canvas.manager
1375-
13761378
@staticmethod
13771379
def mainloop():
13781380
if not wx.App.IsMainLoopRunning():

0 commit comments

Comments
 (0)