##// END OF EJS Templates
Convert "osx" gui framework to/from "macosx" in Matplotlib (#14420)...
M Bussonnier -
r28753:85bb5302 merge
parent child Browse files
Show More
@@ -1,518 +1,538 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities."""
2 """Pylab (matplotlib) support utilities."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from io import BytesIO
7 from io import BytesIO
8 from binascii import b2a_base64
8 from binascii import b2a_base64
9 from functools import partial
9 from functools import partial
10 import warnings
10 import warnings
11
11
12 from IPython.core.display import _pngxy
12 from IPython.core.display import _pngxy
13 from IPython.utils.decorators import flag_calls
13 from IPython.utils.decorators import flag_calls
14
14
15
15
16 # Matplotlib backend resolution functionality moved from IPython to Matplotlib
16 # Matplotlib backend resolution functionality moved from IPython to Matplotlib
17 # in IPython 8.24 and Matplotlib 3.9.1. Need to keep `backends` and `backend2gui`
17 # in IPython 8.24 and Matplotlib 3.9.1. Need to keep `backends` and `backend2gui`
18 # here for earlier Matplotlib and for external backend libraries such as
18 # here for earlier Matplotlib and for external backend libraries such as
19 # mplcairo that might rely upon it.
19 # mplcairo that might rely upon it.
20 _deprecated_backends = {
20 _deprecated_backends = {
21 "tk": "TkAgg",
21 "tk": "TkAgg",
22 "gtk": "GTKAgg",
22 "gtk": "GTKAgg",
23 "gtk3": "GTK3Agg",
23 "gtk3": "GTK3Agg",
24 "gtk4": "GTK4Agg",
24 "gtk4": "GTK4Agg",
25 "wx": "WXAgg",
25 "wx": "WXAgg",
26 "qt4": "Qt4Agg",
26 "qt4": "Qt4Agg",
27 "qt5": "Qt5Agg",
27 "qt5": "Qt5Agg",
28 "qt6": "QtAgg",
28 "qt6": "QtAgg",
29 "qt": "QtAgg",
29 "qt": "QtAgg",
30 "osx": "MacOSX",
30 "osx": "MacOSX",
31 "nbagg": "nbAgg",
31 "nbagg": "nbAgg",
32 "webagg": "WebAgg",
32 "webagg": "WebAgg",
33 "notebook": "nbAgg",
33 "notebook": "nbAgg",
34 "agg": "agg",
34 "agg": "agg",
35 "svg": "svg",
35 "svg": "svg",
36 "pdf": "pdf",
36 "pdf": "pdf",
37 "ps": "ps",
37 "ps": "ps",
38 "inline": "module://matplotlib_inline.backend_inline",
38 "inline": "module://matplotlib_inline.backend_inline",
39 "ipympl": "module://ipympl.backend_nbagg",
39 "ipympl": "module://ipympl.backend_nbagg",
40 "widget": "module://ipympl.backend_nbagg",
40 "widget": "module://ipympl.backend_nbagg",
41 }
41 }
42
42
43 # We also need a reverse backends2guis mapping that will properly choose which
43 # We also need a reverse backends2guis mapping that will properly choose which
44 # GUI support to activate based on the desired matplotlib backend. For the
44 # GUI support to activate based on the desired matplotlib backend. For the
45 # most part it's just a reverse of the above dict, but we also need to add a
45 # most part it's just a reverse of the above dict, but we also need to add a
46 # few others that map to the same GUI manually:
46 # few others that map to the same GUI manually:
47 _deprecated_backend2gui = dict(
47 _deprecated_backend2gui = dict(
48 zip(_deprecated_backends.values(), _deprecated_backends.keys())
48 zip(_deprecated_backends.values(), _deprecated_backends.keys())
49 )
49 )
50 # In the reverse mapping, there are a few extra valid matplotlib backends that
50 # In the reverse mapping, there are a few extra valid matplotlib backends that
51 # map to the same GUI support
51 # map to the same GUI support
52 _deprecated_backend2gui["GTK"] = _deprecated_backend2gui["GTKCairo"] = "gtk"
52 _deprecated_backend2gui["GTK"] = _deprecated_backend2gui["GTKCairo"] = "gtk"
53 _deprecated_backend2gui["GTK3Cairo"] = "gtk3"
53 _deprecated_backend2gui["GTK3Cairo"] = "gtk3"
54 _deprecated_backend2gui["GTK4Cairo"] = "gtk4"
54 _deprecated_backend2gui["GTK4Cairo"] = "gtk4"
55 _deprecated_backend2gui["WX"] = "wx"
55 _deprecated_backend2gui["WX"] = "wx"
56 _deprecated_backend2gui["CocoaAgg"] = "osx"
56 _deprecated_backend2gui["CocoaAgg"] = "osx"
57 # There needs to be a hysteresis here as the new QtAgg Matplotlib backend
57 # There needs to be a hysteresis here as the new QtAgg Matplotlib backend
58 # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
58 # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
59 # and Qt6.
59 # and Qt6.
60 _deprecated_backend2gui["QtAgg"] = "qt"
60 _deprecated_backend2gui["QtAgg"] = "qt"
61 _deprecated_backend2gui["Qt4Agg"] = "qt4"
61 _deprecated_backend2gui["Qt4Agg"] = "qt4"
62 _deprecated_backend2gui["Qt5Agg"] = "qt5"
62 _deprecated_backend2gui["Qt5Agg"] = "qt5"
63
63
64 # And some backends that don't need GUI integration
64 # And some backends that don't need GUI integration
65 del _deprecated_backend2gui["nbAgg"]
65 del _deprecated_backend2gui["nbAgg"]
66 del _deprecated_backend2gui["agg"]
66 del _deprecated_backend2gui["agg"]
67 del _deprecated_backend2gui["svg"]
67 del _deprecated_backend2gui["svg"]
68 del _deprecated_backend2gui["pdf"]
68 del _deprecated_backend2gui["pdf"]
69 del _deprecated_backend2gui["ps"]
69 del _deprecated_backend2gui["ps"]
70 del _deprecated_backend2gui["module://matplotlib_inline.backend_inline"]
70 del _deprecated_backend2gui["module://matplotlib_inline.backend_inline"]
71 del _deprecated_backend2gui["module://ipympl.backend_nbagg"]
71 del _deprecated_backend2gui["module://ipympl.backend_nbagg"]
72
72
73
73
74 # Deprecated attributes backends and backend2gui mostly following PEP 562.
74 # Deprecated attributes backends and backend2gui mostly following PEP 562.
75 def __getattr__(name):
75 def __getattr__(name):
76 if name in ("backends", "backend2gui"):
76 if name in ("backends", "backend2gui"):
77 warnings.warn(
77 warnings.warn(
78 f"{name} is deprecated since IPython 8.24, backends are managed "
78 f"{name} is deprecated since IPython 8.24, backends are managed "
79 "in matplotlib and can be externally registered.",
79 "in matplotlib and can be externally registered.",
80 DeprecationWarning,
80 DeprecationWarning,
81 )
81 )
82 return globals()[f"_deprecated_{name}"]
82 return globals()[f"_deprecated_{name}"]
83 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
83 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
84
84
85
85
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87 # Matplotlib utilities
87 # Matplotlib utilities
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89
89
90
90
91 def getfigs(*fig_nums):
91 def getfigs(*fig_nums):
92 """Get a list of matplotlib figures by figure numbers.
92 """Get a list of matplotlib figures by figure numbers.
93
93
94 If no arguments are given, all available figures are returned. If the
94 If no arguments are given, all available figures are returned. If the
95 argument list contains references to invalid figures, a warning is printed
95 argument list contains references to invalid figures, a warning is printed
96 but the function continues pasting further figures.
96 but the function continues pasting further figures.
97
97
98 Parameters
98 Parameters
99 ----------
99 ----------
100 figs : tuple
100 figs : tuple
101 A tuple of ints giving the figure numbers of the figures to return.
101 A tuple of ints giving the figure numbers of the figures to return.
102 """
102 """
103 from matplotlib._pylab_helpers import Gcf
103 from matplotlib._pylab_helpers import Gcf
104 if not fig_nums:
104 if not fig_nums:
105 fig_managers = Gcf.get_all_fig_managers()
105 fig_managers = Gcf.get_all_fig_managers()
106 return [fm.canvas.figure for fm in fig_managers]
106 return [fm.canvas.figure for fm in fig_managers]
107 else:
107 else:
108 figs = []
108 figs = []
109 for num in fig_nums:
109 for num in fig_nums:
110 f = Gcf.figs.get(num)
110 f = Gcf.figs.get(num)
111 if f is None:
111 if f is None:
112 print('Warning: figure %s not available.' % num)
112 print('Warning: figure %s not available.' % num)
113 else:
113 else:
114 figs.append(f.canvas.figure)
114 figs.append(f.canvas.figure)
115 return figs
115 return figs
116
116
117
117
118 def figsize(sizex, sizey):
118 def figsize(sizex, sizey):
119 """Set the default figure size to be [sizex, sizey].
119 """Set the default figure size to be [sizex, sizey].
120
120
121 This is just an easy to remember, convenience wrapper that sets::
121 This is just an easy to remember, convenience wrapper that sets::
122
122
123 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
123 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
124 """
124 """
125 import matplotlib
125 import matplotlib
126 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
126 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
127
127
128
128
129 def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
129 def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
130 """Print a figure to an image, and return the resulting file data
130 """Print a figure to an image, and return the resulting file data
131
131
132 Returned data will be bytes unless ``fmt='svg'``,
132 Returned data will be bytes unless ``fmt='svg'``,
133 in which case it will be unicode.
133 in which case it will be unicode.
134
134
135 Any keyword args are passed to fig.canvas.print_figure,
135 Any keyword args are passed to fig.canvas.print_figure,
136 such as ``quality`` or ``bbox_inches``.
136 such as ``quality`` or ``bbox_inches``.
137
137
138 If `base64` is True, return base64-encoded str instead of raw bytes
138 If `base64` is True, return base64-encoded str instead of raw bytes
139 for binary-encoded image formats
139 for binary-encoded image formats
140
140
141 .. versionadded:: 7.29
141 .. versionadded:: 7.29
142 base64 argument
142 base64 argument
143 """
143 """
144 # When there's an empty figure, we shouldn't return anything, otherwise we
144 # When there's an empty figure, we shouldn't return anything, otherwise we
145 # get big blank areas in the qt console.
145 # get big blank areas in the qt console.
146 if not fig.axes and not fig.lines:
146 if not fig.axes and not fig.lines:
147 return
147 return
148
148
149 dpi = fig.dpi
149 dpi = fig.dpi
150 if fmt == 'retina':
150 if fmt == 'retina':
151 dpi = dpi * 2
151 dpi = dpi * 2
152 fmt = 'png'
152 fmt = 'png'
153
153
154 # build keyword args
154 # build keyword args
155 kw = {
155 kw = {
156 "format":fmt,
156 "format":fmt,
157 "facecolor":fig.get_facecolor(),
157 "facecolor":fig.get_facecolor(),
158 "edgecolor":fig.get_edgecolor(),
158 "edgecolor":fig.get_edgecolor(),
159 "dpi":dpi,
159 "dpi":dpi,
160 "bbox_inches":bbox_inches,
160 "bbox_inches":bbox_inches,
161 }
161 }
162 # **kwargs get higher priority
162 # **kwargs get higher priority
163 kw.update(kwargs)
163 kw.update(kwargs)
164
164
165 bytes_io = BytesIO()
165 bytes_io = BytesIO()
166 if fig.canvas is None:
166 if fig.canvas is None:
167 from matplotlib.backend_bases import FigureCanvasBase
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
168 FigureCanvasBase(fig)
169
169
170 fig.canvas.print_figure(bytes_io, **kw)
170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
172 if fmt == 'svg':
173 data = data.decode('utf-8')
173 data = data.decode('utf-8')
174 elif base64:
174 elif base64:
175 data = b2a_base64(data, newline=False).decode("ascii")
175 data = b2a_base64(data, newline=False).decode("ascii")
176 return data
176 return data
177
177
178 def retina_figure(fig, base64=False, **kwargs):
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
179 """format a figure as a pixel-doubled (retina) PNG
180
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
181 If `base64` is True, return base64-encoded str instead of raw bytes
182 for binary-encoded image formats
182 for binary-encoded image formats
183
183
184 .. versionadded:: 7.29
184 .. versionadded:: 7.29
185 base64 argument
185 base64 argument
186 """
186 """
187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
189 # None when the figure is empty.
190 if pngdata is None:
190 if pngdata is None:
191 return
191 return
192 w, h = _pngxy(pngdata)
192 w, h = _pngxy(pngdata)
193 metadata = {"width": w//2, "height":h//2}
193 metadata = {"width": w//2, "height":h//2}
194 if base64:
194 if base64:
195 pngdata = b2a_base64(pngdata, newline=False).decode("ascii")
195 pngdata = b2a_base64(pngdata, newline=False).decode("ascii")
196 return pngdata, metadata
196 return pngdata, metadata
197
197
198
198
199 # We need a little factory function here to create the closure where
199 # We need a little factory function here to create the closure where
200 # safe_execfile can live.
200 # safe_execfile can live.
201 def mpl_runner(safe_execfile):
201 def mpl_runner(safe_execfile):
202 """Factory to return a matplotlib-enabled runner for %run.
202 """Factory to return a matplotlib-enabled runner for %run.
203
203
204 Parameters
204 Parameters
205 ----------
205 ----------
206 safe_execfile : function
206 safe_execfile : function
207 This must be a function with the same interface as the
207 This must be a function with the same interface as the
208 :meth:`safe_execfile` method of IPython.
208 :meth:`safe_execfile` method of IPython.
209
209
210 Returns
210 Returns
211 -------
211 -------
212 A function suitable for use as the ``runner`` argument of the %run magic
212 A function suitable for use as the ``runner`` argument of the %run magic
213 function.
213 function.
214 """
214 """
215
215
216 def mpl_execfile(fname,*where,**kw):
216 def mpl_execfile(fname,*where,**kw):
217 """matplotlib-aware wrapper around safe_execfile.
217 """matplotlib-aware wrapper around safe_execfile.
218
218
219 Its interface is identical to that of the :func:`execfile` builtin.
219 Its interface is identical to that of the :func:`execfile` builtin.
220
220
221 This is ultimately a call to execfile(), but wrapped in safeties to
221 This is ultimately a call to execfile(), but wrapped in safeties to
222 properly handle interactive rendering."""
222 properly handle interactive rendering."""
223
223
224 import matplotlib
224 import matplotlib
225 import matplotlib.pyplot as plt
225 import matplotlib.pyplot as plt
226
226
227 #print '*** Matplotlib runner ***' # dbg
227 #print '*** Matplotlib runner ***' # dbg
228 # turn off rendering until end of script
228 # turn off rendering until end of script
229 with matplotlib.rc_context({"interactive": False}):
229 with matplotlib.rc_context({"interactive": False}):
230 safe_execfile(fname, *where, **kw)
230 safe_execfile(fname, *where, **kw)
231
231
232 if matplotlib.is_interactive():
232 if matplotlib.is_interactive():
233 plt.show()
233 plt.show()
234
234
235 # make rendering call now, if the user tried to do it
235 # make rendering call now, if the user tried to do it
236 if plt.draw_if_interactive.called:
236 if plt.draw_if_interactive.called:
237 plt.draw()
237 plt.draw()
238 plt.draw_if_interactive.called = False
238 plt.draw_if_interactive.called = False
239
239
240 # re-draw everything that is stale
240 # re-draw everything that is stale
241 try:
241 try:
242 da = plt.draw_all
242 da = plt.draw_all
243 except AttributeError:
243 except AttributeError:
244 pass
244 pass
245 else:
245 else:
246 da()
246 da()
247
247
248 return mpl_execfile
248 return mpl_execfile
249
249
250
250
251 def _reshow_nbagg_figure(fig):
251 def _reshow_nbagg_figure(fig):
252 """reshow an nbagg figure"""
252 """reshow an nbagg figure"""
253 try:
253 try:
254 reshow = fig.canvas.manager.reshow
254 reshow = fig.canvas.manager.reshow
255 except AttributeError as e:
255 except AttributeError as e:
256 raise NotImplementedError() from e
256 raise NotImplementedError() from e
257 else:
257 else:
258 reshow()
258 reshow()
259
259
260
260
261 def select_figure_formats(shell, formats, **kwargs):
261 def select_figure_formats(shell, formats, **kwargs):
262 """Select figure formats for the inline backend.
262 """Select figure formats for the inline backend.
263
263
264 Parameters
264 Parameters
265 ----------
265 ----------
266 shell : InteractiveShell
266 shell : InteractiveShell
267 The main IPython instance.
267 The main IPython instance.
268 formats : str or set
268 formats : str or set
269 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
269 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
270 **kwargs : any
270 **kwargs : any
271 Extra keyword arguments to be passed to fig.canvas.print_figure.
271 Extra keyword arguments to be passed to fig.canvas.print_figure.
272 """
272 """
273 import matplotlib
273 import matplotlib
274 from matplotlib.figure import Figure
274 from matplotlib.figure import Figure
275
275
276 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
276 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
277 png_formatter = shell.display_formatter.formatters['image/png']
277 png_formatter = shell.display_formatter.formatters['image/png']
278 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
278 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
279 pdf_formatter = shell.display_formatter.formatters['application/pdf']
279 pdf_formatter = shell.display_formatter.formatters['application/pdf']
280
280
281 if isinstance(formats, str):
281 if isinstance(formats, str):
282 formats = {formats}
282 formats = {formats}
283 # cast in case of list / tuple
283 # cast in case of list / tuple
284 formats = set(formats)
284 formats = set(formats)
285
285
286 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
286 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
287 mplbackend = matplotlib.get_backend().lower()
287 mplbackend = matplotlib.get_backend().lower()
288 if mplbackend in ("nbagg", "ipympl", "widget", "module://ipympl.backend_nbagg"):
288 if mplbackend in ("nbagg", "ipympl", "widget", "module://ipympl.backend_nbagg"):
289 formatter = shell.display_formatter.ipython_display_formatter
289 formatter = shell.display_formatter.ipython_display_formatter
290 formatter.for_type(Figure, _reshow_nbagg_figure)
290 formatter.for_type(Figure, _reshow_nbagg_figure)
291
291
292 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
292 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
293 bad = formats.difference(supported)
293 bad = formats.difference(supported)
294 if bad:
294 if bad:
295 bs = "%s" % ','.join([repr(f) for f in bad])
295 bs = "%s" % ','.join([repr(f) for f in bad])
296 gs = "%s" % ','.join([repr(f) for f in supported])
296 gs = "%s" % ','.join([repr(f) for f in supported])
297 raise ValueError("supported formats are: %s not %s" % (gs, bs))
297 raise ValueError("supported formats are: %s not %s" % (gs, bs))
298
298
299 if "png" in formats:
299 if "png" in formats:
300 png_formatter.for_type(
300 png_formatter.for_type(
301 Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
301 Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
302 )
302 )
303 if "retina" in formats or "png2x" in formats:
303 if "retina" in formats or "png2x" in formats:
304 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
304 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
305 if "jpg" in formats or "jpeg" in formats:
305 if "jpg" in formats or "jpeg" in formats:
306 jpg_formatter.for_type(
306 jpg_formatter.for_type(
307 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
307 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
308 )
308 )
309 if "svg" in formats:
309 if "svg" in formats:
310 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
310 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
311 if "pdf" in formats:
311 if "pdf" in formats:
312 pdf_formatter.for_type(
312 pdf_formatter.for_type(
313 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
313 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
314 )
314 )
315
315
316 #-----------------------------------------------------------------------------
316 #-----------------------------------------------------------------------------
317 # Code for initializing matplotlib and importing pylab
317 # Code for initializing matplotlib and importing pylab
318 #-----------------------------------------------------------------------------
318 #-----------------------------------------------------------------------------
319
319
320
320
321 def find_gui_and_backend(gui=None, gui_select=None):
321 def find_gui_and_backend(gui=None, gui_select=None):
322 """Given a gui string return the gui and mpl backend.
322 """Given a gui string return the gui and mpl backend.
323
323
324 Parameters
324 Parameters
325 ----------
325 ----------
326 gui : str
326 gui : str
327 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
327 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
328 gui_select : str
328 gui_select : str
329 Can be one of ('tk','gtk','wx','qt','qt4','inline').
329 Can be one of ('tk','gtk','wx','qt','qt4','inline').
330 This is any gui already selected by the shell.
330 This is any gui already selected by the shell.
331
331
332 Returns
332 Returns
333 -------
333 -------
334 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
334 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
335 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
335 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
336 """
336 """
337
337
338 import matplotlib
338 import matplotlib
339
339
340 if _matplotlib_manages_backends():
340 if _matplotlib_manages_backends():
341 backend_registry = matplotlib.backends.registry.backend_registry
341 backend_registry = matplotlib.backends.registry.backend_registry
342
342
343 # gui argument may be a gui event loop or may be a backend name.
343 # gui argument may be a gui event loop or may be a backend name.
344 if gui in ("auto", None):
344 if gui in ("auto", None):
345 backend = matplotlib.rcParamsOrig["backend"]
345 backend = matplotlib.rcParamsOrig["backend"]
346 backend, gui = backend_registry.resolve_backend(backend)
346 backend, gui = backend_registry.resolve_backend(backend)
347 else:
347 else:
348 gui = _convert_gui_to_matplotlib(gui)
348 backend, gui = backend_registry.resolve_gui_or_backend(gui)
349 backend, gui = backend_registry.resolve_gui_or_backend(gui)
349
350
351 gui = _convert_gui_from_matplotlib(gui)
350 return gui, backend
352 return gui, backend
351
353
352 # Fallback to previous behaviour (Matplotlib < 3.9)
354 # Fallback to previous behaviour (Matplotlib < 3.9)
353 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
355 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
354 has_unified_qt_backend = mpl_version_info >= (3, 5)
356 has_unified_qt_backend = mpl_version_info >= (3, 5)
355
357
356 from IPython.core.pylabtools import backends
358 from IPython.core.pylabtools import backends
357
359
358 backends_ = dict(backends)
360 backends_ = dict(backends)
359 if not has_unified_qt_backend:
361 if not has_unified_qt_backend:
360 backends_["qt"] = "qt5agg"
362 backends_["qt"] = "qt5agg"
361
363
362 if gui and gui != 'auto':
364 if gui and gui != 'auto':
363 # select backend based on requested gui
365 # select backend based on requested gui
364 backend = backends_[gui]
366 backend = backends_[gui]
365 if gui == 'agg':
367 if gui == 'agg':
366 gui = None
368 gui = None
367 else:
369 else:
368 # We need to read the backend from the original data structure, *not*
370 # We need to read the backend from the original data structure, *not*
369 # from mpl.rcParams, since a prior invocation of %matplotlib may have
371 # from mpl.rcParams, since a prior invocation of %matplotlib may have
370 # overwritten that.
372 # overwritten that.
371 # WARNING: this assumes matplotlib 1.1 or newer!!
373 # WARNING: this assumes matplotlib 1.1 or newer!!
372 backend = matplotlib.rcParamsOrig['backend']
374 backend = matplotlib.rcParamsOrig['backend']
373 # In this case, we need to find what the appropriate gui selection call
375 # In this case, we need to find what the appropriate gui selection call
374 # should be for IPython, so we can activate inputhook accordingly
376 # should be for IPython, so we can activate inputhook accordingly
375 from IPython.core.pylabtools import backend2gui
377 from IPython.core.pylabtools import backend2gui
376 gui = backend2gui.get(backend, None)
378 gui = backend2gui.get(backend, None)
377
379
378 # If we have already had a gui active, we need it and inline are the
380 # If we have already had a gui active, we need it and inline are the
379 # ones allowed.
381 # ones allowed.
380 if gui_select and gui != gui_select:
382 if gui_select and gui != gui_select:
381 gui = gui_select
383 gui = gui_select
382 backend = backends_[gui]
384 backend = backends_[gui]
383
385
384 # Matplotlib before _matplotlib_manages_backends() can return "inline" for
386 # Matplotlib before _matplotlib_manages_backends() can return "inline" for
385 # no gui event loop rather than the None that IPython >= 8.24.0 expects.
387 # no gui event loop rather than the None that IPython >= 8.24.0 expects.
386 if gui == "inline":
388 if gui == "inline":
387 gui = None
389 gui = None
388
390
389 return gui, backend
391 return gui, backend
390
392
391
393
392 def activate_matplotlib(backend):
394 def activate_matplotlib(backend):
393 """Activate the given backend and set interactive to True."""
395 """Activate the given backend and set interactive to True."""
394
396
395 import matplotlib
397 import matplotlib
396 matplotlib.interactive(True)
398 matplotlib.interactive(True)
397
399
398 # Matplotlib had a bug where even switch_backend could not force
400 # Matplotlib had a bug where even switch_backend could not force
399 # the rcParam to update. This needs to be set *before* the module
401 # the rcParam to update. This needs to be set *before* the module
400 # magic of switch_backend().
402 # magic of switch_backend().
401 matplotlib.rcParams['backend'] = backend
403 matplotlib.rcParams['backend'] = backend
402
404
403 # Due to circular imports, pyplot may be only partially initialised
405 # Due to circular imports, pyplot may be only partially initialised
404 # when this function runs.
406 # when this function runs.
405 # So avoid needing matplotlib attribute-lookup to access pyplot.
407 # So avoid needing matplotlib attribute-lookup to access pyplot.
406 from matplotlib import pyplot as plt
408 from matplotlib import pyplot as plt
407
409
408 plt.switch_backend(backend)
410 plt.switch_backend(backend)
409
411
410 plt.show._needmain = False
412 plt.show._needmain = False
411 # We need to detect at runtime whether show() is called by the user.
413 # We need to detect at runtime whether show() is called by the user.
412 # For this, we wrap it into a decorator which adds a 'called' flag.
414 # For this, we wrap it into a decorator which adds a 'called' flag.
413 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
415 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
414
416
415
417
416 def import_pylab(user_ns, import_all=True):
418 def import_pylab(user_ns, import_all=True):
417 """Populate the namespace with pylab-related values.
419 """Populate the namespace with pylab-related values.
418
420
419 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
421 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
420
422
421 Also imports a few names from IPython (figsize, display, getfigs)
423 Also imports a few names from IPython (figsize, display, getfigs)
422
424
423 """
425 """
424
426
425 # Import numpy as np/pyplot as plt are conventions we're trying to
427 # Import numpy as np/pyplot as plt are conventions we're trying to
426 # somewhat standardize on. Making them available to users by default
428 # somewhat standardize on. Making them available to users by default
427 # will greatly help this.
429 # will greatly help this.
428 s = ("import numpy\n"
430 s = ("import numpy\n"
429 "import matplotlib\n"
431 "import matplotlib\n"
430 "from matplotlib import pylab, mlab, pyplot\n"
432 "from matplotlib import pylab, mlab, pyplot\n"
431 "np = numpy\n"
433 "np = numpy\n"
432 "plt = pyplot\n"
434 "plt = pyplot\n"
433 )
435 )
434 exec(s, user_ns)
436 exec(s, user_ns)
435
437
436 if import_all:
438 if import_all:
437 s = ("from matplotlib.pylab import *\n"
439 s = ("from matplotlib.pylab import *\n"
438 "from numpy import *\n")
440 "from numpy import *\n")
439 exec(s, user_ns)
441 exec(s, user_ns)
440
442
441 # IPython symbols to add
443 # IPython symbols to add
442 user_ns['figsize'] = figsize
444 user_ns['figsize'] = figsize
443 from IPython.display import display
445 from IPython.display import display
444 # Add display and getfigs to the user's namespace
446 # Add display and getfigs to the user's namespace
445 user_ns['display'] = display
447 user_ns['display'] = display
446 user_ns['getfigs'] = getfigs
448 user_ns['getfigs'] = getfigs
447
449
448
450
449 def configure_inline_support(shell, backend):
451 def configure_inline_support(shell, backend):
450 """
452 """
451 .. deprecated:: 7.23
453 .. deprecated:: 7.23
452
454
453 use `matplotlib_inline.backend_inline.configure_inline_support()`
455 use `matplotlib_inline.backend_inline.configure_inline_support()`
454
456
455 Configure an IPython shell object for matplotlib use.
457 Configure an IPython shell object for matplotlib use.
456
458
457 Parameters
459 Parameters
458 ----------
460 ----------
459 shell : InteractiveShell instance
461 shell : InteractiveShell instance
460 backend : matplotlib backend
462 backend : matplotlib backend
461 """
463 """
462 warnings.warn(
464 warnings.warn(
463 "`configure_inline_support` is deprecated since IPython 7.23, directly "
465 "`configure_inline_support` is deprecated since IPython 7.23, directly "
464 "use `matplotlib_inline.backend_inline.configure_inline_support()`",
466 "use `matplotlib_inline.backend_inline.configure_inline_support()`",
465 DeprecationWarning,
467 DeprecationWarning,
466 stacklevel=2,
468 stacklevel=2,
467 )
469 )
468
470
469 from matplotlib_inline.backend_inline import (
471 from matplotlib_inline.backend_inline import (
470 configure_inline_support as configure_inline_support_orig,
472 configure_inline_support as configure_inline_support_orig,
471 )
473 )
472
474
473 configure_inline_support_orig(shell, backend)
475 configure_inline_support_orig(shell, backend)
474
476
475
477
476 # Determine if Matplotlib manages backends only if needed, and cache result.
478 # Determine if Matplotlib manages backends only if needed, and cache result.
477 # Do not read this directly, instead use _matplotlib_manages_backends().
479 # Do not read this directly, instead use _matplotlib_manages_backends().
478 _matplotlib_manages_backends_value: bool | None = None
480 _matplotlib_manages_backends_value: bool | None = None
479
481
480
482
481 def _matplotlib_manages_backends() -> bool:
483 def _matplotlib_manages_backends() -> bool:
482 """Return True if Matplotlib manages backends, False otherwise.
484 """Return True if Matplotlib manages backends, False otherwise.
483
485
484 If it returns True, the caller can be sure that
486 If it returns True, the caller can be sure that
485 matplotlib.backends.registry.backend_registry is available along with
487 matplotlib.backends.registry.backend_registry is available along with
486 member functions resolve_gui_or_backend, resolve_backend, list_all, and
488 member functions resolve_gui_or_backend, resolve_backend, list_all, and
487 list_gui_frameworks.
489 list_gui_frameworks.
488 """
490 """
489 global _matplotlib_manages_backends_value
491 global _matplotlib_manages_backends_value
490 if _matplotlib_manages_backends_value is None:
492 if _matplotlib_manages_backends_value is None:
491 try:
493 try:
492 from matplotlib.backends.registry import backend_registry
494 from matplotlib.backends.registry import backend_registry
493
495
494 _matplotlib_manages_backends_value = hasattr(
496 _matplotlib_manages_backends_value = hasattr(
495 backend_registry, "resolve_gui_or_backend"
497 backend_registry, "resolve_gui_or_backend"
496 )
498 )
497 except ImportError:
499 except ImportError:
498 _matplotlib_manages_backends_value = False
500 _matplotlib_manages_backends_value = False
499
501
500 return _matplotlib_manages_backends_value
502 return _matplotlib_manages_backends_value
501
503
502
504
503 def _list_matplotlib_backends_and_gui_loops() -> list[str]:
505 def _list_matplotlib_backends_and_gui_loops() -> list[str]:
504 """Return list of all Matplotlib backends and GUI event loops.
506 """Return list of all Matplotlib backends and GUI event loops.
505
507
506 This is the list returned by
508 This is the list returned by
507 %matplotlib --list
509 %matplotlib --list
508 """
510 """
509 if _matplotlib_manages_backends():
511 if _matplotlib_manages_backends():
510 from matplotlib.backends.registry import backend_registry
512 from matplotlib.backends.registry import backend_registry
511
513
512 ret = backend_registry.list_all() + backend_registry.list_gui_frameworks()
514 ret = backend_registry.list_all() + [
515 _convert_gui_from_matplotlib(gui)
516 for gui in backend_registry.list_gui_frameworks()
517 ]
513 else:
518 else:
514 from IPython.core import pylabtools
519 from IPython.core import pylabtools
515
520
516 ret = list(pylabtools.backends.keys())
521 ret = list(pylabtools.backends.keys())
517
522
518 return sorted(["auto"] + ret)
523 return sorted(["auto"] + ret)
524
525
526 # Matplotlib and IPython do not always use the same gui framework name.
527 # Always use the approprate one of these conversion functions when passing a
528 # gui framework name to/from Matplotlib.
529 def _convert_gui_to_matplotlib(gui: str | None) -> str | None:
530 if gui and gui.lower() == "osx":
531 return "macosx"
532 return gui
533
534
535 def _convert_gui_from_matplotlib(gui: str | None) -> str | None:
536 if gui and gui.lower() == "macosx":
537 return "osx"
538 return gui
@@ -1,354 +1,352 b''
1 """Tests for pylab tools module.
1 """Tests for pylab tools module.
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from binascii import a2b_base64
8 from binascii import a2b_base64
9 from io import BytesIO
9 from io import BytesIO
10
10
11 import pytest
11 import pytest
12
12
13 matplotlib = pytest.importorskip("matplotlib")
13 matplotlib = pytest.importorskip("matplotlib")
14 matplotlib.use('Agg')
14 matplotlib.use('Agg')
15 from matplotlib.figure import Figure
15 from matplotlib.figure import Figure
16
16
17 from matplotlib import pyplot as plt
17 from matplotlib import pyplot as plt
18 from matplotlib_inline import backend_inline
18 from matplotlib_inline import backend_inline
19 import numpy as np
19 import numpy as np
20
20
21 from IPython.core.getipython import get_ipython
21 from IPython.core.getipython import get_ipython
22 from IPython.core.interactiveshell import InteractiveShell
22 from IPython.core.interactiveshell import InteractiveShell
23 from IPython.core.display import _PNG, _JPEG
23 from IPython.core.display import _PNG, _JPEG
24 from .. import pylabtools as pt
24 from .. import pylabtools as pt
25
25
26 from IPython.testing import decorators as dec
26 from IPython.testing import decorators as dec
27
27
28
28
29 def test_figure_to_svg():
29 def test_figure_to_svg():
30 # simple empty-figure test
30 # simple empty-figure test
31 fig = plt.figure()
31 fig = plt.figure()
32 assert pt.print_figure(fig, "svg") is None
32 assert pt.print_figure(fig, "svg") is None
33
33
34 plt.close('all')
34 plt.close('all')
35
35
36 # simple check for at least svg-looking output
36 # simple check for at least svg-looking output
37 fig = plt.figure()
37 fig = plt.figure()
38 ax = fig.add_subplot(1,1,1)
38 ax = fig.add_subplot(1,1,1)
39 ax.plot([1,2,3])
39 ax.plot([1,2,3])
40 plt.draw()
40 plt.draw()
41 svg = pt.print_figure(fig, "svg")[:100].lower()
41 svg = pt.print_figure(fig, "svg")[:100].lower()
42 assert "doctype svg" in svg
42 assert "doctype svg" in svg
43
43
44
44
45 def _check_pil_jpeg_bytes():
45 def _check_pil_jpeg_bytes():
46 """Skip if PIL can't write JPEGs to BytesIO objects"""
46 """Skip if PIL can't write JPEGs to BytesIO objects"""
47 # PIL's JPEG plugin can't write to BytesIO objects
47 # PIL's JPEG plugin can't write to BytesIO objects
48 # Pillow fixes this
48 # Pillow fixes this
49 from PIL import Image
49 from PIL import Image
50 buf = BytesIO()
50 buf = BytesIO()
51 img = Image.new("RGB", (4,4))
51 img = Image.new("RGB", (4,4))
52 try:
52 try:
53 img.save(buf, 'jpeg')
53 img.save(buf, 'jpeg')
54 except Exception as e:
54 except Exception as e:
55 ename = e.__class__.__name__
55 ename = e.__class__.__name__
56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
57
57
58 @dec.skip_without("PIL.Image")
58 @dec.skip_without("PIL.Image")
59 def test_figure_to_jpeg():
59 def test_figure_to_jpeg():
60 _check_pil_jpeg_bytes()
60 _check_pil_jpeg_bytes()
61 # simple check for at least jpeg-looking output
61 # simple check for at least jpeg-looking output
62 fig = plt.figure()
62 fig = plt.figure()
63 ax = fig.add_subplot(1,1,1)
63 ax = fig.add_subplot(1,1,1)
64 ax.plot([1,2,3])
64 ax.plot([1,2,3])
65 plt.draw()
65 plt.draw()
66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
67 assert jpeg.startswith(_JPEG)
67 assert jpeg.startswith(_JPEG)
68
68
69 def test_retina_figure():
69 def test_retina_figure():
70 # simple empty-figure test
70 # simple empty-figure test
71 fig = plt.figure()
71 fig = plt.figure()
72 assert pt.retina_figure(fig) == None
72 assert pt.retina_figure(fig) == None
73 plt.close('all')
73 plt.close('all')
74
74
75 fig = plt.figure()
75 fig = plt.figure()
76 ax = fig.add_subplot(1,1,1)
76 ax = fig.add_subplot(1,1,1)
77 ax.plot([1,2,3])
77 ax.plot([1,2,3])
78 plt.draw()
78 plt.draw()
79 png, md = pt.retina_figure(fig)
79 png, md = pt.retina_figure(fig)
80 assert png.startswith(_PNG)
80 assert png.startswith(_PNG)
81 assert "width" in md
81 assert "width" in md
82 assert "height" in md
82 assert "height" in md
83
83
84
84
85 _fmt_mime_map = {
85 _fmt_mime_map = {
86 'png': 'image/png',
86 'png': 'image/png',
87 'jpeg': 'image/jpeg',
87 'jpeg': 'image/jpeg',
88 'pdf': 'application/pdf',
88 'pdf': 'application/pdf',
89 'retina': 'image/png',
89 'retina': 'image/png',
90 'svg': 'image/svg+xml',
90 'svg': 'image/svg+xml',
91 }
91 }
92
92
93 def test_select_figure_formats_str():
93 def test_select_figure_formats_str():
94 ip = get_ipython()
94 ip = get_ipython()
95 for fmt, active_mime in _fmt_mime_map.items():
95 for fmt, active_mime in _fmt_mime_map.items():
96 pt.select_figure_formats(ip, fmt)
96 pt.select_figure_formats(ip, fmt)
97 for mime, f in ip.display_formatter.formatters.items():
97 for mime, f in ip.display_formatter.formatters.items():
98 if mime == active_mime:
98 if mime == active_mime:
99 assert Figure in f
99 assert Figure in f
100 else:
100 else:
101 assert Figure not in f
101 assert Figure not in f
102
102
103 def test_select_figure_formats_kwargs():
103 def test_select_figure_formats_kwargs():
104 ip = get_ipython()
104 ip = get_ipython()
105 kwargs = dict(bbox_inches="tight")
105 kwargs = dict(bbox_inches="tight")
106 pt.select_figure_formats(ip, "png", **kwargs)
106 pt.select_figure_formats(ip, "png", **kwargs)
107 formatter = ip.display_formatter.formatters["image/png"]
107 formatter = ip.display_formatter.formatters["image/png"]
108 f = formatter.lookup_by_type(Figure)
108 f = formatter.lookup_by_type(Figure)
109 cell = f.keywords
109 cell = f.keywords
110 expected = kwargs
110 expected = kwargs
111 expected["base64"] = True
111 expected["base64"] = True
112 expected["fmt"] = "png"
112 expected["fmt"] = "png"
113 assert cell == expected
113 assert cell == expected
114
114
115 # check that the formatter doesn't raise
115 # check that the formatter doesn't raise
116 fig = plt.figure()
116 fig = plt.figure()
117 ax = fig.add_subplot(1,1,1)
117 ax = fig.add_subplot(1,1,1)
118 ax.plot([1,2,3])
118 ax.plot([1,2,3])
119 plt.draw()
119 plt.draw()
120 formatter.enabled = True
120 formatter.enabled = True
121 png = formatter(fig)
121 png = formatter(fig)
122 assert isinstance(png, str)
122 assert isinstance(png, str)
123 png_bytes = a2b_base64(png)
123 png_bytes = a2b_base64(png)
124 assert png_bytes.startswith(_PNG)
124 assert png_bytes.startswith(_PNG)
125
125
126 def test_select_figure_formats_set():
126 def test_select_figure_formats_set():
127 ip = get_ipython()
127 ip = get_ipython()
128 for fmts in [
128 for fmts in [
129 {'png', 'svg'},
129 {'png', 'svg'},
130 ['png'],
130 ['png'],
131 ('jpeg', 'pdf', 'retina'),
131 ('jpeg', 'pdf', 'retina'),
132 {'svg'},
132 {'svg'},
133 ]:
133 ]:
134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
135 pt.select_figure_formats(ip, fmts)
135 pt.select_figure_formats(ip, fmts)
136 for mime, f in ip.display_formatter.formatters.items():
136 for mime, f in ip.display_formatter.formatters.items():
137 if mime in active_mimes:
137 if mime in active_mimes:
138 assert Figure in f
138 assert Figure in f
139 else:
139 else:
140 assert Figure not in f
140 assert Figure not in f
141
141
142 def test_select_figure_formats_bad():
142 def test_select_figure_formats_bad():
143 ip = get_ipython()
143 ip = get_ipython()
144 with pytest.raises(ValueError):
144 with pytest.raises(ValueError):
145 pt.select_figure_formats(ip, 'foo')
145 pt.select_figure_formats(ip, 'foo')
146 with pytest.raises(ValueError):
146 with pytest.raises(ValueError):
147 pt.select_figure_formats(ip, {'png', 'foo'})
147 pt.select_figure_formats(ip, {'png', 'foo'})
148 with pytest.raises(ValueError):
148 with pytest.raises(ValueError):
149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
150
150
151 def test_import_pylab():
151 def test_import_pylab():
152 ns = {}
152 ns = {}
153 pt.import_pylab(ns, import_all=False)
153 pt.import_pylab(ns, import_all=False)
154 assert "plt" in ns
154 assert "plt" in ns
155 assert ns["np"] == np
155 assert ns["np"] == np
156
156
157
157
158 class TestPylabSwitch(object):
158 class TestPylabSwitch(object):
159 class Shell(InteractiveShell):
159 class Shell(InteractiveShell):
160 def init_history(self):
160 def init_history(self):
161 """Sets up the command history, and starts regular autosaves."""
161 """Sets up the command history, and starts regular autosaves."""
162 self.config.HistoryManager.hist_file = ":memory:"
162 self.config.HistoryManager.hist_file = ":memory:"
163 super().init_history()
163 super().init_history()
164
164
165 def enable_gui(self, gui):
165 def enable_gui(self, gui):
166 pass
166 pass
167
167
168 def setup_method(self):
168 def setup_method(self):
169 import matplotlib
169 import matplotlib
170 def act_mpl(backend):
170 def act_mpl(backend):
171 matplotlib.rcParams['backend'] = backend
171 matplotlib.rcParams['backend'] = backend
172
172
173 # Save rcParams since they get modified
173 # Save rcParams since they get modified
174 self._saved_rcParams = matplotlib.rcParams
174 self._saved_rcParams = matplotlib.rcParams
175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
176 matplotlib.rcParams = dict(backend="QtAgg")
176 matplotlib.rcParams = dict(backend="QtAgg")
177 matplotlib.rcParamsOrig = dict(backend="QtAgg")
177 matplotlib.rcParamsOrig = dict(backend="QtAgg")
178
178
179 # Mock out functions
179 # Mock out functions
180 self._save_am = pt.activate_matplotlib
180 self._save_am = pt.activate_matplotlib
181 pt.activate_matplotlib = act_mpl
181 pt.activate_matplotlib = act_mpl
182 self._save_ip = pt.import_pylab
182 self._save_ip = pt.import_pylab
183 pt.import_pylab = lambda *a,**kw:None
183 pt.import_pylab = lambda *a,**kw:None
184 self._save_cis = backend_inline.configure_inline_support
184 self._save_cis = backend_inline.configure_inline_support
185 backend_inline.configure_inline_support = lambda *a, **kw: None
185 backend_inline.configure_inline_support = lambda *a, **kw: None
186
186
187 def teardown_method(self):
187 def teardown_method(self):
188 pt.activate_matplotlib = self._save_am
188 pt.activate_matplotlib = self._save_am
189 pt.import_pylab = self._save_ip
189 pt.import_pylab = self._save_ip
190 backend_inline.configure_inline_support = self._save_cis
190 backend_inline.configure_inline_support = self._save_cis
191 import matplotlib
191 import matplotlib
192 matplotlib.rcParams = self._saved_rcParams
192 matplotlib.rcParams = self._saved_rcParams
193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
194
194
195 def test_qt(self):
195 def test_qt(self):
196 s = self.Shell()
196 s = self.Shell()
197 gui, backend = s.enable_matplotlib(None)
197 gui, backend = s.enable_matplotlib(None)
198 assert gui == "qt"
198 assert gui == "qt"
199 assert s.pylab_gui_select == "qt"
199 assert s.pylab_gui_select == "qt"
200
200
201 gui, backend = s.enable_matplotlib("inline")
201 gui, backend = s.enable_matplotlib("inline")
202 assert gui is None
202 assert gui is None
203 assert s.pylab_gui_select == "qt"
203 assert s.pylab_gui_select == "qt"
204
204
205 gui, backend = s.enable_matplotlib("qt")
205 gui, backend = s.enable_matplotlib("qt")
206 assert gui == "qt"
206 assert gui == "qt"
207 assert s.pylab_gui_select == "qt"
207 assert s.pylab_gui_select == "qt"
208
208
209 gui, backend = s.enable_matplotlib("inline")
209 gui, backend = s.enable_matplotlib("inline")
210 assert gui is None
210 assert gui is None
211 assert s.pylab_gui_select == "qt"
211 assert s.pylab_gui_select == "qt"
212
212
213 gui, backend = s.enable_matplotlib()
213 gui, backend = s.enable_matplotlib()
214 assert gui == "qt"
214 assert gui == "qt"
215 assert s.pylab_gui_select == "qt"
215 assert s.pylab_gui_select == "qt"
216
216
217 def test_inline(self):
217 def test_inline(self):
218 s = self.Shell()
218 s = self.Shell()
219 gui, backend = s.enable_matplotlib("inline")
219 gui, backend = s.enable_matplotlib("inline")
220 assert gui is None
220 assert gui is None
221 assert s.pylab_gui_select == None
221 assert s.pylab_gui_select == None
222
222
223 gui, backend = s.enable_matplotlib("inline")
223 gui, backend = s.enable_matplotlib("inline")
224 assert gui is None
224 assert gui is None
225 assert s.pylab_gui_select == None
225 assert s.pylab_gui_select == None
226
226
227 gui, backend = s.enable_matplotlib("qt")
227 gui, backend = s.enable_matplotlib("qt")
228 assert gui == "qt"
228 assert gui == "qt"
229 assert s.pylab_gui_select == "qt"
229 assert s.pylab_gui_select == "qt"
230
230
231 def test_inline_twice(self):
231 def test_inline_twice(self):
232 "Using '%matplotlib inline' twice should not reset formatters"
232 "Using '%matplotlib inline' twice should not reset formatters"
233
233
234 ip = self.Shell()
234 ip = self.Shell()
235 gui, backend = ip.enable_matplotlib("inline")
235 gui, backend = ip.enable_matplotlib("inline")
236 assert gui is None
236 assert gui is None
237
237
238 fmts = {'png'}
238 fmts = {'png'}
239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
240 pt.select_figure_formats(ip, fmts)
240 pt.select_figure_formats(ip, fmts)
241
241
242 gui, backend = ip.enable_matplotlib("inline")
242 gui, backend = ip.enable_matplotlib("inline")
243 assert gui is None
243 assert gui is None
244
244
245 for mime, f in ip.display_formatter.formatters.items():
245 for mime, f in ip.display_formatter.formatters.items():
246 if mime in active_mimes:
246 if mime in active_mimes:
247 assert Figure in f
247 assert Figure in f
248 else:
248 else:
249 assert Figure not in f
249 assert Figure not in f
250
250
251 def test_qt_gtk(self):
251 def test_qt_gtk(self):
252 s = self.Shell()
252 s = self.Shell()
253 gui, backend = s.enable_matplotlib("qt")
253 gui, backend = s.enable_matplotlib("qt")
254 assert gui == "qt"
254 assert gui == "qt"
255 assert s.pylab_gui_select == "qt"
255 assert s.pylab_gui_select == "qt"
256
256
257 gui, backend = s.enable_matplotlib("gtk3")
257 gui, backend = s.enable_matplotlib("gtk3")
258 assert gui == "qt"
258 assert gui == "qt"
259 assert s.pylab_gui_select == "qt"
259 assert s.pylab_gui_select == "qt"
260
260
261
261
262 def test_no_gui_backends():
262 def test_no_gui_backends():
263 for k in ['agg', 'svg', 'pdf', 'ps']:
263 for k in ['agg', 'svg', 'pdf', 'ps']:
264 assert k not in pt.backend2gui
264 assert k not in pt.backend2gui
265
265
266
266
267 def test_figure_no_canvas():
267 def test_figure_no_canvas():
268 fig = Figure()
268 fig = Figure()
269 fig.canvas = None
269 fig.canvas = None
270 pt.print_figure(fig)
270 pt.print_figure(fig)
271
271
272
272
273 @pytest.mark.parametrize(
273 @pytest.mark.parametrize(
274 "name, expected_gui, expected_backend",
274 "name, expected_gui, expected_backend",
275 [
275 [
276 # name is gui
276 # name is gui
277 ("gtk3", "gtk3", "gtk3agg"),
277 ("gtk3", "gtk3", "gtk3agg"),
278 ("gtk4", "gtk4", "gtk4agg"),
278 ("gtk4", "gtk4", "gtk4agg"),
279 ("headless", "headless", "agg"),
279 ("headless", None, "agg"),
280 ("osx", "osx", "macosx"),
280 ("osx", "osx", "macosx"),
281 ("qt", "qt", "qtagg"),
281 ("qt", "qt", "qtagg"),
282 ("qt5", "qt5", "qt5agg"),
282 ("qt5", "qt5", "qt5agg"),
283 ("qt6", "qt6", "qt6agg"),
283 ("qt6", "qt6", "qtagg"),
284 ("tk", "tk", "tkagg"),
284 ("tk", "tk", "tkagg"),
285 ("wx", "wx", "wxagg"),
285 ("wx", "wx", "wxagg"),
286 # name is backend
286 # name is backend
287 ("agg", None, "agg"),
287 ("agg", None, "agg"),
288 ("cairo", None, "cairo"),
288 ("cairo", None, "cairo"),
289 ("pdf", None, "pdf"),
289 ("pdf", None, "pdf"),
290 ("ps", None, "ps"),
290 ("ps", None, "ps"),
291 ("svg", None, "svg"),
291 ("svg", None, "svg"),
292 ("template", None, "template"),
292 ("template", None, "template"),
293 ("gtk3agg", "gtk3", "gtk3agg"),
293 ("gtk3agg", "gtk3", "gtk3agg"),
294 ("gtk3cairo", "gtk3", "gtk3cairo"),
294 ("gtk3cairo", "gtk3", "gtk3cairo"),
295 ("gtk4agg", "gtk4", "gtk4agg"),
295 ("gtk4agg", "gtk4", "gtk4agg"),
296 ("gtk4cairo", "gtk4", "gtk4cairo"),
296 ("gtk4cairo", "gtk4", "gtk4cairo"),
297 ("macosx", "osx", "macosx"),
297 ("macosx", "osx", "macosx"),
298 ("nbagg", "nbagg", "nbagg"),
298 ("nbagg", "nbagg", "nbagg"),
299 ("notebook", "nbagg", "notebook"),
299 ("notebook", "nbagg", "notebook"),
300 ("qtagg", "qt", "qtagg"),
300 ("qtagg", "qt", "qtagg"),
301 ("qtcairo", "qt", "qtcairo"),
301 ("qtcairo", "qt", "qtcairo"),
302 ("qt5agg", "qt5", "qt5agg"),
302 ("qt5agg", "qt5", "qt5agg"),
303 ("qt5cairo", "qt5", "qt5cairo"),
303 ("qt5cairo", "qt5", "qt5cairo"),
304 ("qt6agg", "qt", "qt6agg"),
305 ("qt6cairo", "qt", "qt6cairo"),
306 ("tkagg", "tk", "tkagg"),
304 ("tkagg", "tk", "tkagg"),
307 ("tkcairo", "tk", "tkcairo"),
305 ("tkcairo", "tk", "tkcairo"),
308 ("webagg", "webagg", "webagg"),
306 ("webagg", "webagg", "webagg"),
309 ("wxagg", "wx", "wxagg"),
307 ("wxagg", "wx", "wxagg"),
310 ("wxcairo", "wx", "wxcairo"),
308 ("wxcairo", "wx", "wxcairo"),
311 ],
309 ],
312 )
310 )
313 def test_backend_builtin(name, expected_gui, expected_backend):
311 def test_backend_builtin(name, expected_gui, expected_backend):
314 # Test correct identification of Matplotlib built-in backends without importing and using them,
312 # Test correct identification of Matplotlib built-in backends without importing and using them,
315 # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are
313 # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are
316 # installed.
314 # installed.
317
315
318 mpl_manages_backends = pt._matplotlib_manages_backends()
316 mpl_manages_backends = pt._matplotlib_manages_backends()
319 if not mpl_manages_backends:
317 if not mpl_manages_backends:
320 # Backends not supported before _matplotlib_manages_backends or supported
318 # Backends not supported before _matplotlib_manages_backends or supported
321 # but with different expected_gui or expected_backend.
319 # but with different expected_gui or expected_backend.
322 if (
320 if (
323 name.endswith("agg")
321 name.endswith("agg")
324 or name.endswith("cairo")
322 or name.endswith("cairo")
325 or name in ("headless", "macosx", "pdf", "ps", "svg", "template")
323 or name in ("headless", "macosx", "pdf", "ps", "svg", "template")
326 ):
324 ):
327 pytest.skip()
325 pytest.skip()
328 elif name == "qt6":
326 elif name == "qt6":
329 expected_backend = "qtagg"
327 expected_backend = "qtagg"
330 elif name == "notebook":
328 elif name == "notebook":
331 expected_backend, expected_gui = expected_gui, expected_backend
329 expected_backend, expected_gui = expected_gui, expected_backend
332
330
333 gui, backend = pt.find_gui_and_backend(name)
331 gui, backend = pt.find_gui_and_backend(name)
334 if not mpl_manages_backends:
332 if not mpl_manages_backends:
335 gui = gui.lower() if gui else None
333 gui = gui.lower() if gui else None
336 backend = backend.lower() if backend else None
334 backend = backend.lower() if backend else None
337 assert gui == expected_gui
335 assert gui == expected_gui
338 assert backend == expected_backend
336 assert backend == expected_backend
339
337
340
338
341 def test_backend_entry_point():
339 def test_backend_entry_point():
342 gui, backend = pt.find_gui_and_backend("inline")
340 gui, backend = pt.find_gui_and_backend("inline")
343 assert gui is None
341 assert gui is None
344 expected_backend = (
342 expected_backend = (
345 "inline"
343 "inline"
346 if pt._matplotlib_manages_backends()
344 if pt._matplotlib_manages_backends()
347 else "module://matplotlib_inline.backend_inline"
345 else "module://matplotlib_inline.backend_inline"
348 )
346 )
349 assert backend == expected_backend
347 assert backend == expected_backend
350
348
351
349
352 def test_backend_unknown():
350 def test_backend_unknown():
353 with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError):
351 with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError):
354 pt.find_gui_and_backend("name-does-not-exist")
352 pt.find_gui_and_backend("name-does-not-exist")
General Comments 0
You need to be logged in to leave comments. Login now