Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/IPython/core/pylabtools.py: 22%
160 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:05 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:05 +0000
1# -*- coding: utf-8 -*-
2"""Pylab (matplotlib) support utilities."""
4# Copyright (c) IPython Development Team.
5# Distributed under the terms of the Modified BSD License.
7from io import BytesIO
8from binascii import b2a_base64
9from functools import partial
10import warnings
12from IPython.core.display import _pngxy
13from IPython.utils.decorators import flag_calls
15# If user specifies a GUI, that dictates the backend, otherwise we read the
16# user's mpl default from the mpl rc structure
17backends = {
18 "tk": "TkAgg",
19 "gtk": "GTKAgg",
20 "gtk3": "GTK3Agg",
21 "gtk4": "GTK4Agg",
22 "wx": "WXAgg",
23 "qt4": "Qt4Agg",
24 "qt5": "Qt5Agg",
25 "qt6": "QtAgg",
26 "qt": "QtAgg",
27 "osx": "MacOSX",
28 "nbagg": "nbAgg",
29 "webagg": "WebAgg",
30 "notebook": "nbAgg",
31 "agg": "agg",
32 "svg": "svg",
33 "pdf": "pdf",
34 "ps": "ps",
35 "inline": "module://matplotlib_inline.backend_inline",
36 "ipympl": "module://ipympl.backend_nbagg",
37 "widget": "module://ipympl.backend_nbagg",
38}
40# We also need a reverse backends2guis mapping that will properly choose which
41# GUI support to activate based on the desired matplotlib backend. For the
42# most part it's just a reverse of the above dict, but we also need to add a
43# few others that map to the same GUI manually:
44backend2gui = dict(zip(backends.values(), backends.keys()))
45# In the reverse mapping, there are a few extra valid matplotlib backends that
46# map to the same GUI support
47backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk"
48backend2gui["GTK3Cairo"] = "gtk3"
49backend2gui["GTK4Cairo"] = "gtk4"
50backend2gui["WX"] = "wx"
51backend2gui["CocoaAgg"] = "osx"
52# There needs to be a hysteresis here as the new QtAgg Matplotlib backend
53# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
54# and Qt6.
55backend2gui["QtAgg"] = "qt"
56backend2gui["Qt4Agg"] = "qt4"
57backend2gui["Qt5Agg"] = "qt5"
59# And some backends that don't need GUI integration
60del backend2gui["nbAgg"]
61del backend2gui["agg"]
62del backend2gui["svg"]
63del backend2gui["pdf"]
64del backend2gui["ps"]
65del backend2gui["module://matplotlib_inline.backend_inline"]
66del backend2gui["module://ipympl.backend_nbagg"]
68#-----------------------------------------------------------------------------
69# Matplotlib utilities
70#-----------------------------------------------------------------------------
73def getfigs(*fig_nums):
74 """Get a list of matplotlib figures by figure numbers.
76 If no arguments are given, all available figures are returned. If the
77 argument list contains references to invalid figures, a warning is printed
78 but the function continues pasting further figures.
80 Parameters
81 ----------
82 figs : tuple
83 A tuple of ints giving the figure numbers of the figures to return.
84 """
85 from matplotlib._pylab_helpers import Gcf
86 if not fig_nums:
87 fig_managers = Gcf.get_all_fig_managers()
88 return [fm.canvas.figure for fm in fig_managers]
89 else:
90 figs = []
91 for num in fig_nums:
92 f = Gcf.figs.get(num)
93 if f is None:
94 print('Warning: figure %s not available.' % num)
95 else:
96 figs.append(f.canvas.figure)
97 return figs
100def figsize(sizex, sizey):
101 """Set the default figure size to be [sizex, sizey].
103 This is just an easy to remember, convenience wrapper that sets::
105 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
106 """
107 import matplotlib
108 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
111def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
112 """Print a figure to an image, and return the resulting file data
114 Returned data will be bytes unless ``fmt='svg'``,
115 in which case it will be unicode.
117 Any keyword args are passed to fig.canvas.print_figure,
118 such as ``quality`` or ``bbox_inches``.
120 If `base64` is True, return base64-encoded str instead of raw bytes
121 for binary-encoded image formats
123 .. versionadded:: 7.29
124 base64 argument
125 """
126 # When there's an empty figure, we shouldn't return anything, otherwise we
127 # get big blank areas in the qt console.
128 if not fig.axes and not fig.lines:
129 return
131 dpi = fig.dpi
132 if fmt == 'retina':
133 dpi = dpi * 2
134 fmt = 'png'
136 # build keyword args
137 kw = {
138 "format":fmt,
139 "facecolor":fig.get_facecolor(),
140 "edgecolor":fig.get_edgecolor(),
141 "dpi":dpi,
142 "bbox_inches":bbox_inches,
143 }
144 # **kwargs get higher priority
145 kw.update(kwargs)
147 bytes_io = BytesIO()
148 if fig.canvas is None:
149 from matplotlib.backend_bases import FigureCanvasBase
150 FigureCanvasBase(fig)
152 fig.canvas.print_figure(bytes_io, **kw)
153 data = bytes_io.getvalue()
154 if fmt == 'svg':
155 data = data.decode('utf-8')
156 elif base64:
157 data = b2a_base64(data, newline=False).decode("ascii")
158 return data
160def retina_figure(fig, base64=False, **kwargs):
161 """format a figure as a pixel-doubled (retina) PNG
163 If `base64` is True, return base64-encoded str instead of raw bytes
164 for binary-encoded image formats
166 .. versionadded:: 7.29
167 base64 argument
168 """
169 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
170 # Make sure that retina_figure acts just like print_figure and returns
171 # None when the figure is empty.
172 if pngdata is None:
173 return
174 w, h = _pngxy(pngdata)
175 metadata = {"width": w//2, "height":h//2}
176 if base64:
177 pngdata = b2a_base64(pngdata, newline=False).decode("ascii")
178 return pngdata, metadata
181# We need a little factory function here to create the closure where
182# safe_execfile can live.
183def mpl_runner(safe_execfile):
184 """Factory to return a matplotlib-enabled runner for %run.
186 Parameters
187 ----------
188 safe_execfile : function
189 This must be a function with the same interface as the
190 :meth:`safe_execfile` method of IPython.
192 Returns
193 -------
194 A function suitable for use as the ``runner`` argument of the %run magic
195 function.
196 """
198 def mpl_execfile(fname,*where,**kw):
199 """matplotlib-aware wrapper around safe_execfile.
201 Its interface is identical to that of the :func:`execfile` builtin.
203 This is ultimately a call to execfile(), but wrapped in safeties to
204 properly handle interactive rendering."""
206 import matplotlib
207 import matplotlib.pyplot as plt
209 #print '*** Matplotlib runner ***' # dbg
210 # turn off rendering until end of script
211 with matplotlib.rc_context({"interactive": False}):
212 safe_execfile(fname, *where, **kw)
214 if matplotlib.is_interactive():
215 plt.show()
217 # make rendering call now, if the user tried to do it
218 if plt.draw_if_interactive.called:
219 plt.draw()
220 plt.draw_if_interactive.called = False
222 # re-draw everything that is stale
223 try:
224 da = plt.draw_all
225 except AttributeError:
226 pass
227 else:
228 da()
230 return mpl_execfile
233def _reshow_nbagg_figure(fig):
234 """reshow an nbagg figure"""
235 try:
236 reshow = fig.canvas.manager.reshow
237 except AttributeError as e:
238 raise NotImplementedError() from e
239 else:
240 reshow()
243def select_figure_formats(shell, formats, **kwargs):
244 """Select figure formats for the inline backend.
246 Parameters
247 ----------
248 shell : InteractiveShell
249 The main IPython instance.
250 formats : str or set
251 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
252 **kwargs : any
253 Extra keyword arguments to be passed to fig.canvas.print_figure.
254 """
255 import matplotlib
256 from matplotlib.figure import Figure
258 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
259 png_formatter = shell.display_formatter.formatters['image/png']
260 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
261 pdf_formatter = shell.display_formatter.formatters['application/pdf']
263 if isinstance(formats, str):
264 formats = {formats}
265 # cast in case of list / tuple
266 formats = set(formats)
268 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
269 mplbackend = matplotlib.get_backend().lower()
270 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
271 formatter = shell.display_formatter.ipython_display_formatter
272 formatter.for_type(Figure, _reshow_nbagg_figure)
274 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
275 bad = formats.difference(supported)
276 if bad:
277 bs = "%s" % ','.join([repr(f) for f in bad])
278 gs = "%s" % ','.join([repr(f) for f in supported])
279 raise ValueError("supported formats are: %s not %s" % (gs, bs))
281 if "png" in formats:
282 png_formatter.for_type(
283 Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
284 )
285 if "retina" in formats or "png2x" in formats:
286 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
287 if "jpg" in formats or "jpeg" in formats:
288 jpg_formatter.for_type(
289 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
290 )
291 if "svg" in formats:
292 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
293 if "pdf" in formats:
294 pdf_formatter.for_type(
295 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
296 )
298#-----------------------------------------------------------------------------
299# Code for initializing matplotlib and importing pylab
300#-----------------------------------------------------------------------------
303def find_gui_and_backend(gui=None, gui_select=None):
304 """Given a gui string return the gui and mpl backend.
306 Parameters
307 ----------
308 gui : str
309 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
310 gui_select : str
311 Can be one of ('tk','gtk','wx','qt','qt4','inline').
312 This is any gui already selected by the shell.
314 Returns
315 -------
316 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
317 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
318 """
320 import matplotlib
322 has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5)
324 backends_ = dict(backends)
325 if not has_unified_qt_backend:
326 backends_["qt"] = "qt5agg"
328 if gui and gui != 'auto':
329 # select backend based on requested gui
330 backend = backends_[gui]
331 if gui == 'agg':
332 gui = None
333 else:
334 # We need to read the backend from the original data structure, *not*
335 # from mpl.rcParams, since a prior invocation of %matplotlib may have
336 # overwritten that.
337 # WARNING: this assumes matplotlib 1.1 or newer!!
338 backend = matplotlib.rcParamsOrig['backend']
339 # In this case, we need to find what the appropriate gui selection call
340 # should be for IPython, so we can activate inputhook accordingly
341 gui = backend2gui.get(backend, None)
343 # If we have already had a gui active, we need it and inline are the
344 # ones allowed.
345 if gui_select and gui != gui_select:
346 gui = gui_select
347 backend = backends_[gui]
349 return gui, backend
352def activate_matplotlib(backend):
353 """Activate the given backend and set interactive to True."""
355 import matplotlib
356 matplotlib.interactive(True)
358 # Matplotlib had a bug where even switch_backend could not force
359 # the rcParam to update. This needs to be set *before* the module
360 # magic of switch_backend().
361 matplotlib.rcParams['backend'] = backend
363 # Due to circular imports, pyplot may be only partially initialised
364 # when this function runs.
365 # So avoid needing matplotlib attribute-lookup to access pyplot.
366 from matplotlib import pyplot as plt
368 plt.switch_backend(backend)
370 plt.show._needmain = False
371 # We need to detect at runtime whether show() is called by the user.
372 # For this, we wrap it into a decorator which adds a 'called' flag.
373 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
376def import_pylab(user_ns, import_all=True):
377 """Populate the namespace with pylab-related values.
379 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
381 Also imports a few names from IPython (figsize, display, getfigs)
383 """
385 # Import numpy as np/pyplot as plt are conventions we're trying to
386 # somewhat standardize on. Making them available to users by default
387 # will greatly help this.
388 s = ("import numpy\n"
389 "import matplotlib\n"
390 "from matplotlib import pylab, mlab, pyplot\n"
391 "np = numpy\n"
392 "plt = pyplot\n"
393 )
394 exec(s, user_ns)
396 if import_all:
397 s = ("from matplotlib.pylab import *\n"
398 "from numpy import *\n")
399 exec(s, user_ns)
401 # IPython symbols to add
402 user_ns['figsize'] = figsize
403 from IPython.display import display
404 # Add display and getfigs to the user's namespace
405 user_ns['display'] = display
406 user_ns['getfigs'] = getfigs
409def configure_inline_support(shell, backend):
410 """
411 .. deprecated:: 7.23
413 use `matplotlib_inline.backend_inline.configure_inline_support()`
415 Configure an IPython shell object for matplotlib use.
417 Parameters
418 ----------
419 shell : InteractiveShell instance
420 backend : matplotlib backend
421 """
422 warnings.warn(
423 "`configure_inline_support` is deprecated since IPython 7.23, directly "
424 "use `matplotlib_inline.backend_inline.configure_inline_support()`",
425 DeprecationWarning,
426 stacklevel=2,
427 )
429 from matplotlib_inline.backend_inline import (
430 configure_inline_support as configure_inline_support_orig,
431 )
433 configure_inline_support_orig(shell, backend)