Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9bb44a8

Browse files
committed
Move show() to somewhere naturally inheritable.
It's actually not clear whether to move it to FigureCanvas or FigureManager. FigureCanvas already has start_event_loop (cf. start_main_loop), but pyplot_show/start_main_loop is more a global/pyplot-only concept, so perhaps it belongs to FigureManager instead? OTOH, being on canvas_class makes it easier for switch_backend to access it (it doesn't need to go through `canvas_class.manager_class.pyplot_show`, which is relevant considering that some designs of inheritable backends didn't even have a manager_class attribute).
1 parent 04c8107 commit 9bb44a8

11 files changed

+145
-80
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2480,6 +2480,42 @@ def stop_event_loop(self):
24802480
"""
24812481
self._looping = False
24822482

2483+
@classmethod
2484+
def start_main_loop(cls):
2485+
"""
2486+
Start the main event loop.
2487+
2488+
Interactive backends need to reimplement this method or `pyplot_show`.
2489+
"""
2490+
2491+
@classmethod
2492+
def pyplot_show(cls, *, block=None):
2493+
"""
2494+
Show all figures. This method is the implementation of `.pyplot.show`.
2495+
2496+
Interactive backends need to reimplement this method or
2497+
`start_main_loop`.
2498+
2499+
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
2500+
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
2501+
`interactive` mode.
2502+
"""
2503+
managers = Gcf.get_all_fig_managers()
2504+
if not managers:
2505+
return
2506+
for manager in managers:
2507+
try:
2508+
manager.show() # Emits a warning for non-interactive backend.
2509+
except NonGuiException as exc:
2510+
_api.warn_external(str(exc))
2511+
if block is None:
2512+
# Hack: Are we in IPython's pylab mode?
2513+
from matplotlib import pyplot
2514+
ipython_pylab = hasattr(pyplot.show, "_needmain")
2515+
block = not ipython_pylab and not is_interactive()
2516+
if block:
2517+
cls.start_main_loop()
2518+
24832519

24842520
def key_press_handler(event, canvas=None, toolbar=None):
24852521
"""
@@ -3459,7 +3495,12 @@ def new_figure_manager_given_figure(cls, num, figure):
34593495

34603496
@classmethod
34613497
def draw_if_interactive(cls):
3462-
if cls.mainloop is not None and is_interactive():
3498+
canvas_class = cls.FigureCanvas
3499+
interactive_backend = (
3500+
canvas_class.start_main_loop != FigureCanvasBase.start_main_loop
3501+
or canvas_class.show != FigureCanvasBase.show
3502+
)
3503+
if interactive_backend and is_interactive():
34633504
manager = Gcf.get_active()
34643505
if manager:
34653506
manager.canvas.draw_idle()

lib/matplotlib/backends/_backend_gtk.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from matplotlib import _api, backend_tools, cbook
1010
from matplotlib._pylab_helpers import Gcf
1111
from matplotlib.backend_bases import (
12-
_Backend, FigureManagerBase, NavigationToolbar2, TimerBase)
12+
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
13+
TimerBase)
1314
from matplotlib.backend_tools import Cursors
1415

1516
import gi
@@ -118,6 +119,29 @@ def _on_timer(self):
118119
return False
119120

120121

