##// END OF EJS Templates
Convert "osx" gui framework to/from "macosx" in Matplotlib
Ian Thomas -
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
General Comments 0
You need to be logged in to leave comments. Login now