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

Skip to content

Commit daee950

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 5fe4b34 commit daee950

11 files changed

+140
-75
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,42 @@ def stop_event_loop(self):
24942494
"""
24952495
self._looping = False
24962496

2497+
@classmethod
2498+
def start_main_loop(cls):
2499+
"""
2500+
Start the main event loop.
2501+
2502+
Interactive backends need to reimplement this method or `pyplot_show`.
2503+
"""
2504+
2505+
@classmethod
2506+
def pyplot_show(cls, *, block=None):
2507+
"""
2508+
Show all figures. This method is the implementation of `.pyplot.show`.
2509+
2510+
Interactive backends need to reimplement this method or
2511+
`start_main_loop`.
2512+
2513+
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
2514+
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
2515+
`interactive` mode.
2516+
"""
2517+
managers = Gcf.get_all_fig_managers()
2518+
if not managers:
2519+
return
2520+
for manager in managers:
2521+
try:
2522+
manager.show() # Emits a warning for non-interactive backend.
2523+
except NonGuiException as exc:
2524+
_api.warn_external(str(exc))
2525+
if block is None:
2526+
# Hack: Are we in IPython's pylab mode?
2527+
from matplotlib import pyplot
2528+
ipython_pylab = hasattr(pyplot.show, "_needmain")
2529+
block = not ipython_pylab and not is_interactive()
2530+
if block:
2531+
cls.start_main_loop()
2532+
24972533

24982534
def key_press_handler(event, canvas=None, toolbar=None):
24992535
"""
@@ -3473,7 +3509,12 @@ def new_figure_manager_given_figure(cls, num, figure):
34733509

34743510
@classmethod
34753511
def draw_if_interactive(cls):
3476-
if cls.mainloop is not None and is_interactive():
3512+
canvas_class = cls.FigureCanvas
3513+
interactive_backend = (
3514+
canvas_class.start_main_loop != FigureCanvasBase.start_main_loop
3515+
or canvas_class.show != FigureCanvasBase.show
3516+
)
3517+
if interactive_backend and is_interactive():
34773518
manager = Gcf.get_active()
34783519
if manager:
34793520
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
# The GTK3/GTK4 backends will have already called `gi.require_version` to set
@@ -112,6 +113,29 @@ def _on_timer(self):
112113
return False
113114

114115

116+
class _FigureCanvasGTK(FigureCanvasBase):
117+
_timer_cls = TimerGTK
118+
119+
@classmethod
120+
def start_main_loop(cls):
121+
global _application
122+
if _application is None:
123+
return
124+
125+
try:
126+
_application.run() # Quits when all added windows close.
127+
except KeyboardInterrupt:
128+
# Ensure all windows can process their close event from
129+
# _shutdown_application.
130+
context = GLib.MainContext.default()
131+
while context.pending():
132+
context.iteration(True)
133+
raise
134+
finally:
135+
# Running after quit is undefined, so create a new one next time.
136+
_application = None
137+
138+
115139
class _FigureManagerGTK(FigureManagerBase):
116140
"""
117141
Attributes
@@ -299,21 +323,4 @@ def trigger(self, *args):
299323

300324

301325
class _BackendGTK(_Backend):
302-
@staticmethod
303-
def mainloop():
304-
global _application
305-
if _application is None:
306-
return
307-
308-
try:
309-
_application.run() # Quits when all added windows close.
310-
except KeyboardInterrupt:
311-
# Ensure all windows can process their close event from
312-
# _shutdown_application.
313-
context = GLib.MainContext.default()
314-
while context.pending():
315-
context.iteration(True)
316-
raise
317-
finally:
318-
# Running after quit is undefined, so create a new one next time.
319-
_application = None
326+
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(Gtk.DrawingArea, FigureCanvasBase):
71+
class FigureCanvasGTK3(Gtk.DrawingArea, _FigureCanvasGTK):
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(Gtk.DrawingArea, FigureCanvasBase):
32+
class FigureCanvasGTK4(Gtk.DrawingArea, _FigureCanvasGTK):
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
@@ -97,6 +97,10 @@ def resize(self, width, height):
9797
FigureCanvasBase.resize_event(self)
9898
self.draw_idle()
9999

100+
@classmethod
101+
def start_main_loop(cls):
102+
_macosx.show()
103+
100104

101105
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
102106

@@ -171,7 +175,4 @@ def show(self):
171175
class _BackendMac(_Backend):
172176
FigureCanvas = FigureCanvasMac
173177
FigureManager = FigureManagerMac
174-
175-
@staticmethod
176-
def mainloop():
177-
_macosx.show()
178+
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
@@ -518,6 +518,12 @@ def _draw_rect_callback(painter):
518518
self._draw_rect_callback = _draw_rect_callback
519519
self.update()
520520

521+
@classmethod
522+
def start_main_loop(cls):
523+
qapp = QtWidgets.QApplication.instance()
524+
with _maybe_allow_interrupt(qapp):
525+
qt_compat._exec(qapp)
526+
521527

522528
class MainWindow(QtWidgets.QMainWindow):
523529
closing = QtCore.Signal()
@@ -1040,9 +1046,4 @@ def trigger(self, *args, **kwargs):
10401046
class _BackendQT(_Backend):
10411047
FigureCanvas = FigureCanvasQT
10421048
FigureManager = FigureManagerQT
1043-
1044-
@staticmethod
1045-
def mainloop():
1046-
qapp = QtWidgets.QApplication.instance()
1047-
with _maybe_allow_interrupt(qapp):
1048-
qt_compat._exec(qapp)
1049+
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ def new_figure_manager(num, *args, FigureClass=Figure, **kwargs):
310310
new_figure_manager_given_figure
311311
backend_mod.new_figure_manager = new_figure_manager
312312

313+
if show is None:
314+
backend_mod.show = canvas_class.pyplot_show
315+
313316
_log.debug("Loaded backend %s version %s.",
314317
newbackend, backend_mod.backend_version)
315318

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)