122+
class _FigureCanvasGTK(FigureCanvasBase):
123+
_timer_cls = TimerGTK
124+
125+
@classmethod
126+
def start_main_loop(cls):
127+
global _application
128+
if _application is None:
129+
return
130+
131+
try:
132+
_application.run() # Quits when all added windows close.
133+
except KeyboardInterrupt:
134+
# Ensure all windows can process their close event from
135+
# _shutdown_application.
136+
context = GLib.MainContext.default()
137+
while context.pending():
138+
context.iteration(True)
139+
raise
140+
finally:
141+
# Running after quit is undefined, so create a new one next time.
142+
_application = None
143+
144+
121145
class _FigureManagerGTK(FigureManagerBase):
122146
"""
123147
Attributes
@@ -305,21 +329,4 @@ def trigger(self, *args):
305329

306330

307331
class _BackendGTK(_Backend):
308-
@staticmethod
309-
def mainloop():
310-
global _application
311-
if _application is None:
312-
return
313-
314-
try:
315-
_application.run() # Quits when all added windows close.
316-
except KeyboardInterrupt:
317-
# Ensure all windows can process their close event from
318-
# _shutdown_application.
319-
context = GLib.MainContext.default()
320-
while context.pending():
321-
context.iteration(True)
322-
raise
323-
finally:
324-
# Running after quit is undefined, so create a new one next time.
325-
_application = None
332+
mainloop = _FigureCanvasGTK.start_main_loop

lib/matplotlib/backends/_backend_tk.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,20 @@ def set_cursor(self, cursor):
396396
except tkinter.TclError:
397397
pass
398398

399+
@classmethod
400+
def start_main_loop(cls):
401+
managers = Gcf.get_all_fig_managers()
402+
if managers:
403+
first_manager = managers[0]
404+
manager_class = type(first_manager)
405+
if manager_class._owns_mainloop:
406+
return
407+
manager_class._owns_mainloop = True
408+
try:
409+
first_manager.window.mainloop()
410+
finally:
411+
manager_class._owns_mainloop = False
412+
399413

400414
class FigureManagerTk(FigureManagerBase):
401415
"""
@@ -1002,18 +1016,6 @@ def trigger(self, *args):
10021016

10031017
@_Backend.export
10041018
class _BackendTk(_Backend):
1019+
FigureCanvas = FigureCanvasTk
10051020
FigureManager = FigureManagerTk
1006-
1007-
@staticmethod
1008-
def mainloop():
1009-
managers = Gcf.get_all_fig_managers()
1010-
if managers:
1011-
first_manager = managers[0]
1012-
manager_class = type(first_manager)
1013-
if manager_class._owns_mainloop:
1014-
return
1015-
manager_class._owns_mainloop = True
1016-
try:
1017-
first_manager.window.mainloop()
1018-
finally:
1019-
manager_class._owns_mainloop = False
1021+
mainloop = FigureCanvas.start_main_loop

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from gi.repository import Gio, GLib, GObject, Gtk, Gdk
2727
from . import _backend_gtk
2828
from ._backend_gtk import (
29-
_BackendGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
29+
_BackendGTK, _FigureCanvasGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
3030
TimerGTK as TimerGTK3,
3131
)
3232
from ._backend_gtk import backend_version # noqa: F401 # pylint: disable=W0611
@@ -68,9 +68,8 @@ def _mpl_to_gtk_cursor(mpl_cursor):
6868
_backend_gtk.mpl_to_gtk_cursor_name(mpl_cursor))
6969

7070

71-
class FigureCanvasGTK3(FigureCanvasBase, Gtk.DrawingArea):
71+
class FigureCanvasGTK3(_FigureCanvasGTK, Gtk.DrawingArea):
7272
required_interactive_framework = "gtk3"
73-
_timer_cls = TimerGTK3
7473
manager_class = _api.classproperty(lambda cls: FigureManagerGTK3)
7574
# Setting this as a static constant prevents
7675
# this resulting expression from leaking

lib/matplotlib/backends/backend_gtk4.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@
2323
from gi.repository import Gio, GLib, GObject, Gtk, Gdk, GdkPixbuf
2424
from . import _backend_gtk
2525
from ._backend_gtk import (
26-
_BackendGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
26+
_BackendGTK, _FigureCanvasGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
2727
TimerGTK as TimerGTK4,
2828
)
2929
from ._backend_gtk import backend_version # noqa: F401 # pylint: disable=W0611
3030

3131

