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