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