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