32-
class FigureCanvasGTK4(FigureCanvasBase, Gtk.DrawingArea):
32+
class FigureCanvasGTK4(_FigureCanvasGTK, Gtk.DrawingArea):
3333
required_interactive_framework = "gtk4"
3434
supports_blit = False
35-
_timer_cls = TimerGTK4
3635
manager_class = _api.classproperty(lambda cls: FigureManagerGTK4)
3736
_context_is_scaled = False
3837

lib/matplotlib/backends/backend_macosx.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ def resize(self, width, height):
103103
FigureCanvasBase.resize_event(self)
104104
self.draw_idle()
105105

106+
@classmethod
107+
def start_main_loop(cls):
108+
_macosx.show()
109+
106110

107111
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
108112

@@ -174,7 +178,4 @@ def show(self):
174178
class _BackendMac(_Backend):
175179
FigureCanvas = FigureCanvasMac
176180
FigureManager = FigureManagerMac
177-
178-
@staticmethod
179-
def mainloop():
180-
_macosx.show()
181+
mainloop = FigureCanvas.start_main_loop

lib/matplotlib/backends/backend_qt.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,12 @@ def _draw_rect_callback(painter):
477477
self._draw_rect_callback = _draw_rect_callback
478478
self.update()
479479

480+
@classmethod
481+
def start_main_loop(cls):
482+
qapp = QtWidgets.QApplication.instance()
483+
with _maybe_allow_interrupt(qapp):
484+
qt_compat._exec(qapp)
485+
480486

481487
class MainWindow(QtWidgets.QMainWindow):
482488
closing = QtCore.Signal()
@@ -999,9 +1005,4 @@ def trigger(self, *args, **kwargs):
9991005
class _BackendQT(_Backend):
10001006
FigureCanvas = FigureCanvasQT
10011007
FigureManager = FigureManagerQT
1002-
1003-
@staticmethod
1004-
def mainloop():
1005-
qapp = QtWidgets.QApplication.instance()
1006-
with _maybe_allow_interrupt(qapp):
1007-
qt_compat._exec(qapp)
1008+
mainloop = FigureCanvas.start_main_loop

lib/matplotlib/backends/backend_webagg.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ class FigureManagerWebAgg(core.FigureManagerWebAgg):
5555
class FigureCanvasWebAgg(core.FigureCanvasWebAggCore):
5656
manager_class = FigureManagerWebAgg
5757

58+
@classmethod
59+
def pyplot_show(cls, *, block=None):
60+
WebAggApplication.initialize()
61+
62+
url = "http://{address}:{port}{prefix}".format(
63+
address=WebAggApplication.address,
64+
port=WebAggApplication.port,
65+
prefix=WebAggApplication.url_prefix)
66+
67+
if mpl.rcParams['webagg.open_in_browser']:
68+
import webbrowser
69+
if not webbrowser.open(url):
70+
print("To view figure, visit {0}".format(url))
71+
else:
72+
print("To view figure, visit {0}".format(url))
73+
74+
WebAggApplication.start()
75+
5876

5977
class WebAggApplication(tornado.web.Application):
6078
initialized = False
@@ -305,21 +323,3 @@ def ipython_inline_display(figure):
305323
class _BackendWebAgg(_Backend):
306324
FigureCanvas = FigureCanvasWebAgg
307325
FigureManager = FigureManagerWebAgg
308-
309-
@staticmethod
310-
def show(*, block=None):
311-
WebAggApplication.initialize()
312-
313-
url = "http://{address}:{port}{prefix}".format(
314-
address=WebAggApplication.address,
315-
port=WebAggApplication.port,
316-
prefix=WebAggApplication.url_prefix)
317-
318-
if mpl.rcParams['webagg.open_in_browser']:
319-
import webbrowser
320-
if not webbrowser.open(url):
321-
print("To view figure, visit {0}".format(url))
322-
else:
323-
print("To view figure, visit {0}".format(url))
324-
325-
WebAggApplication.start()

