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