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