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