##// END OF EJS Templates
Backport PR #6587 : Support %matplotlib qt5 and %matplotlib nbagg
Thomas Kluyver -
Show More
@@ -1,382 +1,384 b''
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 from __future__ import print_function
10 from __future__ import print_function
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2009 The IPython Development Team
13 # Copyright (C) 2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import sys
23 import sys
24 from io import BytesIO
24 from io import BytesIO
25
25
26 from IPython.core.display import _pngxy
26 from IPython.core.display import _pngxy
27 from IPython.utils.decorators import flag_calls
27 from IPython.utils.decorators import flag_calls
28 from IPython.utils import py3compat
28 from IPython.utils import py3compat
29
29
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
31 # user's mpl default from the mpl rc structure
31 # user's mpl default from the mpl rc structure
32 backends = {'tk': 'TkAgg',
32 backends = {'tk': 'TkAgg',
33 'gtk': 'GTKAgg',
33 'gtk': 'GTKAgg',
34 'gtk3': 'GTK3Agg',
34 'gtk3': 'GTK3Agg',
35 'wx': 'WXAgg',
35 'wx': 'WXAgg',
36 'qt': 'Qt4Agg', # qt3 not supported
36 'qt': 'Qt4Agg', # qt3 not supported
37 'qt4': 'Qt4Agg',
37 'qt4': 'Qt4Agg',
38 'qt5': 'Qt5Agg',
38 'osx': 'MacOSX',
39 'osx': 'MacOSX',
40 'nbagg': 'nbAgg',
39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
41 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
40
42
41 # We also need a reverse backends2guis mapping that will properly choose which
43 # We also need a reverse backends2guis mapping that will properly choose which
42 # GUI support to activate based on the desired matplotlib backend. For the
44 # GUI support to activate based on the desired matplotlib backend. For the
43 # most part it's just a reverse of the above dict, but we also need to add a
45 # most part it's just a reverse of the above dict, but we also need to add a
44 # few others that map to the same GUI manually:
46 # few others that map to the same GUI manually:
45 backend2gui = dict(zip(backends.values(), backends.keys()))
47 backend2gui = dict(zip(backends.values(), backends.keys()))
46 # Our tests expect backend2gui to just return 'qt'
48 # Our tests expect backend2gui to just return 'qt'
47 backend2gui['Qt4Agg'] = 'qt'
49 backend2gui['Qt4Agg'] = 'qt'
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
50 # In the reverse mapping, there are a few extra valid matplotlib backends that
49 # map to the same GUI support
51 # map to the same GUI support
50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
52 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
51 backend2gui['GTK3Cairo'] = 'gtk3'
53 backend2gui['GTK3Cairo'] = 'gtk3'
52 backend2gui['WX'] = 'wx'
54 backend2gui['WX'] = 'wx'
53 backend2gui['CocoaAgg'] = 'osx'
55 backend2gui['CocoaAgg'] = 'osx'
54
56
55 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
56 # Matplotlib utilities
58 # Matplotlib utilities
57 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
58
60
59
61
60 def getfigs(*fig_nums):
62 def getfigs(*fig_nums):
61 """Get a list of matplotlib figures by figure numbers.
63 """Get a list of matplotlib figures by figure numbers.
62
64
63 If no arguments are given, all available figures are returned. If the
65 If no arguments are given, all available figures are returned. If the
64 argument list contains references to invalid figures, a warning is printed
66 argument list contains references to invalid figures, a warning is printed
65 but the function continues pasting further figures.
67 but the function continues pasting further figures.
66
68
67 Parameters
69 Parameters
68 ----------
70 ----------
69 figs : tuple
71 figs : tuple
70 A tuple of ints giving the figure numbers of the figures to return.
72 A tuple of ints giving the figure numbers of the figures to return.
71 """
73 """
72 from matplotlib._pylab_helpers import Gcf
74 from matplotlib._pylab_helpers import Gcf
73 if not fig_nums:
75 if not fig_nums:
74 fig_managers = Gcf.get_all_fig_managers()
76 fig_managers = Gcf.get_all_fig_managers()
75 return [fm.canvas.figure for fm in fig_managers]
77 return [fm.canvas.figure for fm in fig_managers]
76 else:
78 else:
77 figs = []
79 figs = []
78 for num in fig_nums:
80 for num in fig_nums:
79 f = Gcf.figs.get(num)
81 f = Gcf.figs.get(num)
80 if f is None:
82 if f is None:
81 print('Warning: figure %s not available.' % num)
83 print('Warning: figure %s not available.' % num)
82 else:
84 else:
83 figs.append(f.canvas.figure)
85 figs.append(f.canvas.figure)
84 return figs
86 return figs
85
87
86
88
87 def figsize(sizex, sizey):
89 def figsize(sizex, sizey):
88 """Set the default figure size to be [sizex, sizey].
90 """Set the default figure size to be [sizex, sizey].
89
91
90 This is just an easy to remember, convenience wrapper that sets::
92 This is just an easy to remember, convenience wrapper that sets::
91
93
92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
93 """
95 """
94 import matplotlib
96 import matplotlib
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
97 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
96
98
97
99
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
100 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
99 """Print a figure to an image, and return the resulting file data
101 """Print a figure to an image, and return the resulting file data
100
102
101 Returned data will be bytes unless ``fmt='svg'``,
103 Returned data will be bytes unless ``fmt='svg'``,
102 in which case it will be unicode.
104 in which case it will be unicode.
103
105
104 Any keyword args are passed to fig.canvas.print_figure,
106 Any keyword args are passed to fig.canvas.print_figure,
105 such as ``quality`` or ``bbox_inches``.
107 such as ``quality`` or ``bbox_inches``.
106 """
108 """
107 from matplotlib import rcParams
109 from matplotlib import rcParams
108 # When there's an empty figure, we shouldn't return anything, otherwise we
110 # When there's an empty figure, we shouldn't return anything, otherwise we
109 # get big blank areas in the qt console.
111 # get big blank areas in the qt console.
110 if not fig.axes and not fig.lines:
112 if not fig.axes and not fig.lines:
111 return
113 return
112
114
113 dpi = rcParams['savefig.dpi']
115 dpi = rcParams['savefig.dpi']
114 if fmt == 'retina':
116 if fmt == 'retina':
115 dpi = dpi * 2
117 dpi = dpi * 2
116 fmt = 'png'
118 fmt = 'png'
117
119
118 # build keyword args
120 # build keyword args
119 kw = dict(
121 kw = dict(
120 format=fmt,
122 format=fmt,
121 facecolor=fig.get_facecolor(),
123 facecolor=fig.get_facecolor(),
122 edgecolor=fig.get_edgecolor(),
124 edgecolor=fig.get_edgecolor(),
123 dpi=dpi,
125 dpi=dpi,
124 bbox_inches=bbox_inches,
126 bbox_inches=bbox_inches,
125 )
127 )
126 # **kwargs get higher priority
128 # **kwargs get higher priority
127 kw.update(kwargs)
129 kw.update(kwargs)
128
130
129 bytes_io = BytesIO()
131 bytes_io = BytesIO()
130 fig.canvas.print_figure(bytes_io, **kw)
132 fig.canvas.print_figure(bytes_io, **kw)
131 data = bytes_io.getvalue()
133 data = bytes_io.getvalue()
132 if fmt == 'svg':
134 if fmt == 'svg':
133 data = data.decode('utf-8')
135 data = data.decode('utf-8')
134 return data
136 return data
135
137
136 def retina_figure(fig, **kwargs):
138 def retina_figure(fig, **kwargs):
137 """format a figure as a pixel-doubled (retina) PNG"""
139 """format a figure as a pixel-doubled (retina) PNG"""
138 pngdata = print_figure(fig, fmt='retina', **kwargs)
140 pngdata = print_figure(fig, fmt='retina', **kwargs)
139 w, h = _pngxy(pngdata)
141 w, h = _pngxy(pngdata)
140 metadata = dict(width=w//2, height=h//2)
142 metadata = dict(width=w//2, height=h//2)
141 return pngdata, metadata
143 return pngdata, metadata
142
144
143 # We need a little factory function here to create the closure where
145 # We need a little factory function here to create the closure where
144 # safe_execfile can live.
146 # safe_execfile can live.
145 def mpl_runner(safe_execfile):
147 def mpl_runner(safe_execfile):
146 """Factory to return a matplotlib-enabled runner for %run.
148 """Factory to return a matplotlib-enabled runner for %run.
147
149
148 Parameters
150 Parameters
149 ----------
151 ----------
150 safe_execfile : function
152 safe_execfile : function
151 This must be a function with the same interface as the
153 This must be a function with the same interface as the
152 :meth:`safe_execfile` method of IPython.
154 :meth:`safe_execfile` method of IPython.
153
155
154 Returns
156 Returns
155 -------
157 -------
156 A function suitable for use as the ``runner`` argument of the %run magic
158 A function suitable for use as the ``runner`` argument of the %run magic
157 function.
159 function.
158 """
160 """
159
161
160 def mpl_execfile(fname,*where,**kw):
162 def mpl_execfile(fname,*where,**kw):
161 """matplotlib-aware wrapper around safe_execfile.
163 """matplotlib-aware wrapper around safe_execfile.
162
164
163 Its interface is identical to that of the :func:`execfile` builtin.
165 Its interface is identical to that of the :func:`execfile` builtin.
164
166
165 This is ultimately a call to execfile(), but wrapped in safeties to
167 This is ultimately a call to execfile(), but wrapped in safeties to
166 properly handle interactive rendering."""
168 properly handle interactive rendering."""
167
169
168 import matplotlib
170 import matplotlib
169 import matplotlib.pylab as pylab
171 import matplotlib.pylab as pylab
170
172
171 #print '*** Matplotlib runner ***' # dbg
173 #print '*** Matplotlib runner ***' # dbg
172 # turn off rendering until end of script
174 # turn off rendering until end of script
173 is_interactive = matplotlib.rcParams['interactive']
175 is_interactive = matplotlib.rcParams['interactive']
174 matplotlib.interactive(False)
176 matplotlib.interactive(False)
175 safe_execfile(fname,*where,**kw)
177 safe_execfile(fname,*where,**kw)
176 matplotlib.interactive(is_interactive)
178 matplotlib.interactive(is_interactive)
177 # make rendering call now, if the user tried to do it
179 # make rendering call now, if the user tried to do it
178 if pylab.draw_if_interactive.called:
180 if pylab.draw_if_interactive.called:
179 pylab.draw()
181 pylab.draw()
180 pylab.draw_if_interactive.called = False
182 pylab.draw_if_interactive.called = False
181
183
182 return mpl_execfile
184 return mpl_execfile
183
185
184
186
185 def select_figure_formats(shell, formats, **kwargs):
187 def select_figure_formats(shell, formats, **kwargs):
186 """Select figure formats for the inline backend.
188 """Select figure formats for the inline backend.
187
189
188 Parameters
190 Parameters
189 ==========
191 ==========
190 shell : InteractiveShell
192 shell : InteractiveShell
191 The main IPython instance.
193 The main IPython instance.
192 formats : str or set
194 formats : str or set
193 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
195 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
194 **kwargs : any
196 **kwargs : any
195 Extra keyword arguments to be passed to fig.canvas.print_figure.
197 Extra keyword arguments to be passed to fig.canvas.print_figure.
196 """
198 """
197 from matplotlib.figure import Figure
199 from matplotlib.figure import Figure
198 from IPython.kernel.zmq.pylab import backend_inline
200 from IPython.kernel.zmq.pylab import backend_inline
199
201
200 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
202 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
201 png_formatter = shell.display_formatter.formatters['image/png']
203 png_formatter = shell.display_formatter.formatters['image/png']
202 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
204 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
203 pdf_formatter = shell.display_formatter.formatters['application/pdf']
205 pdf_formatter = shell.display_formatter.formatters['application/pdf']
204
206
205 if isinstance(formats, py3compat.string_types):
207 if isinstance(formats, py3compat.string_types):
206 formats = {formats}
208 formats = {formats}
207 # cast in case of list / tuple
209 # cast in case of list / tuple
208 formats = set(formats)
210 formats = set(formats)
209
211
210 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
212 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
211
213
212 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
214 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
213 bad = formats.difference(supported)
215 bad = formats.difference(supported)
214 if bad:
216 if bad:
215 bs = "%s" % ','.join([repr(f) for f in bad])
217 bs = "%s" % ','.join([repr(f) for f in bad])
216 gs = "%s" % ','.join([repr(f) for f in supported])
218 gs = "%s" % ','.join([repr(f) for f in supported])
217 raise ValueError("supported formats are: %s not %s" % (gs, bs))
219 raise ValueError("supported formats are: %s not %s" % (gs, bs))
218
220
219 if 'png' in formats:
221 if 'png' in formats:
220 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
222 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
221 if 'retina' in formats or 'png2x' in formats:
223 if 'retina' in formats or 'png2x' in formats:
222 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
224 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
223 if 'jpg' in formats or 'jpeg' in formats:
225 if 'jpg' in formats or 'jpeg' in formats:
224 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
226 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
225 if 'svg' in formats:
227 if 'svg' in formats:
226 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
228 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
227 if 'pdf' in formats:
229 if 'pdf' in formats:
228 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
230 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
229
231
230 #-----------------------------------------------------------------------------
232 #-----------------------------------------------------------------------------
231 # Code for initializing matplotlib and importing pylab
233 # Code for initializing matplotlib and importing pylab
232 #-----------------------------------------------------------------------------
234 #-----------------------------------------------------------------------------
233
235
234
236
235 def find_gui_and_backend(gui=None, gui_select=None):
237 def find_gui_and_backend(gui=None, gui_select=None):
236 """Given a gui string return the gui and mpl backend.
238 """Given a gui string return the gui and mpl backend.
237
239
238 Parameters
240 Parameters
239 ----------
241 ----------
240 gui : str
242 gui : str
241 Can be one of ('tk','gtk','wx','qt','qt4','inline').
243 Can be one of ('tk','gtk','wx','qt','qt4','inline').
242 gui_select : str
244 gui_select : str
243 Can be one of ('tk','gtk','wx','qt','qt4','inline').
245 Can be one of ('tk','gtk','wx','qt','qt4','inline').
244 This is any gui already selected by the shell.
246 This is any gui already selected by the shell.
245
247
246 Returns
248 Returns
247 -------
249 -------
248 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
250 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
249 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
251 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
250 """
252 """
251
253
252 import matplotlib
254 import matplotlib
253
255
254 if gui and gui != 'auto':
256 if gui and gui != 'auto':
255 # select backend based on requested gui
257 # select backend based on requested gui
256 backend = backends[gui]
258 backend = backends[gui]
257 else:
259 else:
258 # We need to read the backend from the original data structure, *not*
260 # We need to read the backend from the original data structure, *not*
259 # from mpl.rcParams, since a prior invocation of %matplotlib may have
261 # from mpl.rcParams, since a prior invocation of %matplotlib may have
260 # overwritten that.
262 # overwritten that.
261 # WARNING: this assumes matplotlib 1.1 or newer!!
263 # WARNING: this assumes matplotlib 1.1 or newer!!
262 backend = matplotlib.rcParamsOrig['backend']
264 backend = matplotlib.rcParamsOrig['backend']
263 # In this case, we need to find what the appropriate gui selection call
265 # In this case, we need to find what the appropriate gui selection call
264 # should be for IPython, so we can activate inputhook accordingly
266 # should be for IPython, so we can activate inputhook accordingly
265 gui = backend2gui.get(backend, None)
267 gui = backend2gui.get(backend, None)
266
268
267 # If we have already had a gui active, we need it and inline are the
269 # If we have already had a gui active, we need it and inline are the
268 # ones allowed.
270 # ones allowed.
269 if gui_select and gui != gui_select:
271 if gui_select and gui != gui_select:
270 gui = gui_select
272 gui = gui_select
271 backend = backends[gui]
273 backend = backends[gui]
272
274
273 return gui, backend
275 return gui, backend
274
276
275
277
276 def activate_matplotlib(backend):
278 def activate_matplotlib(backend):
277 """Activate the given backend and set interactive to True."""
279 """Activate the given backend and set interactive to True."""
278
280
279 import matplotlib
281 import matplotlib
280 matplotlib.interactive(True)
282 matplotlib.interactive(True)
281
283
282 # Matplotlib had a bug where even switch_backend could not force
284 # Matplotlib had a bug where even switch_backend could not force
283 # the rcParam to update. This needs to be set *before* the module
285 # the rcParam to update. This needs to be set *before* the module
284 # magic of switch_backend().
286 # magic of switch_backend().
285 matplotlib.rcParams['backend'] = backend
287 matplotlib.rcParams['backend'] = backend
286
288
287 import matplotlib.pyplot
289 import matplotlib.pyplot
288 matplotlib.pyplot.switch_backend(backend)
290 matplotlib.pyplot.switch_backend(backend)
289
291
290 # This must be imported last in the matplotlib series, after
292 # This must be imported last in the matplotlib series, after
291 # backend/interactivity choices have been made
293 # backend/interactivity choices have been made
292 import matplotlib.pylab as pylab
294 import matplotlib.pylab as pylab
293
295
294 pylab.show._needmain = False
296 pylab.show._needmain = False
295 # We need to detect at runtime whether show() is called by the user.
297 # We need to detect at runtime whether show() is called by the user.
296 # For this, we wrap it into a decorator which adds a 'called' flag.
298 # For this, we wrap it into a decorator which adds a 'called' flag.
297 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
299 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
298
300
299
301
300 def import_pylab(user_ns, import_all=True):
302 def import_pylab(user_ns, import_all=True):
301 """Populate the namespace with pylab-related values.
303 """Populate the namespace with pylab-related values.
302
304
303 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
305 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
304
306
305 Also imports a few names from IPython (figsize, display, getfigs)
307 Also imports a few names from IPython (figsize, display, getfigs)
306
308
307 """
309 """
308
310
309 # Import numpy as np/pyplot as plt are conventions we're trying to
311 # Import numpy as np/pyplot as plt are conventions we're trying to
310 # somewhat standardize on. Making them available to users by default
312 # somewhat standardize on. Making them available to users by default
311 # will greatly help this.
313 # will greatly help this.
312 s = ("import numpy\n"
314 s = ("import numpy\n"
313 "import matplotlib\n"
315 "import matplotlib\n"
314 "from matplotlib import pylab, mlab, pyplot\n"
316 "from matplotlib import pylab, mlab, pyplot\n"
315 "np = numpy\n"
317 "np = numpy\n"
316 "plt = pyplot\n"
318 "plt = pyplot\n"
317 )
319 )
318 exec(s, user_ns)
320 exec(s, user_ns)
319
321
320 if import_all:
322 if import_all:
321 s = ("from matplotlib.pylab import *\n"
323 s = ("from matplotlib.pylab import *\n"
322 "from numpy import *\n")
324 "from numpy import *\n")
323 exec(s, user_ns)
325 exec(s, user_ns)
324
326
325 # IPython symbols to add
327 # IPython symbols to add
326 user_ns['figsize'] = figsize
328 user_ns['figsize'] = figsize
327 from IPython.core.display import display
329 from IPython.core.display import display
328 # Add display and getfigs to the user's namespace
330 # Add display and getfigs to the user's namespace
329 user_ns['display'] = display
331 user_ns['display'] = display
330 user_ns['getfigs'] = getfigs
332 user_ns['getfigs'] = getfigs
331
333
332
334
333 def configure_inline_support(shell, backend):
335 def configure_inline_support(shell, backend):
334 """Configure an IPython shell object for matplotlib use.
336 """Configure an IPython shell object for matplotlib use.
335
337
336 Parameters
338 Parameters
337 ----------
339 ----------
338 shell : InteractiveShell instance
340 shell : InteractiveShell instance
339
341
340 backend : matplotlib backend
342 backend : matplotlib backend
341 """
343 """
342 # If using our svg payload backend, register the post-execution
344 # If using our svg payload backend, register the post-execution
343 # function that will pick up the results for display. This can only be
345 # function that will pick up the results for display. This can only be
344 # done with access to the real shell object.
346 # done with access to the real shell object.
345
347
346 # Note: if we can't load the inline backend, then there's no point
348 # Note: if we can't load the inline backend, then there's no point
347 # continuing (such as in terminal-only shells in environments without
349 # continuing (such as in terminal-only shells in environments without
348 # zeromq available).
350 # zeromq available).
349 try:
351 try:
350 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
352 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
351 except ImportError:
353 except ImportError:
352 return
354 return
353 from matplotlib import pyplot
355 from matplotlib import pyplot
354
356
355 cfg = InlineBackend.instance(parent=shell)
357 cfg = InlineBackend.instance(parent=shell)
356 cfg.shell = shell
358 cfg.shell = shell
357 if cfg not in shell.configurables:
359 if cfg not in shell.configurables:
358 shell.configurables.append(cfg)
360 shell.configurables.append(cfg)
359
361
360 if backend == backends['inline']:
362 if backend == backends['inline']:
361 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
363 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
362 shell.events.register('post_execute', flush_figures)
364 shell.events.register('post_execute', flush_figures)
363
365
364 # Save rcParams that will be overwrittern
366 # Save rcParams that will be overwrittern
365 shell._saved_rcParams = dict()
367 shell._saved_rcParams = dict()
366 for k in cfg.rc:
368 for k in cfg.rc:
367 shell._saved_rcParams[k] = pyplot.rcParams[k]
369 shell._saved_rcParams[k] = pyplot.rcParams[k]
368 # load inline_rc
370 # load inline_rc
369 pyplot.rcParams.update(cfg.rc)
371 pyplot.rcParams.update(cfg.rc)
370 else:
372 else:
371 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
373 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
372 try:
374 try:
373 shell.events.unregister('post_execute', flush_figures)
375 shell.events.unregister('post_execute', flush_figures)
374 except ValueError:
376 except ValueError:
375 pass
377 pass
376 if hasattr(shell, '_saved_rcParams'):
378 if hasattr(shell, '_saved_rcParams'):
377 pyplot.rcParams.update(shell._saved_rcParams)
379 pyplot.rcParams.update(shell._saved_rcParams)
378 del shell._saved_rcParams
380 del shell._saved_rcParams
379
381
380 # Setup the default figure format
382 # Setup the default figure format
381 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
383 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
382
384
@@ -1,255 +1,263 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Event loop integration for the ZeroMQ-based kernels.
2 """Event loop integration for the ZeroMQ-based kernels.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2011 The IPython Development Team
6 # Copyright (C) 2011 The IPython Development Team
7
7
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import sys
18 import sys
18
19
19 # System library imports
20 # System library imports
20 import zmq
21 import zmq
21
22
22 # Local imports
23 # Local imports
23 from IPython.config.application import Application
24 from IPython.config.application import Application
24 from IPython.utils import io
25 from IPython.utils import io
25
26
26
27
27 #------------------------------------------------------------------------------
28 #------------------------------------------------------------------------------
28 # Eventloops for integrating the Kernel into different GUIs
29 # Eventloops for integrating the Kernel into different GUIs
29 #------------------------------------------------------------------------------
30 #------------------------------------------------------------------------------
30
31
31 def _on_os_x_10_9():
32 def _on_os_x_10_9():
32 import platform
33 import platform
33 from distutils.version import LooseVersion as V
34 from distutils.version import LooseVersion as V
34 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
35 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
35
36
36 def _notify_stream_qt(kernel, stream):
37 def _notify_stream_qt(kernel, stream):
37
38
38 from IPython.external.qt_for_kernel import QtCore
39 from IPython.external.qt_for_kernel import QtCore
39
40
40 if _on_os_x_10_9() and kernel._darwin_app_nap:
41 if _on_os_x_10_9() and kernel._darwin_app_nap:
41 from IPython.external.appnope import nope_scope as context
42 from IPython.external.appnope import nope_scope as context
42 else:
43 else:
43 from IPython.core.interactiveshell import NoOpContext as context
44 from IPython.core.interactiveshell import NoOpContext as context
44
45
45 def process_stream_events():
46 def process_stream_events():
46 while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN:
47 while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN:
47 with context():
48 with context():
48 kernel.do_one_iteration()
49 kernel.do_one_iteration()
49
50
50 fd = stream.getsockopt(zmq.FD)
51 fd = stream.getsockopt(zmq.FD)
51 notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app)
52 notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app)
52 notifier.activated.connect(process_stream_events)
53 notifier.activated.connect(process_stream_events)
53
54
54 def loop_qt4(kernel):
55 def loop_qt4(kernel):
55 """Start a kernel with PyQt4 event loop integration."""
56 """Start a kernel with PyQt4 event loop integration."""
56
57
57 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
58 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
58
59
59 kernel.app = get_app_qt4([" "])
60 kernel.app = get_app_qt4([" "])
60 kernel.app.setQuitOnLastWindowClosed(False)
61 kernel.app.setQuitOnLastWindowClosed(False)
61
62
62 for s in kernel.shell_streams:
63 for s in kernel.shell_streams:
63 _notify_stream_qt(kernel, s)
64 _notify_stream_qt(kernel, s)
64
65
65 start_event_loop_qt4(kernel.app)
66 start_event_loop_qt4(kernel.app)
66
67
68 def loop_qt5(kernel):
69 """Start a kernel with PyQt5 event loop integration"""
70 os.environ['QT_API'] = 'pyqt5'
71 return loop_qt4(kernel)
72
67
73
68 def loop_wx(kernel):
74 def loop_wx(kernel):
69 """Start a kernel with wx event loop support."""
75 """Start a kernel with wx event loop support."""
70
76
71 import wx
77 import wx
72 from IPython.lib.guisupport import start_event_loop_wx
78 from IPython.lib.guisupport import start_event_loop_wx
73
79
74 if _on_os_x_10_9() and kernel._darwin_app_nap:
80 if _on_os_x_10_9() and kernel._darwin_app_nap:
75 # we don't hook up App Nap contexts for Wx,
81 # we don't hook up App Nap contexts for Wx,
76 # just disable it outright.
82 # just disable it outright.
77 from IPython.external.appnope import nope
83 from IPython.external.appnope import nope
78 nope()
84 nope()
79
85
80 doi = kernel.do_one_iteration
86 doi = kernel.do_one_iteration
81 # Wx uses milliseconds
87 # Wx uses milliseconds
82 poll_interval = int(1000*kernel._poll_interval)
88 poll_interval = int(1000*kernel._poll_interval)
83
89
84 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
90 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
85 # We make the Frame hidden when we create it in the main app below.
91 # We make the Frame hidden when we create it in the main app below.
86 class TimerFrame(wx.Frame):
92 class TimerFrame(wx.Frame):
87 def __init__(self, func):
93 def __init__(self, func):
88 wx.Frame.__init__(self, None, -1)
94 wx.Frame.__init__(self, None, -1)
89 self.timer = wx.Timer(self)
95 self.timer = wx.Timer(self)
90 # Units for the timer are in milliseconds
96 # Units for the timer are in milliseconds
91 self.timer.Start(poll_interval)
97 self.timer.Start(poll_interval)
92 self.Bind(wx.EVT_TIMER, self.on_timer)
98 self.Bind(wx.EVT_TIMER, self.on_timer)
93 self.func = func
99 self.func = func
94
100
95 def on_timer(self, event):
101 def on_timer(self, event):
96 self.func()
102 self.func()
97
103
98 # We need a custom wx.App to create our Frame subclass that has the
104 # We need a custom wx.App to create our Frame subclass that has the
99 # wx.Timer to drive the ZMQ event loop.
105 # wx.Timer to drive the ZMQ event loop.
100 class IPWxApp(wx.App):
106 class IPWxApp(wx.App):
101 def OnInit(self):
107 def OnInit(self):
102 self.frame = TimerFrame(doi)
108 self.frame = TimerFrame(doi)
103 self.frame.Show(False)
109 self.frame.Show(False)
104 return True
110 return True
105
111
106 # The redirect=False here makes sure that wx doesn't replace
112 # The redirect=False here makes sure that wx doesn't replace
107 # sys.stdout/stderr with its own classes.
113 # sys.stdout/stderr with its own classes.
108 kernel.app = IPWxApp(redirect=False)
114 kernel.app = IPWxApp(redirect=False)
109
115
110 # The import of wx on Linux sets the handler for signal.SIGINT
116 # The import of wx on Linux sets the handler for signal.SIGINT
111 # to 0. This is a bug in wx or gtk. We fix by just setting it
117 # to 0. This is a bug in wx or gtk. We fix by just setting it
112 # back to the Python default.
118 # back to the Python default.
113 import signal
119 import signal
114 if not callable(signal.getsignal(signal.SIGINT)):
120 if not callable(signal.getsignal(signal.SIGINT)):
115 signal.signal(signal.SIGINT, signal.default_int_handler)
121 signal.signal(signal.SIGINT, signal.default_int_handler)
116
122
117 start_event_loop_wx(kernel.app)
123 start_event_loop_wx(kernel.app)
118
124
119
125
120 def loop_tk(kernel):
126 def loop_tk(kernel):
121 """Start a kernel with the Tk event loop."""
127 """Start a kernel with the Tk event loop."""
122
128
123 try:
129 try:
124 from tkinter import Tk # Py 3
130 from tkinter import Tk # Py 3
125 except ImportError:
131 except ImportError:
126 from Tkinter import Tk # Py 2
132 from Tkinter import Tk # Py 2
127 doi = kernel.do_one_iteration
133 doi = kernel.do_one_iteration
128 # Tk uses milliseconds
134 # Tk uses milliseconds
129 poll_interval = int(1000*kernel._poll_interval)
135 poll_interval = int(1000*kernel._poll_interval)
130 # For Tkinter, we create a Tk object and call its withdraw method.
136 # For Tkinter, we create a Tk object and call its withdraw method.
131 class Timer(object):
137 class Timer(object):
132 def __init__(self, func):
138 def __init__(self, func):
133 self.app = Tk()
139 self.app = Tk()
134 self.app.withdraw()
140 self.app.withdraw()
135 self.func = func
141 self.func = func
136
142
137 def on_timer(self):
143 def on_timer(self):
138 self.func()
144 self.func()
139 self.app.after(poll_interval, self.on_timer)
145 self.app.after(poll_interval, self.on_timer)
140
146
141 def start(self):
147 def start(self):
142 self.on_timer() # Call it once to get things going.
148 self.on_timer() # Call it once to get things going.
143 self.app.mainloop()
149 self.app.mainloop()
144
150
145 kernel.timer = Timer(doi)
151 kernel.timer = Timer(doi)
146 kernel.timer.start()
152 kernel.timer.start()
147
153
148
154
149 def loop_gtk(kernel):
155 def loop_gtk(kernel):
150 """Start the kernel, coordinating with the GTK event loop"""
156 """Start the kernel, coordinating with the GTK event loop"""
151 from .gui.gtkembed import GTKEmbed
157 from .gui.gtkembed import GTKEmbed
152
158
153 gtk_kernel = GTKEmbed(kernel)
159 gtk_kernel = GTKEmbed(kernel)
154 gtk_kernel.start()
160 gtk_kernel.start()
155
161
156
162
157 def loop_cocoa(kernel):
163 def loop_cocoa(kernel):
158 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
164 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
159 via the matplotlib MacOSX backend.
165 via the matplotlib MacOSX backend.
160 """
166 """
161 import matplotlib
167 import matplotlib
162 if matplotlib.__version__ < '1.1.0':
168 if matplotlib.__version__ < '1.1.0':
163 kernel.log.warn(
169 kernel.log.warn(
164 "MacOSX backend in matplotlib %s doesn't have a Timer, "
170 "MacOSX backend in matplotlib %s doesn't have a Timer, "
165 "falling back on Tk for CFRunLoop integration. Note that "
171 "falling back on Tk for CFRunLoop integration. Note that "
166 "even this won't work if Tk is linked against X11 instead of "
172 "even this won't work if Tk is linked against X11 instead of "
167 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
173 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
168 "you must use matplotlib >= 1.1.0, or a native libtk."
174 "you must use matplotlib >= 1.1.0, or a native libtk."
169 )
175 )
170 return loop_tk(kernel)
176 return loop_tk(kernel)
171
177
172 from matplotlib.backends.backend_macosx import TimerMac, show
178 from matplotlib.backends.backend_macosx import TimerMac, show
173
179
174 # scale interval for sec->ms
180 # scale interval for sec->ms
175 poll_interval = int(1000*kernel._poll_interval)
181 poll_interval = int(1000*kernel._poll_interval)
176
182
177 real_excepthook = sys.excepthook
183 real_excepthook = sys.excepthook
178 def handle_int(etype, value, tb):
184 def handle_int(etype, value, tb):
179 """don't let KeyboardInterrupts look like crashes"""
185 """don't let KeyboardInterrupts look like crashes"""
180 if etype is KeyboardInterrupt:
186 if etype is KeyboardInterrupt:
181 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
187 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
182 else:
188 else:
183 real_excepthook(etype, value, tb)
189 real_excepthook(etype, value, tb)
184
190
185 # add doi() as a Timer to the CFRunLoop
191 # add doi() as a Timer to the CFRunLoop
186 def doi():
192 def doi():
187 # restore excepthook during IPython code
193 # restore excepthook during IPython code
188 sys.excepthook = real_excepthook
194 sys.excepthook = real_excepthook
189 kernel.do_one_iteration()
195 kernel.do_one_iteration()
190 # and back:
196 # and back:
191 sys.excepthook = handle_int
197 sys.excepthook = handle_int
192
198
193 t = TimerMac(poll_interval)
199 t = TimerMac(poll_interval)
194 t.add_callback(doi)
200 t.add_callback(doi)
195 t.start()
201 t.start()
196
202
197 # but still need a Poller for when there are no active windows,
203 # but still need a Poller for when there are no active windows,
198 # during which time mainloop() returns immediately
204 # during which time mainloop() returns immediately
199 poller = zmq.Poller()
205 poller = zmq.Poller()
200 if kernel.control_stream:
206 if kernel.control_stream:
201 poller.register(kernel.control_stream.socket, zmq.POLLIN)
207 poller.register(kernel.control_stream.socket, zmq.POLLIN)
202 for stream in kernel.shell_streams:
208 for stream in kernel.shell_streams:
203 poller.register(stream.socket, zmq.POLLIN)
209 poller.register(stream.socket, zmq.POLLIN)
204
210
205 while True:
211 while True:
206 try:
212 try:
207 # double nested try/except, to properly catch KeyboardInterrupt
213 # double nested try/except, to properly catch KeyboardInterrupt
208 # due to pyzmq Issue #130
214 # due to pyzmq Issue #130
209 try:
215 try:
210 # don't let interrupts during mainloop invoke crash_handler:
216 # don't let interrupts during mainloop invoke crash_handler:
211 sys.excepthook = handle_int
217 sys.excepthook = handle_int
212 show.mainloop()
218 show.mainloop()
213 sys.excepthook = real_excepthook
219 sys.excepthook = real_excepthook
214 # use poller if mainloop returned (no windows)
220 # use poller if mainloop returned (no windows)
215 # scale by extra factor of 10, since it's a real poll
221 # scale by extra factor of 10, since it's a real poll
216 poller.poll(10*poll_interval)
222 poller.poll(10*poll_interval)
217 kernel.do_one_iteration()
223 kernel.do_one_iteration()
218 except:
224 except:
219 raise
225 raise
220 except KeyboardInterrupt:
226 except KeyboardInterrupt:
221 # Ctrl-C shouldn't crash the kernel
227 # Ctrl-C shouldn't crash the kernel
222 io.raw_print("KeyboardInterrupt caught in kernel")
228 io.raw_print("KeyboardInterrupt caught in kernel")
223 finally:
229 finally:
224 # ensure excepthook is restored
230 # ensure excepthook is restored
225 sys.excepthook = real_excepthook
231 sys.excepthook = real_excepthook
226
232
227 # mapping of keys to loop functions
233 # mapping of keys to loop functions
228 loop_map = {
234 loop_map = {
229 'qt' : loop_qt4,
235 'qt' : loop_qt4,
230 'qt4': loop_qt4,
236 'qt4': loop_qt4,
237 'qt5': loop_qt5,
231 'inline': None,
238 'inline': None,
239 'nbagg': None,
232 'osx': loop_cocoa,
240 'osx': loop_cocoa,
233 'wx' : loop_wx,
241 'wx' : loop_wx,
234 'tk' : loop_tk,
242 'tk' : loop_tk,
235 'gtk': loop_gtk,
243 'gtk': loop_gtk,
236 None : None,
244 None : None,
237 }
245 }
238
246
239
247
240 def enable_gui(gui, kernel=None):
248 def enable_gui(gui, kernel=None):
241 """Enable integration with a given GUI"""
249 """Enable integration with a given GUI"""
242 if gui not in loop_map:
250 if gui not in loop_map:
243 e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys())
251 e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys())
244 raise ValueError(e)
252 raise ValueError(e)
245 if kernel is None:
253 if kernel is None:
246 if Application.initialized():
254 if Application.initialized():
247 kernel = getattr(Application.instance(), 'kernel', None)
255 kernel = getattr(Application.instance(), 'kernel', None)
248 if kernel is None:
256 if kernel is None:
249 raise RuntimeError("You didn't specify a kernel,"
257 raise RuntimeError("You didn't specify a kernel,"
250 " and no IPython Application with a kernel appears to be running."
258 " and no IPython Application with a kernel appears to be running."
251 )
259 )
252 loop = loop_map[gui]
260 loop = loop_map[gui]
253 if loop and kernel.eventloop is not None and kernel.eventloop is not loop:
261 if loop and kernel.eventloop is not None and kernel.eventloop is not loop:
254 raise RuntimeError("Cannot activate multiple GUI eventloops")
262 raise RuntimeError("Cannot activate multiple GUI eventloops")
255 kernel.eventloop = loop
263 kernel.eventloop = loop
General Comments 0
You need to be logged in to leave comments. Login now