##// END OF EJS Templates
Backport PR #4230: Switch correctly to the user's default matplotlib backend after inline....
MinRK -
Show More
@@ -0,0 +1,4
1 We fixed an issue with switching between matplotlib inline and GUI backends,
2 but the fix requires matplotlib 1.1 or newer. So from now on, we consider
3 matplotlib 1.1 to be the minimally supported version for IPython. Older
4 versions for the most part will work, but we make no guarantees about it.
@@ -1,335 +1,339
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities.
2 """Pylab (matplotlib) support utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Fernando Perez.
7 * Fernando Perez.
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2009 The IPython Development Team
12 # Copyright (C) 2009 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import sys
22 import sys
23 from io import BytesIO
23 from io import BytesIO
24
24
25 from IPython.core.display import _pngxy
25 from IPython.core.display import _pngxy
26 from IPython.utils.decorators import flag_calls
26 from IPython.utils.decorators import flag_calls
27
27
28 # If user specifies a GUI, that dictates the backend, otherwise we read the
28 # If user specifies a GUI, that dictates the backend, otherwise we read the
29 # user's mpl default from the mpl rc structure
29 # user's mpl default from the mpl rc structure
30 backends = {'tk': 'TkAgg',
30 backends = {'tk': 'TkAgg',
31 'gtk': 'GTKAgg',
31 'gtk': 'GTKAgg',
32 'wx': 'WXAgg',
32 'wx': 'WXAgg',
33 'qt': 'Qt4Agg', # qt3 not supported
33 'qt': 'Qt4Agg', # qt3 not supported
34 'qt4': 'Qt4Agg',
34 'qt4': 'Qt4Agg',
35 'osx': 'MacOSX',
35 'osx': 'MacOSX',
36 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
36 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
37
37
38 # We also need a reverse backends2guis mapping that will properly choose which
38 # We also need a reverse backends2guis mapping that will properly choose which
39 # GUI support to activate based on the desired matplotlib backend. For the
39 # GUI support to activate based on the desired matplotlib backend. For the
40 # most part it's just a reverse of the above dict, but we also need to add a
40 # most part it's just a reverse of the above dict, but we also need to add a
41 # few others that map to the same GUI manually:
41 # few others that map to the same GUI manually:
42 backend2gui = dict(zip(backends.values(), backends.keys()))
42 backend2gui = dict(zip(backends.values(), backends.keys()))
43 # Our tests expect backend2gui to just return 'qt'
43 # Our tests expect backend2gui to just return 'qt'
44 backend2gui['Qt4Agg'] = 'qt'
44 backend2gui['Qt4Agg'] = 'qt'
45 # In the reverse mapping, there are a few extra valid matplotlib backends that
45 # In the reverse mapping, there are a few extra valid matplotlib backends that
46 # map to the same GUI support
46 # map to the same GUI support
47 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
47 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
48 backend2gui['WX'] = 'wx'
48 backend2gui['WX'] = 'wx'
49 backend2gui['CocoaAgg'] = 'osx'
49 backend2gui['CocoaAgg'] = 'osx'
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Matplotlib utilities
52 # Matplotlib utilities
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55
55
56 def getfigs(*fig_nums):
56 def getfigs(*fig_nums):
57 """Get a list of matplotlib figures by figure numbers.
57 """Get a list of matplotlib figures by figure numbers.
58
58
59 If no arguments are given, all available figures are returned. If the
59 If no arguments are given, all available figures are returned. If the
60 argument list contains references to invalid figures, a warning is printed
60 argument list contains references to invalid figures, a warning is printed
61 but the function continues pasting further figures.
61 but the function continues pasting further figures.
62
62
63 Parameters
63 Parameters
64 ----------
64 ----------
65 figs : tuple
65 figs : tuple
66 A tuple of ints giving the figure numbers of the figures to return.
66 A tuple of ints giving the figure numbers of the figures to return.
67 """
67 """
68 from matplotlib._pylab_helpers import Gcf
68 from matplotlib._pylab_helpers import Gcf
69 if not fig_nums:
69 if not fig_nums:
70 fig_managers = Gcf.get_all_fig_managers()
70 fig_managers = Gcf.get_all_fig_managers()
71 return [fm.canvas.figure for fm in fig_managers]
71 return [fm.canvas.figure for fm in fig_managers]
72 else:
72 else:
73 figs = []
73 figs = []
74 for num in fig_nums:
74 for num in fig_nums:
75 f = Gcf.figs.get(num)
75 f = Gcf.figs.get(num)
76 if f is None:
76 if f is None:
77 print('Warning: figure %s not available.' % num)
77 print('Warning: figure %s not available.' % num)
78 else:
78 else:
79 figs.append(f.canvas.figure)
79 figs.append(f.canvas.figure)
80 return figs
80 return figs
81
81
82
82
83 def figsize(sizex, sizey):
83 def figsize(sizex, sizey):
84 """Set the default figure size to be [sizex, sizey].
84 """Set the default figure size to be [sizex, sizey].
85
85
86 This is just an easy to remember, convenience wrapper that sets::
86 This is just an easy to remember, convenience wrapper that sets::
87
87
88 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
88 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
89 """
89 """
90 import matplotlib
90 import matplotlib
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92
92
93
93
94 def print_figure(fig, fmt='png'):
94 def print_figure(fig, fmt='png'):
95 """Convert a figure to svg or png for inline display."""
95 """Convert a figure to svg or png for inline display."""
96 from matplotlib import rcParams
96 from matplotlib import rcParams
97 # When there's an empty figure, we shouldn't return anything, otherwise we
97 # When there's an empty figure, we shouldn't return anything, otherwise we
98 # get big blank areas in the qt console.
98 # get big blank areas in the qt console.
99 if not fig.axes and not fig.lines:
99 if not fig.axes and not fig.lines:
100 return
100 return
101
101
102 fc = fig.get_facecolor()
102 fc = fig.get_facecolor()
103 ec = fig.get_edgecolor()
103 ec = fig.get_edgecolor()
104 bytes_io = BytesIO()
104 bytes_io = BytesIO()
105 dpi = rcParams['savefig.dpi']
105 dpi = rcParams['savefig.dpi']
106 if fmt == 'retina':
106 if fmt == 'retina':
107 dpi = dpi * 2
107 dpi = dpi * 2
108 fmt = 'png'
108 fmt = 'png'
109 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
109 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
110 facecolor=fc, edgecolor=ec, dpi=dpi)
110 facecolor=fc, edgecolor=ec, dpi=dpi)
111 data = bytes_io.getvalue()
111 data = bytes_io.getvalue()
112 return data
112 return data
113
113
114 def retina_figure(fig):
114 def retina_figure(fig):
115 """format a figure as a pixel-doubled (retina) PNG"""
115 """format a figure as a pixel-doubled (retina) PNG"""
116 pngdata = print_figure(fig, fmt='retina')
116 pngdata = print_figure(fig, fmt='retina')
117 w, h = _pngxy(pngdata)
117 w, h = _pngxy(pngdata)
118 metadata = dict(width=w//2, height=h//2)
118 metadata = dict(width=w//2, height=h//2)
119 return pngdata, metadata
119 return pngdata, metadata
120
120
121 # We need a little factory function here to create the closure where
121 # We need a little factory function here to create the closure where
122 # safe_execfile can live.
122 # safe_execfile can live.
123 def mpl_runner(safe_execfile):
123 def mpl_runner(safe_execfile):
124 """Factory to return a matplotlib-enabled runner for %run.
124 """Factory to return a matplotlib-enabled runner for %run.
125
125
126 Parameters
126 Parameters
127 ----------
127 ----------
128 safe_execfile : function
128 safe_execfile : function
129 This must be a function with the same interface as the
129 This must be a function with the same interface as the
130 :meth:`safe_execfile` method of IPython.
130 :meth:`safe_execfile` method of IPython.
131
131
132 Returns
132 Returns
133 -------
133 -------
134 A function suitable for use as the ``runner`` argument of the %run magic
134 A function suitable for use as the ``runner`` argument of the %run magic
135 function.
135 function.
136 """
136 """
137
137
138 def mpl_execfile(fname,*where,**kw):
138 def mpl_execfile(fname,*where,**kw):
139 """matplotlib-aware wrapper around safe_execfile.
139 """matplotlib-aware wrapper around safe_execfile.
140
140
141 Its interface is identical to that of the :func:`execfile` builtin.
141 Its interface is identical to that of the :func:`execfile` builtin.
142
142
143 This is ultimately a call to execfile(), but wrapped in safeties to
143 This is ultimately a call to execfile(), but wrapped in safeties to
144 properly handle interactive rendering."""
144 properly handle interactive rendering."""
145
145
146 import matplotlib
146 import matplotlib
147 import matplotlib.pylab as pylab
147 import matplotlib.pylab as pylab
148
148
149 #print '*** Matplotlib runner ***' # dbg
149 #print '*** Matplotlib runner ***' # dbg
150 # turn off rendering until end of script
150 # turn off rendering until end of script
151 is_interactive = matplotlib.rcParams['interactive']
151 is_interactive = matplotlib.rcParams['interactive']
152 matplotlib.interactive(False)
152 matplotlib.interactive(False)
153 safe_execfile(fname,*where,**kw)
153 safe_execfile(fname,*where,**kw)
154 matplotlib.interactive(is_interactive)
154 matplotlib.interactive(is_interactive)
155 # make rendering call now, if the user tried to do it
155 # make rendering call now, if the user tried to do it
156 if pylab.draw_if_interactive.called:
156 if pylab.draw_if_interactive.called:
157 pylab.draw()
157 pylab.draw()
158 pylab.draw_if_interactive.called = False
158 pylab.draw_if_interactive.called = False
159
159
160 return mpl_execfile
160 return mpl_execfile
161
161
162
162
163 def select_figure_format(shell, fmt):
163 def select_figure_format(shell, fmt):
164 """Select figure format for inline backend, can be 'png', 'retina', or 'svg'.
164 """Select figure format for inline backend, can be 'png', 'retina', or 'svg'.
165
165
166 Using this method ensures only one figure format is active at a time.
166 Using this method ensures only one figure format is active at a time.
167 """
167 """
168 from matplotlib.figure import Figure
168 from matplotlib.figure import Figure
169 from IPython.kernel.zmq.pylab import backend_inline
169 from IPython.kernel.zmq.pylab import backend_inline
170
170
171 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
171 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
172 png_formatter = shell.display_formatter.formatters['image/png']
172 png_formatter = shell.display_formatter.formatters['image/png']
173
173
174 if fmt == 'png':
174 if fmt == 'png':
175 svg_formatter.type_printers.pop(Figure, None)
175 svg_formatter.type_printers.pop(Figure, None)
176 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
176 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
177 elif fmt in ('png2x', 'retina'):
177 elif fmt in ('png2x', 'retina'):
178 svg_formatter.type_printers.pop(Figure, None)
178 svg_formatter.type_printers.pop(Figure, None)
179 png_formatter.for_type(Figure, retina_figure)
179 png_formatter.for_type(Figure, retina_figure)
180 elif fmt == 'svg':
180 elif fmt == 'svg':
181 png_formatter.type_printers.pop(Figure, None)
181 png_formatter.type_printers.pop(Figure, None)
182 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
182 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
183 else:
183 else:
184 raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt)
184 raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt)
185
185
186 # set the format to be used in the backend()
186 # set the format to be used in the backend()
187 backend_inline._figure_format = fmt
187 backend_inline._figure_format = fmt
188
188
189 #-----------------------------------------------------------------------------
189 #-----------------------------------------------------------------------------
190 # Code for initializing matplotlib and importing pylab
190 # Code for initializing matplotlib and importing pylab
191 #-----------------------------------------------------------------------------
191 #-----------------------------------------------------------------------------
192
192
193
193
194 def find_gui_and_backend(gui=None, gui_select=None):
194 def find_gui_and_backend(gui=None, gui_select=None):
195 """Given a gui string return the gui and mpl backend.
195 """Given a gui string return the gui and mpl backend.
196
196
197 Parameters
197 Parameters
198 ----------
198 ----------
199 gui : str
199 gui : str
200 Can be one of ('tk','gtk','wx','qt','qt4','inline').
200 Can be one of ('tk','gtk','wx','qt','qt4','inline').
201 gui_select : str
201 gui_select : str
202 Can be one of ('tk','gtk','wx','qt','qt4','inline').
202 Can be one of ('tk','gtk','wx','qt','qt4','inline').
203 This is any gui already selected by the shell.
203 This is any gui already selected by the shell.
204
204
205 Returns
205 Returns
206 -------
206 -------
207 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
207 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
208 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
208 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
209 """
209 """
210
210
211 import matplotlib
211 import matplotlib
212
212
213 if gui and gui != 'auto':
213 if gui and gui != 'auto':
214 # select backend based on requested gui
214 # select backend based on requested gui
215 backend = backends[gui]
215 backend = backends[gui]
216 else:
216 else:
217 backend = matplotlib.rcParams['backend']
217 # We need to read the backend from the original data structure, *not*
218 # from mpl.rcParams, since a prior invocation of %matplotlib may have
219 # overwritten that.
220 # WARNING: this assumes matplotlib 1.1 or newer!!
221 backend = matplotlib.rcParamsOrig['backend']
218 # In this case, we need to find what the appropriate gui selection call
222 # In this case, we need to find what the appropriate gui selection call
219 # should be for IPython, so we can activate inputhook accordingly
223 # should be for IPython, so we can activate inputhook accordingly
220 gui = backend2gui.get(backend, None)
224 gui = backend2gui.get(backend, None)
221
225
222 # If we have already had a gui active, we need it and inline are the
226 # If we have already had a gui active, we need it and inline are the
223 # ones allowed.
227 # ones allowed.
224 if gui_select and gui != gui_select:
228 if gui_select and gui != gui_select:
225 gui = gui_select
229 gui = gui_select
226 backend = backends[gui]
230 backend = backends[gui]
227
231
228 return gui, backend
232 return gui, backend
229
233
230
234
231 def activate_matplotlib(backend):
235 def activate_matplotlib(backend):
232 """Activate the given backend and set interactive to True."""
236 """Activate the given backend and set interactive to True."""
233
237
234 import matplotlib
238 import matplotlib
235 matplotlib.interactive(True)
239 matplotlib.interactive(True)
236
240
237 # Matplotlib had a bug where even switch_backend could not force
241 # Matplotlib had a bug where even switch_backend could not force
238 # the rcParam to update. This needs to be set *before* the module
242 # the rcParam to update. This needs to be set *before* the module
239 # magic of switch_backend().
243 # magic of switch_backend().
240 matplotlib.rcParams['backend'] = backend
244 matplotlib.rcParams['backend'] = backend
241
245
242 import matplotlib.pyplot
246 import matplotlib.pyplot
243 matplotlib.pyplot.switch_backend(backend)
247 matplotlib.pyplot.switch_backend(backend)
244
248
245 # This must be imported last in the matplotlib series, after
249 # This must be imported last in the matplotlib series, after
246 # backend/interactivity choices have been made
250 # backend/interactivity choices have been made
247 import matplotlib.pylab as pylab
251 import matplotlib.pylab as pylab
248
252
249 pylab.show._needmain = False
253 pylab.show._needmain = False
250 # We need to detect at runtime whether show() is called by the user.
254 # We need to detect at runtime whether show() is called by the user.
251 # For this, we wrap it into a decorator which adds a 'called' flag.
255 # For this, we wrap it into a decorator which adds a 'called' flag.
252 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
256 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
253
257
254
258
255 def import_pylab(user_ns, import_all=True):
259 def import_pylab(user_ns, import_all=True):
256 """Populate the namespace with pylab-related values.
260 """Populate the namespace with pylab-related values.
257
261
258 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
262 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
259
263
260 Also imports a few names from IPython (figsize, display, getfigs)
264 Also imports a few names from IPython (figsize, display, getfigs)
261
265
262 """
266 """
263
267
264 # Import numpy as np/pyplot as plt are conventions we're trying to
268 # Import numpy as np/pyplot as plt are conventions we're trying to
265 # somewhat standardize on. Making them available to users by default
269 # somewhat standardize on. Making them available to users by default
266 # will greatly help this.
270 # will greatly help this.
267 s = ("import numpy\n"
271 s = ("import numpy\n"
268 "import matplotlib\n"
272 "import matplotlib\n"
269 "from matplotlib import pylab, mlab, pyplot\n"
273 "from matplotlib import pylab, mlab, pyplot\n"
270 "np = numpy\n"
274 "np = numpy\n"
271 "plt = pyplot\n"
275 "plt = pyplot\n"
272 )
276 )
273 exec s in user_ns
277 exec s in user_ns
274
278
275 if import_all:
279 if import_all:
276 s = ("from matplotlib.pylab import *\n"
280 s = ("from matplotlib.pylab import *\n"
277 "from numpy import *\n")
281 "from numpy import *\n")
278 exec s in user_ns
282 exec s in user_ns
279
283
280 # IPython symbols to add
284 # IPython symbols to add
281 user_ns['figsize'] = figsize
285 user_ns['figsize'] = figsize
282 from IPython.core.display import display
286 from IPython.core.display import display
283 # Add display and getfigs to the user's namespace
287 # Add display and getfigs to the user's namespace
284 user_ns['display'] = display
288 user_ns['display'] = display
285 user_ns['getfigs'] = getfigs
289 user_ns['getfigs'] = getfigs
286
290
287
291
288 def configure_inline_support(shell, backend):
292 def configure_inline_support(shell, backend):
289 """Configure an IPython shell object for matplotlib use.
293 """Configure an IPython shell object for matplotlib use.
290
294
291 Parameters
295 Parameters
292 ----------
296 ----------
293 shell : InteractiveShell instance
297 shell : InteractiveShell instance
294
298
295 backend : matplotlib backend
299 backend : matplotlib backend
296 """
300 """
297 # If using our svg payload backend, register the post-execution
301 # If using our svg payload backend, register the post-execution
298 # function that will pick up the results for display. This can only be
302 # function that will pick up the results for display. This can only be
299 # done with access to the real shell object.
303 # done with access to the real shell object.
300
304
301 # Note: if we can't load the inline backend, then there's no point
305 # Note: if we can't load the inline backend, then there's no point
302 # continuing (such as in terminal-only shells in environments without
306 # continuing (such as in terminal-only shells in environments without
303 # zeromq available).
307 # zeromq available).
304 try:
308 try:
305 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
309 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
306 except ImportError:
310 except ImportError:
307 return
311 return
308 from matplotlib import pyplot
312 from matplotlib import pyplot
309
313
310 cfg = InlineBackend.instance(parent=shell)
314 cfg = InlineBackend.instance(parent=shell)
311 cfg.shell = shell
315 cfg.shell = shell
312 if cfg not in shell.configurables:
316 if cfg not in shell.configurables:
313 shell.configurables.append(cfg)
317 shell.configurables.append(cfg)
314
318
315 if backend == backends['inline']:
319 if backend == backends['inline']:
316 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
320 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
317 shell.register_post_execute(flush_figures)
321 shell.register_post_execute(flush_figures)
318
322
319 # Save rcParams that will be overwrittern
323 # Save rcParams that will be overwrittern
320 shell._saved_rcParams = dict()
324 shell._saved_rcParams = dict()
321 for k in cfg.rc:
325 for k in cfg.rc:
322 shell._saved_rcParams[k] = pyplot.rcParams[k]
326 shell._saved_rcParams[k] = pyplot.rcParams[k]
323 # load inline_rc
327 # load inline_rc
324 pyplot.rcParams.update(cfg.rc)
328 pyplot.rcParams.update(cfg.rc)
325 else:
329 else:
326 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
330 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
327 if flush_figures in shell._post_execute:
331 if flush_figures in shell._post_execute:
328 shell._post_execute.pop(flush_figures)
332 shell._post_execute.pop(flush_figures)
329 if hasattr(shell, '_saved_rcParams'):
333 if hasattr(shell, '_saved_rcParams'):
330 pyplot.rcParams.update(shell._saved_rcParams)
334 pyplot.rcParams.update(shell._saved_rcParams)
331 del shell._saved_rcParams
335 del shell._saved_rcParams
332
336
333 # Setup the default figure format
337 # Setup the default figure format
334 select_figure_format(shell, cfg.figure_format)
338 select_figure_format(shell, cfg.figure_format)
335
339
General Comments 0
You need to be logged in to leave comments. Login now