lib/matplotlib/backends/backend_wx.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,13 @@ def _on_enter(self, event):
831831
event.Skip()
832832
FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y))
833833

834+
@classmethod
835+
def start_main_loop(cls):
836+
if not wx.App.IsMainLoopRunning():
837+
wxapp = wx.GetApp()
838+
if wxapp is not None:
839+
wxapp.MainLoop()
840+
834841

835842
class FigureCanvasWx(_FigureCanvasWxBase):
836843
# Rendering to a Wx canvas using the deprecated Wx renderer.
@@ -1364,10 +1371,4 @@ def trigger(self, *args, **kwargs):
13641371
class _BackendWx(_Backend):
13651372
FigureCanvas = FigureCanvasWx
13661373
FigureManager = FigureManagerWx
1367-
1368-
@staticmethod
1369-
def mainloop():
1370-
if not wx.App.IsMainLoopRunning():
1371-
wxapp = wx.GetApp()
1372-
if wxapp is not None:
1373-
wxapp.MainLoop()
1374+
mainloop = FigureCanvas.start_main_loop

lib/matplotlib/pyplot.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,18 +280,18 @@ def switch_backend(newbackend):
280280
# Classically, backends can directly export these functions. This should
281281
# keep working for backcompat.
282282
new_figure_manager = getattr(backend_mod, "new_figure_manager", None)
283-
# draw_if_interactive = getattr(backend_mod, "draw_if_interactive", None)
284-
# show = getattr(backend_mod, "show", None)
283+
show = getattr(backend_mod, "show", None)
285284
# In that classical approach, backends are implemented as modules, but
286285
# "inherit" default method implementations from backend_bases._Backend.
287286
# This is achieved by creating a "class" that inherits from
288287
# backend_bases._Backend and whose body is filled with the module globals.
289288
class backend_mod(matplotlib.backend_bases._Backend):
290289
locals().update(vars(backend_mod))
291290

292-
# However, the newer approach for defining new_figure_manager (and, in
293-
# the future, draw_if_interactive and show) is to derive them from canvas
294-
# methods. In that case, also update backend_mod accordingly.
291+
# However, the newer approach for defining new_figure_manager and show
292+
# is to derive them from canvas methods. In that case, also update
293+
# backend_mod accordingly.
294+
295295
if new_figure_manager is None:
296296
def new_figure_manager_given_figure(num, figure):
297297
return canvas_class.new_manager(figure, num)
@@ -304,6 +304,9 @@ def new_figure_manager(num, *args, FigureClass=Figure, **kwargs):
304304
new_figure_manager_given_figure
305305
backend_mod.new_figure_manager = new_figure_manager
306306

307+
if show is None:
308+
backend_mod.show = canvas_class.pyplot_show
309+
307310
_log.debug("Loaded backend %s version %s.",
308311
newbackend, backend_mod.backend_version)
309312

lib/matplotlib/tests/test_backend_template.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import sys
66
from types import SimpleNamespace
7+
from unittest.mock import MagicMock
78

89
import matplotlib as mpl
910
from matplotlib import pyplot as plt
@@ -21,3 +22,13 @@ def test_new_manager(monkeypatch):
2122
monkeypatch.setitem(sys.modules, "mpl_test_backend", mpl_test_backend)
2223
mpl.use("module://mpl_test_backend")
2324
assert type(plt.figure().canvas) == backend_template.FigureCanvasTemplate
25+
26+
27+
def test_show(monkeypatch):
28+
mpl_test_backend = SimpleNamespace(**vars(backend_template))
29+
mock_show = backend_template.FigureCanvasTemplate.pyplot_show = MagicMock()
30+
del mpl_test_backend.show
31+
monkeypatch.setitem(sys.modules, "mpl_test_backend", mpl_test_backend)
32+
mpl.use("module://mpl_test_backend")
33+
plt.show()
34+
mock_show.assert_called_with()

0 commit comments

Comments
 (0)