##// END OF EJS Templates
_matplotlib_manages_backends uses hasattr not version check
Ian Thomas -
Show More
@@ -1,479 +1,498 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 return globals()[f"_deprecated_{name}"]
81 return globals()[f"_deprecated_{name}"]
82 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
82 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
83
83
84
84
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86 # Matplotlib utilities
86 # Matplotlib utilities
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88
88
89
89
90 def getfigs(*fig_nums):
90 def getfigs(*fig_nums):
91 """Get a list of matplotlib figures by figure numbers.
91 """Get a list of matplotlib figures by figure numbers.
92
92
93 If no arguments are given, all available figures are returned. If the
93 If no arguments are given, all available figures are returned. If the
94 argument list contains references to invalid figures, a warning is printed
94 argument list contains references to invalid figures, a warning is printed
95 but the function continues pasting further figures.
95 but the function continues pasting further figures.
96
96
97 Parameters
97 Parameters
98 ----------
98 ----------
99 figs : tuple
99 figs : tuple
100 A tuple of ints giving the figure numbers of the figures to return.
100 A tuple of ints giving the figure numbers of the figures to return.
101 """
101 """
102 from matplotlib._pylab_helpers import Gcf
102 from matplotlib._pylab_helpers import Gcf
103 if not fig_nums:
103 if not fig_nums:
104 fig_managers = Gcf.get_all_fig_managers()
104 fig_managers = Gcf.get_all_fig_managers()
105 return [fm.canvas.figure for fm in fig_managers]
105 return [fm.canvas.figure for fm in fig_managers]
106 else:
106 else:
107 figs = []
107 figs = []
108 for num in fig_nums:
108 for num in fig_nums:
109 f = Gcf.figs.get(num)
109 f = Gcf.figs.get(num)
110 if f is None:
110 if f is None:
111 print('Warning: figure %s not available.' % num)
111 print('Warning: figure %s not available.' % num)
112 else:
112 else:
113 figs.append(f.canvas.figure)
113 figs.append(f.canvas.figure)
114 return figs
114 return figs
115
115
116
116
117 def figsize(sizex, sizey):
117 def figsize(sizex, sizey):
118 """Set the default figure size to be [sizex, sizey].
118 """Set the default figure size to be [sizex, sizey].
119
119
120 This is just an easy to remember, convenience wrapper that sets::
120 This is just an easy to remember, convenience wrapper that sets::
121
121
122 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
122 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
123 """
123 """
124 import matplotlib
124 import matplotlib
125 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
125 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
126
126
127
127
128 def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
128 def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
129 """Print a figure to an image, and return the resulting file data
129 """Print a figure to an image, and return the resulting file data
130
130
131 Returned data will be bytes unless ``fmt='svg'``,
131 Returned data will be bytes unless ``fmt='svg'``,
132 in which case it will be unicode.
132 in which case it will be unicode.
133
133
134 Any keyword args are passed to fig.canvas.print_figure,
134 Any keyword args are passed to fig.canvas.print_figure,
135 such as ``quality`` or ``bbox_inches``.
135 such as ``quality`` or ``bbox_inches``.
136
136
137 If `base64` is True, return base64-encoded str instead of raw bytes
137 If `base64` is True, return base64-encoded str instead of raw bytes
138 for binary-encoded image formats
138 for binary-encoded image formats
139
139
140 .. versionadded:: 7.29
140 .. versionadded:: 7.29
141 base64 argument
141 base64 argument
142 """
142 """
143 # When there's an empty figure, we shouldn't return anything, otherwise we
143 # When there's an empty figure, we shouldn't return anything, otherwise we
144 # get big blank areas in the qt console.
144 # get big blank areas in the qt console.
145 if not fig.axes and not fig.lines:
145 if not fig.axes and not fig.lines:
146 return
146 return
147
147
148 dpi = fig.dpi
148 dpi = fig.dpi
149 if fmt == 'retina':
149 if fmt == 'retina':
150 dpi = dpi * 2
150 dpi = dpi * 2
151 fmt = 'png'
151 fmt = 'png'
152
152
153 # build keyword args
153 # build keyword args
154 kw = {
154 kw = {
155 "format":fmt,
155 "format":fmt,
156 "facecolor":fig.get_facecolor(),
156 "facecolor":fig.get_facecolor(),
157 "edgecolor":fig.get_edgecolor(),
157 "edgecolor":fig.get_edgecolor(),
158 "dpi":dpi,
158 "dpi":dpi,
159 "bbox_inches":bbox_inches,
159 "bbox_inches":bbox_inches,
160 }
160 }
161 # **kwargs get higher priority
161 # **kwargs get higher priority
162 kw.update(kwargs)
162 kw.update(kwargs)
163
163
164 bytes_io = BytesIO()
164 bytes_io = BytesIO()
165 if fig.canvas is None:
165 if fig.canvas is None:
166 from matplotlib.backend_bases import FigureCanvasBase
166 from matplotlib.backend_bases import FigureCanvasBase
167 FigureCanvasBase(fig)
167 FigureCanvasBase(fig)
168
168
169 fig.canvas.print_figure(bytes_io, **kw)
169 fig.canvas.print_figure(bytes_io, **kw)
170 data = bytes_io.getvalue()
170 data = bytes_io.getvalue()
171 if fmt == 'svg':
171 if fmt == 'svg':
172 data = data.decode('utf-8')
172 data = data.decode('utf-8')
173 elif base64:
173 elif base64:
174 data = b2a_base64(data, newline=False).decode("ascii")
174 data = b2a_base64(data, newline=False).decode("ascii")
175 return data
175 return data
176
176
177 def retina_figure(fig, base64=False, **kwargs):
177 def retina_figure(fig, base64=False, **kwargs):
178 """format a figure as a pixel-doubled (retina) PNG
178 """format a figure as a pixel-doubled (retina) PNG
179
179
180 If `base64` is True, return base64-encoded str instead of raw bytes
180 If `base64` is True, return base64-encoded str instead of raw bytes
181 for binary-encoded image formats
181 for binary-encoded image formats
182
182
183 .. versionadded:: 7.29
183 .. versionadded:: 7.29
184 base64 argument
184 base64 argument
185 """
185 """
186 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
186 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
187 # Make sure that retina_figure acts just like print_figure and returns
187 # Make sure that retina_figure acts just like print_figure and returns
188 # None when the figure is empty.
188 # None when the figure is empty.
189 if pngdata is None:
189 if pngdata is None:
190 return
190 return
191 w, h = _pngxy(pngdata)
191 w, h = _pngxy(pngdata)
192 metadata = {"width": w//2, "height":h//2}
192 metadata = {"width": w//2, "height":h//2}
193 if base64:
193 if base64:
194 pngdata = b2a_base64(pngdata, newline=False).decode("ascii")
194 pngdata = b2a_base64(pngdata, newline=False).decode("ascii")
195 return pngdata, metadata
195 return pngdata, metadata
196
196
197
197
198 # We need a little factory function here to create the closure where
198 # We need a little factory function here to create the closure where
199 # safe_execfile can live.
199 # safe_execfile can live.
200 def mpl_runner(safe_execfile):
200 def mpl_runner(safe_execfile):
201 """Factory to return a matplotlib-enabled runner for %run.
201 """Factory to return a matplotlib-enabled runner for %run.
202
202
203 Parameters
203 Parameters
204 ----------
204 ----------
205 safe_execfile : function
205 safe_execfile : function
206 This must be a function with the same interface as the
206 This must be a function with the same interface as the
207 :meth:`safe_execfile` method of IPython.
207 :meth:`safe_execfile` method of IPython.
208
208
209 Returns
209 Returns
210 -------
210 -------
211 A function suitable for use as the ``runner`` argument of the %run magic
211 A function suitable for use as the ``runner`` argument of the %run magic
212 function.
212 function.
213 """
213 """
214
214
215 def mpl_execfile(fname,*where,**kw):
215 def mpl_execfile(fname,*where,**kw):
216 """matplotlib-aware wrapper around safe_execfile.
216 """matplotlib-aware wrapper around safe_execfile.
217
217
218 Its interface is identical to that of the :func:`execfile` builtin.
218 Its interface is identical to that of the :func:`execfile` builtin.
219
219
220 This is ultimately a call to execfile(), but wrapped in safeties to
220 This is ultimately a call to execfile(), but wrapped in safeties to
221 properly handle interactive rendering."""
221 properly handle interactive rendering."""
222
222
223 import matplotlib
223 import matplotlib
224 import matplotlib.pyplot as plt
224 import matplotlib.pyplot as plt
225
225
226 #print '*** Matplotlib runner ***' # dbg
226 #print '*** Matplotlib runner ***' # dbg
227 # turn off rendering until end of script
227 # turn off rendering until end of script
228 with matplotlib.rc_context({"interactive": False}):
228 with matplotlib.rc_context({"interactive": False}):
229 safe_execfile(fname, *where, **kw)
229 safe_execfile(fname, *where, **kw)
230
230
231 if matplotlib.is_interactive():
231 if matplotlib.is_interactive():
232 plt.show()
232 plt.show()
233
233
234 # make rendering call now, if the user tried to do it
234 # make rendering call now, if the user tried to do it
235 if plt.draw_if_interactive.called:
235 if plt.draw_if_interactive.called:
236 plt.draw()
236 plt.draw()
237 plt.draw_if_interactive.called = False
237 plt.draw_if_interactive.called = False
238
238
239 # re-draw everything that is stale
239 # re-draw everything that is stale
240 try:
240 try:
241 da = plt.draw_all
241 da = plt.draw_all
242 except AttributeError:
242 except AttributeError:
243 pass
243 pass
244 else:
244 else:
245 da()
245 da()
246
246
247 return mpl_execfile
247 return mpl_execfile
248
248
249
249
250 def _reshow_nbagg_figure(fig):
250 def _reshow_nbagg_figure(fig):
251 """reshow an nbagg figure"""
251 """reshow an nbagg figure"""
252 try:
252 try:
253 reshow = fig.canvas.manager.reshow
253 reshow = fig.canvas.manager.reshow
254 except AttributeError as e:
254 except AttributeError as e:
255 raise NotImplementedError() from e
255 raise NotImplementedError() from e
256 else:
256 else:
257 reshow()
257 reshow()
258
258
259
259
260 def select_figure_formats(shell, formats, **kwargs):
260 def select_figure_formats(shell, formats, **kwargs):
261 """Select figure formats for the inline backend.
261 """Select figure formats for the inline backend.
262
262
263 Parameters
263 Parameters
264 ----------
264 ----------
265 shell : InteractiveShell
265 shell : InteractiveShell
266 The main IPython instance.
266 The main IPython instance.
267 formats : str or set
267 formats : str or set
268 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
268 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
269 **kwargs : any
269 **kwargs : any
270 Extra keyword arguments to be passed to fig.canvas.print_figure.
270 Extra keyword arguments to be passed to fig.canvas.print_figure.
271 """
271 """
272 import matplotlib
272 import matplotlib
273 from matplotlib.figure import Figure
273 from matplotlib.figure import Figure
274
274
275 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
275 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
276 png_formatter = shell.display_formatter.formatters['image/png']
276 png_formatter = shell.display_formatter.formatters['image/png']
277 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
277 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
278 pdf_formatter = shell.display_formatter.formatters['application/pdf']
278 pdf_formatter = shell.display_formatter.formatters['application/pdf']
279
279
280 if isinstance(formats, str):
280 if isinstance(formats, str):
281 formats = {formats}
281 formats = {formats}
282 # cast in case of list / tuple
282 # cast in case of list / tuple
283 formats = set(formats)
283 formats = set(formats)
284
284
285 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
285 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
286 mplbackend = matplotlib.get_backend().lower()
286 mplbackend = matplotlib.get_backend().lower()
287 if mplbackend in ("nbagg", "ipympl", "widget", "module://ipympl.backend_nbagg"):
287 if mplbackend in ("nbagg", "ipympl", "widget", "module://ipympl.backend_nbagg"):
288 formatter = shell.display_formatter.ipython_display_formatter
288 formatter = shell.display_formatter.ipython_display_formatter
289 formatter.for_type(Figure, _reshow_nbagg_figure)
289 formatter.for_type(Figure, _reshow_nbagg_figure)
290
290
291 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
291 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
292 bad = formats.difference(supported)
292 bad = formats.difference(supported)
293 if bad:
293 if bad:
294 bs = "%s" % ','.join([repr(f) for f in bad])
294 bs = "%s" % ','.join([repr(f) for f in bad])
295 gs = "%s" % ','.join([repr(f) for f in supported])
295 gs = "%s" % ','.join([repr(f) for f in supported])
296 raise ValueError("supported formats are: %s not %s" % (gs, bs))
296 raise ValueError("supported formats are: %s not %s" % (gs, bs))
297
297
298 if "png" in formats:
298 if "png" in formats:
299 png_formatter.for_type(
299 png_formatter.for_type(
300 Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
300 Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
301 )
301 )
302 if "retina" in formats or "png2x" in formats:
302 if "retina" in formats or "png2x" in formats:
303 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
303 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
304 if "jpg" in formats or "jpeg" in formats:
304 if "jpg" in formats or "jpeg" in formats:
305 jpg_formatter.for_type(
305 jpg_formatter.for_type(
306 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
306 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
307 )
307 )
308 if "svg" in formats:
308 if "svg" in formats:
309 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
309 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
310 if "pdf" in formats:
310 if "pdf" in formats:
311 pdf_formatter.for_type(
311 pdf_formatter.for_type(
312 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
312 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
313 )
313 )
314
314
315 #-----------------------------------------------------------------------------
315 #-----------------------------------------------------------------------------
316 # Code for initializing matplotlib and importing pylab
316 # Code for initializing matplotlib and importing pylab
317 #-----------------------------------------------------------------------------
317 #-----------------------------------------------------------------------------
318
318
319
319
320 def find_gui_and_backend(gui=None, gui_select=None):
320 def find_gui_and_backend(gui=None, gui_select=None):
321 """Given a gui string return the gui and mpl backend.
321 """Given a gui string return the gui and mpl backend.
322
322
323 Parameters
323 Parameters
324 ----------
324 ----------
325 gui : str
325 gui : str
326 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
326 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
327 gui_select : str
327 gui_select : str
328 Can be one of ('tk','gtk','wx','qt','qt4','inline').
328 Can be one of ('tk','gtk','wx','qt','qt4','inline').
329 This is any gui already selected by the shell.
329 This is any gui already selected by the shell.
330
330
331 Returns
331 Returns
332 -------
332 -------
333 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
333 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
334 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
334 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
335 """
335 """
336
336
337 import matplotlib
337 import matplotlib
338
338
339 if _matplotlib_manages_backends():
339 if _matplotlib_manages_backends():
340 backend_registry = matplotlib.backends.registry.backend_registry
340 backend_registry = matplotlib.backends.registry.backend_registry
341
341
342 # gui argument may be a gui event loop or may be a backend name.
342 # gui argument may be a gui event loop or may be a backend name.
343 if gui in ("auto", None):
343 if gui in ("auto", None):
344 backend = matplotlib.rcParamsOrig["backend"]
344 backend = matplotlib.rcParamsOrig["backend"]
345 backend, gui = backend_registry.resolve_backend(backend)
345 backend, gui = backend_registry.resolve_backend(backend)
346 else:
346 else:
347 backend, gui = backend_registry.resolve_gui_or_backend(gui)
347 backend, gui = backend_registry.resolve_gui_or_backend(gui)
348
348
349 return gui, backend
349 return gui, backend
350
350
351 # Fallback to previous behaviour (Matplotlib < 3.9)
351 # Fallback to previous behaviour (Matplotlib < 3.9)
352 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
352 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
353 has_unified_qt_backend = mpl_version_info >= (3, 5)
353 has_unified_qt_backend = mpl_version_info >= (3, 5)
354
354
355 from IPython.core.pylabtools import backends
355 from IPython.core.pylabtools import backends
356
356
357 backends_ = dict(backends)
357 backends_ = dict(backends)
358 if not has_unified_qt_backend:
358 if not has_unified_qt_backend:
359 backends_["qt"] = "qt5agg"
359 backends_["qt"] = "qt5agg"
360
360
361 if gui and gui != 'auto':
361 if gui and gui != 'auto':
362 # select backend based on requested gui
362 # select backend based on requested gui
363 backend = backends_[gui]
363 backend = backends_[gui]
364 if gui == 'agg':
364 if gui == 'agg':
365 gui = None
365 gui = None
366 else:
366 else:
367 # We need to read the backend from the original data structure, *not*
367 # We need to read the backend from the original data structure, *not*
368 # from mpl.rcParams, since a prior invocation of %matplotlib may have
368 # from mpl.rcParams, since a prior invocation of %matplotlib may have
369 # overwritten that.
369 # overwritten that.
370 # WARNING: this assumes matplotlib 1.1 or newer!!
370 # WARNING: this assumes matplotlib 1.1 or newer!!
371 backend = matplotlib.rcParamsOrig['backend']
371 backend = matplotlib.rcParamsOrig['backend']
372 # In this case, we need to find what the appropriate gui selection call
372 # In this case, we need to find what the appropriate gui selection call
373 # should be for IPython, so we can activate inputhook accordingly
373 # should be for IPython, so we can activate inputhook accordingly
374 from IPython.core.pylabtools import backend2gui
374 from IPython.core.pylabtools import backend2gui
375 gui = backend2gui.get(backend, None)
375 gui = backend2gui.get(backend, None)
376
376
377 # If we have already had a gui active, we need it and inline are the
377 # If we have already had a gui active, we need it and inline are the
378 # ones allowed.
378 # ones allowed.
379 if gui_select and gui != gui_select:
379 if gui_select and gui != gui_select:
380 gui = gui_select
380 gui = gui_select
381 backend = backends_[gui]
381 backend = backends_[gui]
382
382
383 # Matplotlib before _matplotlib_manages_backends() can return "inline" for
383 # Matplotlib before _matplotlib_manages_backends() can return "inline" for
384 # no gui event loop rather than the None that IPython >= 8.24.0 expects.
384 # no gui event loop rather than the None that IPython >= 8.24.0 expects.
385 if gui == "inline":
385 if gui == "inline":
386 gui = None
386 gui = None
387
387
388 return gui, backend
388 return gui, backend
389
389
390
390
391 def activate_matplotlib(backend):
391 def activate_matplotlib(backend):
392 """Activate the given backend and set interactive to True."""
392 """Activate the given backend and set interactive to True."""
393
393
394 import matplotlib
394 import matplotlib
395 matplotlib.interactive(True)
395 matplotlib.interactive(True)
396
396
397 # Matplotlib had a bug where even switch_backend could not force
397 # Matplotlib had a bug where even switch_backend could not force
398 # the rcParam to update. This needs to be set *before* the module
398 # the rcParam to update. This needs to be set *before* the module
399 # magic of switch_backend().
399 # magic of switch_backend().
400 matplotlib.rcParams['backend'] = backend
400 matplotlib.rcParams['backend'] = backend
401
401
402 # Due to circular imports, pyplot may be only partially initialised
402 # Due to circular imports, pyplot may be only partially initialised
403 # when this function runs.
403 # when this function runs.
404 # So avoid needing matplotlib attribute-lookup to access pyplot.
404 # So avoid needing matplotlib attribute-lookup to access pyplot.
405 from matplotlib import pyplot as plt
405 from matplotlib import pyplot as plt
406
406
407 plt.switch_backend(backend)
407 plt.switch_backend(backend)
408
408
409 plt.show._needmain = False
409 plt.show._needmain = False
410 # We need to detect at runtime whether show() is called by the user.
410 # We need to detect at runtime whether show() is called by the user.
411 # For this, we wrap it into a decorator which adds a 'called' flag.
411 # For this, we wrap it into a decorator which adds a 'called' flag.
412 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
412 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
413
413
414
414
415 def import_pylab(user_ns, import_all=True):
415 def import_pylab(user_ns, import_all=True):
416 """Populate the namespace with pylab-related values.
416 """Populate the namespace with pylab-related values.
417
417
418 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
418 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
419
419
420 Also imports a few names from IPython (figsize, display, getfigs)
420 Also imports a few names from IPython (figsize, display, getfigs)
421
421
422 """
422 """
423
423
424 # Import numpy as np/pyplot as plt are conventions we're trying to
424 # Import numpy as np/pyplot as plt are conventions we're trying to
425 # somewhat standardize on. Making them available to users by default
425 # somewhat standardize on. Making them available to users by default
426 # will greatly help this.
426 # will greatly help this.
427 s = ("import numpy\n"
427 s = ("import numpy\n"
428 "import matplotlib\n"
428 "import matplotlib\n"
429 "from matplotlib import pylab, mlab, pyplot\n"
429 "from matplotlib import pylab, mlab, pyplot\n"
430 "np = numpy\n"
430 "np = numpy\n"
431 "plt = pyplot\n"
431 "plt = pyplot\n"
432 )
432 )
433 exec(s, user_ns)
433 exec(s, user_ns)
434
434
435 if import_all:
435 if import_all:
436 s = ("from matplotlib.pylab import *\n"
436 s = ("from matplotlib.pylab import *\n"
437 "from numpy import *\n")
437 "from numpy import *\n")
438 exec(s, user_ns)
438 exec(s, user_ns)
439
439
440 # IPython symbols to add
440 # IPython symbols to add
441 user_ns['figsize'] = figsize
441 user_ns['figsize'] = figsize
442 from IPython.display import display
442 from IPython.display import display
443 # Add display and getfigs to the user's namespace
443 # Add display and getfigs to the user's namespace
444 user_ns['display'] = display
444 user_ns['display'] = display
445 user_ns['getfigs'] = getfigs
445 user_ns['getfigs'] = getfigs
446
446
447
447
448 def configure_inline_support(shell, backend):
448 def configure_inline_support(shell, backend):
449 """
449 """
450 .. deprecated:: 7.23
450 .. deprecated:: 7.23
451
451
452 use `matplotlib_inline.backend_inline.configure_inline_support()`
452 use `matplotlib_inline.backend_inline.configure_inline_support()`
453
453
454 Configure an IPython shell object for matplotlib use.
454 Configure an IPython shell object for matplotlib use.
455
455
456 Parameters
456 Parameters
457 ----------
457 ----------
458 shell : InteractiveShell instance
458 shell : InteractiveShell instance
459 backend : matplotlib backend
459 backend : matplotlib backend
460 """
460 """
461 warnings.warn(
461 warnings.warn(
462 "`configure_inline_support` is deprecated since IPython 7.23, directly "
462 "`configure_inline_support` is deprecated since IPython 7.23, directly "
463 "use `matplotlib_inline.backend_inline.configure_inline_support()`",
463 "use `matplotlib_inline.backend_inline.configure_inline_support()`",
464 DeprecationWarning,
464 DeprecationWarning,
465 stacklevel=2,
465 stacklevel=2,
466 )
466 )
467
467
468 from matplotlib_inline.backend_inline import (
468 from matplotlib_inline.backend_inline import (
469 configure_inline_support as configure_inline_support_orig,
469 configure_inline_support as configure_inline_support_orig,
470 )
470 )
471
471
472 configure_inline_support_orig(shell, backend)
472 configure_inline_support_orig(shell, backend)
473
473
474
474
475 def _matplotlib_manages_backends():
475 # Determine if Matplotlib manages backends only if needed, and cache result.
476 import matplotlib
476 # Do not read this directly, instead use _matplotlib_manages_backends().
477 _matplotlib_manages_backends_value: bool | None = None
477
478
478 mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0))
479
479 return mpl_version_info >= (3, 9)
480 def _matplotlib_manages_backends() -> bool:
481 """Return True if Matplotlib manages backends, False otherwise.
482
483 If it returns True, the caller can be sure that
484 matplotlib.backends.registry.backend_registry is available along with
485 member functions resolve_gui_or_backend, resolve_backend and list_all.
486 """
487 global _matplotlib_manages_backends_value
488 if _matplotlib_manages_backends_value is None:
489 try:
490 from matplotlib.backends.registry import backend_registry
491
492 _matplotlib_manages_backends_value = hasattr(
493 backend_registry, "resolve_gui_or_backend"
494 )
495 except ImportError:
496 _matplotlib_manages_backends_value = False
497
498 return _matplotlib_manages_backends_value
General Comments 0
You need to be logged in to leave comments. Login now