##// END OF EJS Templates
Make PNG images in the Qt console work in Python 3.
Grahame Bowland -
Show More
@@ -1,252 +1,252 b''
1 # Standard libary imports.
1 # Standard libary imports.
2 from base64 import decodestring
2 from base64 import decodestring
3 import os
3 import os
4 import re
4 import re
5
5
6 # System libary imports.
6 # System libary imports.
7 from IPython.external.qt import QtCore, QtGui
7 from IPython.external.qt import QtCore, QtGui
8
8
9 # Local imports
9 # Local imports
10 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
10 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
11 from ipython_widget import IPythonWidget
11 from ipython_widget import IPythonWidget
12
12
13
13
14 class RichIPythonWidget(IPythonWidget):
14 class RichIPythonWidget(IPythonWidget):
15 """ An IPythonWidget that supports rich text, including lists, images, and
15 """ An IPythonWidget that supports rich text, including lists, images, and
16 tables. Note that raw performance will be reduced compared to the plain
16 tables. Note that raw performance will be reduced compared to the plain
17 text version.
17 text version.
18 """
18 """
19
19
20 # RichIPythonWidget protected class variables.
20 # RichIPythonWidget protected class variables.
21 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
21 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
22
22
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24 # 'object' interface
24 # 'object' interface
25 #---------------------------------------------------------------------------
25 #---------------------------------------------------------------------------
26
26
27 def __init__(self, *args, **kw):
27 def __init__(self, *args, **kw):
28 """ Create a RichIPythonWidget.
28 """ Create a RichIPythonWidget.
29 """
29 """
30 kw['kind'] = 'rich'
30 kw['kind'] = 'rich'
31 super(RichIPythonWidget, self).__init__(*args, **kw)
31 super(RichIPythonWidget, self).__init__(*args, **kw)
32
32
33 # Configure the ConsoleWidget HTML exporter for our formats.
33 # Configure the ConsoleWidget HTML exporter for our formats.
34 self._html_exporter.image_tag = self._get_image_tag
34 self._html_exporter.image_tag = self._get_image_tag
35
35
36 # Dictionary for resolving document resource names to SVG data.
36 # Dictionary for resolving document resource names to SVG data.
37 self._name_to_svg_map = {}
37 self._name_to_svg_map = {}
38
38
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40 # 'ConsoleWidget' protected interface
40 # 'ConsoleWidget' protected interface
41 #---------------------------------------------------------------------------
41 #---------------------------------------------------------------------------
42
42
43 def _context_menu_make(self, pos):
43 def _context_menu_make(self, pos):
44 """ Reimplemented to return a custom context menu for images.
44 """ Reimplemented to return a custom context menu for images.
45 """
45 """
46 format = self._control.cursorForPosition(pos).charFormat()
46 format = self._control.cursorForPosition(pos).charFormat()
47 name = format.stringProperty(QtGui.QTextFormat.ImageName)
47 name = format.stringProperty(QtGui.QTextFormat.ImageName)
48 if name:
48 if name:
49 menu = QtGui.QMenu()
49 menu = QtGui.QMenu()
50
50
51 menu.addAction('Copy Image', lambda: self._copy_image(name))
51 menu.addAction('Copy Image', lambda: self._copy_image(name))
52 menu.addAction('Save Image As...', lambda: self._save_image(name))
52 menu.addAction('Save Image As...', lambda: self._save_image(name))
53 menu.addSeparator()
53 menu.addSeparator()
54
54
55 svg = self._name_to_svg_map.get(name, None)
55 svg = self._name_to_svg_map.get(name, None)
56 if svg is not None:
56 if svg is not None:
57 menu.addSeparator()
57 menu.addSeparator()
58 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
58 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
59 menu.addAction('Save SVG As...',
59 menu.addAction('Save SVG As...',
60 lambda: save_svg(svg, self._control))
60 lambda: save_svg(svg, self._control))
61 else:
61 else:
62 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
62 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
63 return menu
63 return menu
64
64
65 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
66 # 'BaseFrontendMixin' abstract interface
66 # 'BaseFrontendMixin' abstract interface
67 #---------------------------------------------------------------------------
67 #---------------------------------------------------------------------------
68
68
69 def _handle_pyout(self, msg):
69 def _handle_pyout(self, msg):
70 """ Overridden to handle rich data types, like SVG.
70 """ Overridden to handle rich data types, like SVG.
71 """
71 """
72 if not self._hidden and self._is_from_this_session(msg):
72 if not self._hidden and self._is_from_this_session(msg):
73 content = msg['content']
73 content = msg['content']
74 prompt_number = content['execution_count']
74 prompt_number = content['execution_count']
75 data = content['data']
75 data = content['data']
76 if data.has_key('image/svg+xml'):
76 if data.has_key('image/svg+xml'):
77 self._append_plain_text(self.output_sep, True)
77 self._append_plain_text(self.output_sep, True)
78 self._append_html(self._make_out_prompt(prompt_number), True)
78 self._append_html(self._make_out_prompt(prompt_number), True)
79 self._append_svg(data['image/svg+xml'], True)
79 self._append_svg(data['image/svg+xml'], True)
80 self._append_html(self.output_sep2, True)
80 self._append_html(self.output_sep2, True)
81 elif data.has_key('image/png'):
81 elif data.has_key('image/png'):
82 self._append_plain_text(self.output_sep, True)
82 self._append_plain_text(self.output_sep, True)
83 self._append_html(self._make_out_prompt(prompt_number), True)
83 self._append_html(self._make_out_prompt(prompt_number), True)
84 # This helps the output to look nice.
84 # This helps the output to look nice.
85 self._append_plain_text('\n', True)
85 self._append_plain_text('\n', True)
86 self._append_png(decodestring(data['image/png']), True)
86 self._append_png(decodestring(data['image/png'].encode('ascii')), True)
87 self._append_html(self.output_sep2, True)
87 self._append_html(self.output_sep2, True)
88 else:
88 else:
89 # Default back to the plain text representation.
89 # Default back to the plain text representation.
90 return super(RichIPythonWidget, self)._handle_pyout(msg)
90 return super(RichIPythonWidget, self)._handle_pyout(msg)
91
91
92 def _handle_display_data(self, msg):
92 def _handle_display_data(self, msg):
93 """ Overridden to handle rich data types, like SVG.
93 """ Overridden to handle rich data types, like SVG.
94 """
94 """
95 if not self._hidden and self._is_from_this_session(msg):
95 if not self._hidden and self._is_from_this_session(msg):
96 source = msg['content']['source']
96 source = msg['content']['source']
97 data = msg['content']['data']
97 data = msg['content']['data']
98 metadata = msg['content']['metadata']
98 metadata = msg['content']['metadata']
99 # Try to use the svg or html representations.
99 # Try to use the svg or html representations.
100 # FIXME: Is this the right ordering of things to try?
100 # FIXME: Is this the right ordering of things to try?
101 if data.has_key('image/svg+xml'):
101 if data.has_key('image/svg+xml'):
102 svg = data['image/svg+xml']
102 svg = data['image/svg+xml']
103 self._append_svg(svg, True)
103 self._append_svg(svg, True)
104 elif data.has_key('image/png'):
104 elif data.has_key('image/png'):
105 # PNG data is base64 encoded as it passes over the network
105 # PNG data is base64 encoded as it passes over the network
106 # in a JSON structure so we decode it.
106 # in a JSON structure so we decode it.
107 png = decodestring(data['image/png'])
107 png = decodestring(data['image/png'].encode('ascii'))
108 self._append_png(png, True)
108 self._append_png(png, True)
109 else:
109 else:
110 # Default back to the plain text representation.
110 # Default back to the plain text representation.
111 return super(RichIPythonWidget, self)._handle_display_data(msg)
111 return super(RichIPythonWidget, self)._handle_display_data(msg)
112
112
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114 # 'RichIPythonWidget' protected interface
114 # 'RichIPythonWidget' protected interface
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116
116
117 def _append_png(self, png, before_prompt=False):
117 def _append_png(self, png, before_prompt=False):
118 """ Append raw PNG data to the widget.
118 """ Append raw PNG data to the widget.
119 """
119 """
120 self._append_custom(self._insert_png, png, before_prompt)
120 self._append_custom(self._insert_png, png, before_prompt)
121
121
122 def _append_svg(self, svg, before_prompt=False):
122 def _append_svg(self, svg, before_prompt=False):
123 """ Append raw SVG data to the widget.
123 """ Append raw SVG data to the widget.
124 """
124 """
125 self._append_custom(self._insert_svg, svg, before_prompt)
125 self._append_custom(self._insert_svg, svg, before_prompt)
126
126
127 def _add_image(self, image):
127 def _add_image(self, image):
128 """ Adds the specified QImage to the document and returns a
128 """ Adds the specified QImage to the document and returns a
129 QTextImageFormat that references it.
129 QTextImageFormat that references it.
130 """
130 """
131 document = self._control.document()
131 document = self._control.document()
132 name = str(image.cacheKey())
132 name = str(image.cacheKey())
133 document.addResource(QtGui.QTextDocument.ImageResource,
133 document.addResource(QtGui.QTextDocument.ImageResource,
134 QtCore.QUrl(name), image)
134 QtCore.QUrl(name), image)
135 format = QtGui.QTextImageFormat()
135 format = QtGui.QTextImageFormat()
136 format.setName(name)
136 format.setName(name)
137 return format
137 return format
138
138
139 def _copy_image(self, name):
139 def _copy_image(self, name):
140 """ Copies the ImageResource with 'name' to the clipboard.
140 """ Copies the ImageResource with 'name' to the clipboard.
141 """
141 """
142 image = self._get_image(name)
142 image = self._get_image(name)
143 QtGui.QApplication.clipboard().setImage(image)
143 QtGui.QApplication.clipboard().setImage(image)
144
144
145 def _get_image(self, name):
145 def _get_image(self, name):
146 """ Returns the QImage stored as the ImageResource with 'name'.
146 """ Returns the QImage stored as the ImageResource with 'name'.
147 """
147 """
148 document = self._control.document()
148 document = self._control.document()
149 image = document.resource(QtGui.QTextDocument.ImageResource,
149 image = document.resource(QtGui.QTextDocument.ImageResource,
150 QtCore.QUrl(name))
150 QtCore.QUrl(name))
151 return image
151 return image
152
152
153 def _get_image_tag(self, match, path = None, format = "png"):
153 def _get_image_tag(self, match, path = None, format = "png"):
154 """ Return (X)HTML mark-up for the image-tag given by match.
154 """ Return (X)HTML mark-up for the image-tag given by match.
155
155
156 Parameters
156 Parameters
157 ----------
157 ----------
158 match : re.SRE_Match
158 match : re.SRE_Match
159 A match to an HTML image tag as exported by Qt, with
159 A match to an HTML image tag as exported by Qt, with
160 match.group("Name") containing the matched image ID.
160 match.group("Name") containing the matched image ID.
161
161
162 path : string|None, optional [default None]
162 path : string|None, optional [default None]
163 If not None, specifies a path to which supporting files may be
163 If not None, specifies a path to which supporting files may be
164 written (e.g., for linked images). If None, all images are to be
164 written (e.g., for linked images). If None, all images are to be
165 included inline.
165 included inline.
166
166
167 format : "png"|"svg", optional [default "png"]
167 format : "png"|"svg", optional [default "png"]
168 Format for returned or referenced images.
168 Format for returned or referenced images.
169 """
169 """
170 if format == "png":
170 if format == "png":
171 try:
171 try:
172 image = self._get_image(match.group("name"))
172 image = self._get_image(match.group("name"))
173 except KeyError:
173 except KeyError:
174 return "<b>Couldn't find image %s</b>" % match.group("name")
174 return "<b>Couldn't find image %s</b>" % match.group("name")
175
175
176 if path is not None:
176 if path is not None:
177 if not os.path.exists(path):
177 if not os.path.exists(path):
178 os.mkdir(path)
178 os.mkdir(path)
179 relpath = os.path.basename(path)
179 relpath = os.path.basename(path)
180 if image.save("%s/qt_img%s.png" % (path,match.group("name")),
180 if image.save("%s/qt_img%s.png" % (path,match.group("name")),
181 "PNG"):
181 "PNG"):
182 return '<img src="%s/qt_img%s.png">' % (relpath,
182 return '<img src="%s/qt_img%s.png">' % (relpath,
183 match.group("name"))
183 match.group("name"))
184 else:
184 else:
185 return "<b>Couldn't save image!</b>"
185 return "<b>Couldn't save image!</b>"
186 else:
186 else:
187 ba = QtCore.QByteArray()
187 ba = QtCore.QByteArray()
188 buffer_ = QtCore.QBuffer(ba)
188 buffer_ = QtCore.QBuffer(ba)
189 buffer_.open(QtCore.QIODevice.WriteOnly)
189 buffer_.open(QtCore.QIODevice.WriteOnly)
190 image.save(buffer_, "PNG")
190 image.save(buffer_, "PNG")
191 buffer_.close()
191 buffer_.close()
192 return '<img src="data:image/png;base64,\n%s\n" />' % (
192 return '<img src="data:image/png;base64,\n%s\n" />' % (
193 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
193 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
194
194
195 elif format == "svg":
195 elif format == "svg":
196 try:
196 try:
197 svg = str(self._name_to_svg_map[match.group("name")])
197 svg = str(self._name_to_svg_map[match.group("name")])
198 except KeyError:
198 except KeyError:
199 return "<b>Couldn't find image %s</b>" % match.group("name")
199 return "<b>Couldn't find image %s</b>" % match.group("name")
200
200
201 # Not currently checking path, because it's tricky to find a
201 # Not currently checking path, because it's tricky to find a
202 # cross-browser way to embed external SVG images (e.g., via
202 # cross-browser way to embed external SVG images (e.g., via
203 # object or embed tags).
203 # object or embed tags).
204
204
205 # Chop stand-alone header from matplotlib SVG
205 # Chop stand-alone header from matplotlib SVG
206 offset = svg.find("<svg")
206 offset = svg.find("<svg")
207 assert(offset > -1)
207 assert(offset > -1)
208
208
209 return svg[offset:]
209 return svg[offset:]
210
210
211 else:
211 else:
212 return '<b>Unrecognized image format</b>'
212 return '<b>Unrecognized image format</b>'
213
213
214 def _insert_png(self, cursor, png):
214 def _insert_png(self, cursor, png):
215 """ Insert raw PNG data into the widget.
215 """ Insert raw PNG data into the widget.
216 """
216 """
217 try:
217 try:
218 image = QtGui.QImage()
218 image = QtGui.QImage()
219 image.loadFromData(png, 'PNG')
219 image.loadFromData(png, 'PNG')
220 except ValueError:
220 except ValueError:
221 self._insert_plain_text(cursor, 'Received invalid PNG data.')
221 self._insert_plain_text(cursor, 'Received invalid PNG data.')
222 else:
222 else:
223 format = self._add_image(image)
223 format = self._add_image(image)
224 cursor.insertBlock()
224 cursor.insertBlock()
225 cursor.insertImage(format)
225 cursor.insertImage(format)
226 cursor.insertBlock()
226 cursor.insertBlock()
227
227
228 def _insert_svg(self, cursor, svg):
228 def _insert_svg(self, cursor, svg):
229 """ Insert raw SVG data into the widet.
229 """ Insert raw SVG data into the widet.
230 """
230 """
231 try:
231 try:
232 image = svg_to_image(svg)
232 image = svg_to_image(svg)
233 except ValueError:
233 except ValueError:
234 self._insert_plain_text(cursor, 'Received invalid SVG data.')
234 self._insert_plain_text(cursor, 'Received invalid SVG data.')
235 else:
235 else:
236 format = self._add_image(image)
236 format = self._add_image(image)
237 self._name_to_svg_map[format.name()] = svg
237 self._name_to_svg_map[format.name()] = svg
238 cursor.insertBlock()
238 cursor.insertBlock()
239 cursor.insertImage(format)
239 cursor.insertImage(format)
240 cursor.insertBlock()
240 cursor.insertBlock()
241
241
242 def _save_image(self, name, format='PNG'):
242 def _save_image(self, name, format='PNG'):
243 """ Shows a save dialog for the ImageResource with 'name'.
243 """ Shows a save dialog for the ImageResource with 'name'.
244 """
244 """
245 dialog = QtGui.QFileDialog(self._control, 'Save Image')
245 dialog = QtGui.QFileDialog(self._control, 'Save Image')
246 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
246 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
247 dialog.setDefaultSuffix(format.lower())
247 dialog.setDefaultSuffix(format.lower())
248 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
248 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
249 if dialog.exec_():
249 if dialog.exec_():
250 filename = dialog.selectedFiles()[0]
250 filename = dialog.selectedFiles()[0]
251 image = self._get_image(name)
251 image = self._get_image(name)
252 image.save(filename, format)
252 image.save(filename, format)
@@ -1,322 +1,322 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
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 from cStringIO import StringIO
22 from io import BytesIO
23
23
24 from IPython.utils.decorators import flag_calls
24 from IPython.utils.decorators import flag_calls
25
25
26 # If user specifies a GUI, that dictates the backend, otherwise we read the
26 # If user specifies a GUI, that dictates the backend, otherwise we read the
27 # user's mpl default from the mpl rc structure
27 # user's mpl default from the mpl rc structure
28 backends = {'tk': 'TkAgg',
28 backends = {'tk': 'TkAgg',
29 'gtk': 'GTKAgg',
29 'gtk': 'GTKAgg',
30 'wx': 'WXAgg',
30 'wx': 'WXAgg',
31 'qt': 'Qt4Agg', # qt3 not supported
31 'qt': 'Qt4Agg', # qt3 not supported
32 'qt4': 'Qt4Agg',
32 'qt4': 'Qt4Agg',
33 'osx': 'MacOSX',
33 'osx': 'MacOSX',
34 'inline' : 'module://IPython.zmq.pylab.backend_inline'}
34 'inline' : 'module://IPython.zmq.pylab.backend_inline'}
35
35
36 # We also need a reverse backends2guis mapping that will properly choose which
36 # We also need a reverse backends2guis mapping that will properly choose which
37 # GUI support to activate based on the desired matplotlib backend. For the
37 # GUI support to activate based on the desired matplotlib backend. For the
38 # most part it's just a reverse of the above dict, but we also need to add a
38 # most part it's just a reverse of the above dict, but we also need to add a
39 # few others that map to the same GUI manually:
39 # few others that map to the same GUI manually:
40 backend2gui = dict(zip(backends.values(), backends.keys()))
40 backend2gui = dict(zip(backends.values(), backends.keys()))
41 # In the reverse mapping, there are a few extra valid matplotlib backends that
41 # In the reverse mapping, there are a few extra valid matplotlib backends that
42 # map to the same GUI support
42 # map to the same GUI support
43 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
43 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
44 backend2gui['WX'] = 'wx'
44 backend2gui['WX'] = 'wx'
45 backend2gui['CocoaAgg'] = 'osx'
45 backend2gui['CocoaAgg'] = 'osx'
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Matplotlib utilities
48 # Matplotlib utilities
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51
51
52 def getfigs(*fig_nums):
52 def getfigs(*fig_nums):
53 """Get a list of matplotlib figures by figure numbers.
53 """Get a list of matplotlib figures by figure numbers.
54
54
55 If no arguments are given, all available figures are returned. If the
55 If no arguments are given, all available figures are returned. If the
56 argument list contains references to invalid figures, a warning is printed
56 argument list contains references to invalid figures, a warning is printed
57 but the function continues pasting further figures.
57 but the function continues pasting further figures.
58
58
59 Parameters
59 Parameters
60 ----------
60 ----------
61 figs : tuple
61 figs : tuple
62 A tuple of ints giving the figure numbers of the figures to return.
62 A tuple of ints giving the figure numbers of the figures to return.
63 """
63 """
64 from matplotlib._pylab_helpers import Gcf
64 from matplotlib._pylab_helpers import Gcf
65 if not fig_nums:
65 if not fig_nums:
66 fig_managers = Gcf.get_all_fig_managers()
66 fig_managers = Gcf.get_all_fig_managers()
67 return [fm.canvas.figure for fm in fig_managers]
67 return [fm.canvas.figure for fm in fig_managers]
68 else:
68 else:
69 figs = []
69 figs = []
70 for num in fig_nums:
70 for num in fig_nums:
71 f = Gcf.figs.get(num)
71 f = Gcf.figs.get(num)
72 if f is None:
72 if f is None:
73 print('Warning: figure %s not available.' % num)
73 print('Warning: figure %s not available.' % num)
74 else:
74 else:
75 figs.append(f.canvas.figure)
75 figs.append(f.canvas.figure)
76 return figs
76 return figs
77
77
78
78
79 def figsize(sizex, sizey):
79 def figsize(sizex, sizey):
80 """Set the default figure size to be [sizex, sizey].
80 """Set the default figure size to be [sizex, sizey].
81
81
82 This is just an easy to remember, convenience wrapper that sets::
82 This is just an easy to remember, convenience wrapper that sets::
83
83
84 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
84 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
85 """
85 """
86 import matplotlib
86 import matplotlib
87 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
87 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
88
88
89
89
90 def print_figure(fig, fmt='png'):
90 def print_figure(fig, fmt='png'):
91 """Convert a figure to svg or png for inline display."""
91 """Convert a figure to svg or png for inline display."""
92 # When there's an empty figure, we shouldn't return anything, otherwise we
92 # When there's an empty figure, we shouldn't return anything, otherwise we
93 # get big blank areas in the qt console.
93 # get big blank areas in the qt console.
94 if not fig.axes:
94 if not fig.axes:
95 return
95 return
96
96
97 fc = fig.get_facecolor()
97 fc = fig.get_facecolor()
98 ec = fig.get_edgecolor()
98 ec = fig.get_edgecolor()
99 fig.set_facecolor('white')
99 fig.set_facecolor('white')
100 fig.set_edgecolor('white')
100 fig.set_edgecolor('white')
101 try:
101 try:
102 string_io = StringIO()
102 bytes_io = BytesIO()
103 # use 72 dpi to match QTConsole's dpi
103 # use 72 dpi to match QTConsole's dpi
104 fig.canvas.print_figure(string_io, format=fmt, dpi=72,
104 fig.canvas.print_figure(bytes_io, format=fmt, dpi=72,
105 bbox_inches='tight')
105 bbox_inches='tight')
106 data = string_io.getvalue()
106 data = bytes_io.getvalue()
107 finally:
107 finally:
108 fig.set_facecolor(fc)
108 fig.set_facecolor(fc)
109 fig.set_edgecolor(ec)
109 fig.set_edgecolor(ec)
110 return data
110 return data
111
111
112
112
113 # We need a little factory function here to create the closure where
113 # We need a little factory function here to create the closure where
114 # safe_execfile can live.
114 # safe_execfile can live.
115 def mpl_runner(safe_execfile):
115 def mpl_runner(safe_execfile):
116 """Factory to return a matplotlib-enabled runner for %run.
116 """Factory to return a matplotlib-enabled runner for %run.
117
117
118 Parameters
118 Parameters
119 ----------
119 ----------
120 safe_execfile : function
120 safe_execfile : function
121 This must be a function with the same interface as the
121 This must be a function with the same interface as the
122 :meth:`safe_execfile` method of IPython.
122 :meth:`safe_execfile` method of IPython.
123
123
124 Returns
124 Returns
125 -------
125 -------
126 A function suitable for use as the ``runner`` argument of the %run magic
126 A function suitable for use as the ``runner`` argument of the %run magic
127 function.
127 function.
128 """
128 """
129
129
130 def mpl_execfile(fname,*where,**kw):
130 def mpl_execfile(fname,*where,**kw):
131 """matplotlib-aware wrapper around safe_execfile.
131 """matplotlib-aware wrapper around safe_execfile.
132
132
133 Its interface is identical to that of the :func:`execfile` builtin.
133 Its interface is identical to that of the :func:`execfile` builtin.
134
134
135 This is ultimately a call to execfile(), but wrapped in safeties to
135 This is ultimately a call to execfile(), but wrapped in safeties to
136 properly handle interactive rendering."""
136 properly handle interactive rendering."""
137
137
138 import matplotlib
138 import matplotlib
139 import matplotlib.pylab as pylab
139 import matplotlib.pylab as pylab
140
140
141 #print '*** Matplotlib runner ***' # dbg
141 #print '*** Matplotlib runner ***' # dbg
142 # turn off rendering until end of script
142 # turn off rendering until end of script
143 is_interactive = matplotlib.rcParams['interactive']
143 is_interactive = matplotlib.rcParams['interactive']
144 matplotlib.interactive(False)
144 matplotlib.interactive(False)
145 safe_execfile(fname,*where,**kw)
145 safe_execfile(fname,*where,**kw)
146 matplotlib.interactive(is_interactive)
146 matplotlib.interactive(is_interactive)
147 # make rendering call now, if the user tried to do it
147 # make rendering call now, if the user tried to do it
148 if pylab.draw_if_interactive.called:
148 if pylab.draw_if_interactive.called:
149 pylab.draw()
149 pylab.draw()
150 pylab.draw_if_interactive.called = False
150 pylab.draw_if_interactive.called = False
151
151
152 return mpl_execfile
152 return mpl_execfile
153
153
154
154
155 def select_figure_format(shell, fmt):
155 def select_figure_format(shell, fmt):
156 """Select figure format for inline backend, either 'png' or 'svg'.
156 """Select figure format for inline backend, either 'png' or 'svg'.
157
157
158 Using this method ensures only one figure format is active at a time.
158 Using this method ensures only one figure format is active at a time.
159 """
159 """
160 from matplotlib.figure import Figure
160 from matplotlib.figure import Figure
161 from IPython.zmq.pylab import backend_inline
161 from IPython.zmq.pylab import backend_inline
162
162
163 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
163 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
164 png_formatter = shell.display_formatter.formatters['image/png']
164 png_formatter = shell.display_formatter.formatters['image/png']
165
165
166 if fmt=='png':
166 if fmt=='png':
167 svg_formatter.type_printers.pop(Figure, None)
167 svg_formatter.type_printers.pop(Figure, None)
168 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
168 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
169 elif fmt=='svg':
169 elif fmt=='svg':
170 png_formatter.type_printers.pop(Figure, None)
170 png_formatter.type_printers.pop(Figure, None)
171 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
171 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
172 else:
172 else:
173 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
173 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
174
174
175 # set the format to be used in the backend()
175 # set the format to be used in the backend()
176 backend_inline._figure_format = fmt
176 backend_inline._figure_format = fmt
177
177
178 #-----------------------------------------------------------------------------
178 #-----------------------------------------------------------------------------
179 # Code for initializing matplotlib and importing pylab
179 # Code for initializing matplotlib and importing pylab
180 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
181
181
182
182
183 def find_gui_and_backend(gui=None):
183 def find_gui_and_backend(gui=None):
184 """Given a gui string return the gui and mpl backend.
184 """Given a gui string return the gui and mpl backend.
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 gui : str
188 gui : str
189 Can be one of ('tk','gtk','wx','qt','qt4','inline').
189 Can be one of ('tk','gtk','wx','qt','qt4','inline').
190
190
191 Returns
191 Returns
192 -------
192 -------
193 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
193 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
194 'WXAgg','Qt4Agg','module://IPython.zmq.pylab.backend_inline').
194 'WXAgg','Qt4Agg','module://IPython.zmq.pylab.backend_inline').
195 """
195 """
196
196
197 import matplotlib
197 import matplotlib
198
198
199 if gui:
199 if gui:
200 # select backend based on requested gui
200 # select backend based on requested gui
201 backend = backends[gui]
201 backend = backends[gui]
202 else:
202 else:
203 backend = matplotlib.rcParams['backend']
203 backend = matplotlib.rcParams['backend']
204 # In this case, we need to find what the appropriate gui selection call
204 # In this case, we need to find what the appropriate gui selection call
205 # should be for IPython, so we can activate inputhook accordingly
205 # should be for IPython, so we can activate inputhook accordingly
206 gui = backend2gui.get(backend, None)
206 gui = backend2gui.get(backend, None)
207 return gui, backend
207 return gui, backend
208
208
209
209
210 def activate_matplotlib(backend):
210 def activate_matplotlib(backend):
211 """Activate the given backend and set interactive to True."""
211 """Activate the given backend and set interactive to True."""
212
212
213 import matplotlib
213 import matplotlib
214 if backend.startswith('module://'):
214 if backend.startswith('module://'):
215 # Work around bug in matplotlib: matplotlib.use converts the
215 # Work around bug in matplotlib: matplotlib.use converts the
216 # backend_id to lowercase even if a module name is specified!
216 # backend_id to lowercase even if a module name is specified!
217 matplotlib.rcParams['backend'] = backend
217 matplotlib.rcParams['backend'] = backend
218 else:
218 else:
219 matplotlib.use(backend)
219 matplotlib.use(backend)
220 matplotlib.interactive(True)
220 matplotlib.interactive(True)
221
221
222 # This must be imported last in the matplotlib series, after
222 # This must be imported last in the matplotlib series, after
223 # backend/interactivity choices have been made
223 # backend/interactivity choices have been made
224 import matplotlib.pylab as pylab
224 import matplotlib.pylab as pylab
225
225
226 # XXX For now leave this commented out, but depending on discussions with
226 # XXX For now leave this commented out, but depending on discussions with
227 # mpl-dev, we may be able to allow interactive switching...
227 # mpl-dev, we may be able to allow interactive switching...
228 #import matplotlib.pyplot
228 #import matplotlib.pyplot
229 #matplotlib.pyplot.switch_backend(backend)
229 #matplotlib.pyplot.switch_backend(backend)
230
230
231 pylab.show._needmain = False
231 pylab.show._needmain = False
232 # We need to detect at runtime whether show() is called by the user.
232 # We need to detect at runtime whether show() is called by the user.
233 # For this, we wrap it into a decorator which adds a 'called' flag.
233 # For this, we wrap it into a decorator which adds a 'called' flag.
234 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
234 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
235
235
236 def import_pylab(user_ns, backend, import_all=True, shell=None):
236 def import_pylab(user_ns, backend, import_all=True, shell=None):
237 """Import the standard pylab symbols into user_ns."""
237 """Import the standard pylab symbols into user_ns."""
238
238
239 # Import numpy as np/pyplot as plt are conventions we're trying to
239 # Import numpy as np/pyplot as plt are conventions we're trying to
240 # somewhat standardize on. Making them available to users by default
240 # somewhat standardize on. Making them available to users by default
241 # will greatly help this.
241 # will greatly help this.
242 s = ("import numpy\n"
242 s = ("import numpy\n"
243 "import matplotlib\n"
243 "import matplotlib\n"
244 "from matplotlib import pylab, mlab, pyplot\n"
244 "from matplotlib import pylab, mlab, pyplot\n"
245 "np = numpy\n"
245 "np = numpy\n"
246 "plt = pyplot\n"
246 "plt = pyplot\n"
247 )
247 )
248 exec s in user_ns
248 exec s in user_ns
249
249
250 if shell is not None:
250 if shell is not None:
251 exec s in shell.user_ns_hidden
251 exec s in shell.user_ns_hidden
252 # If using our svg payload backend, register the post-execution
252 # If using our svg payload backend, register the post-execution
253 # function that will pick up the results for display. This can only be
253 # function that will pick up the results for display. This can only be
254 # done with access to the real shell object.
254 # done with access to the real shell object.
255 #
255 #
256 from IPython.zmq.pylab.backend_inline import InlineBackendConfig
256 from IPython.zmq.pylab.backend_inline import InlineBackendConfig
257
257
258 cfg = InlineBackendConfig.instance(config=shell.config)
258 cfg = InlineBackendConfig.instance(config=shell.config)
259 cfg.shell = shell
259 cfg.shell = shell
260
260
261 if backend == backends['inline']:
261 if backend == backends['inline']:
262 from IPython.zmq.pylab.backend_inline import flush_figures
262 from IPython.zmq.pylab.backend_inline import flush_figures
263 from matplotlib import pyplot
263 from matplotlib import pyplot
264 shell.register_post_execute(flush_figures)
264 shell.register_post_execute(flush_figures)
265 # load inline_rc
265 # load inline_rc
266 pyplot.rcParams.update(cfg.rc)
266 pyplot.rcParams.update(cfg.rc)
267
267
268 # Add 'figsize' to pyplot and to the user's namespace
268 # Add 'figsize' to pyplot and to the user's namespace
269 user_ns['figsize'] = pyplot.figsize = figsize
269 user_ns['figsize'] = pyplot.figsize = figsize
270 shell.user_ns_hidden['figsize'] = figsize
270 shell.user_ns_hidden['figsize'] = figsize
271
271
272 # Setup the default figure format
272 # Setup the default figure format
273 fmt = cfg.figure_format
273 fmt = cfg.figure_format
274 select_figure_format(shell, fmt)
274 select_figure_format(shell, fmt)
275
275
276 # The old pastefig function has been replaced by display
276 # The old pastefig function has been replaced by display
277 from IPython.core.display import display
277 from IPython.core.display import display
278 # Add display and display_png to the user's namespace
278 # Add display and display_png to the user's namespace
279 user_ns['display'] = display
279 user_ns['display'] = display
280 shell.user_ns_hidden['display'] = display
280 shell.user_ns_hidden['display'] = display
281 user_ns['getfigs'] = getfigs
281 user_ns['getfigs'] = getfigs
282 shell.user_ns_hidden['getfigs'] = getfigs
282 shell.user_ns_hidden['getfigs'] = getfigs
283
283
284 if import_all:
284 if import_all:
285 s = ("from matplotlib.pylab import *\n"
285 s = ("from matplotlib.pylab import *\n"
286 "from numpy import *\n")
286 "from numpy import *\n")
287 exec s in user_ns
287 exec s in user_ns
288 if shell is not None:
288 if shell is not None:
289 exec s in shell.user_ns_hidden
289 exec s in shell.user_ns_hidden
290
290
291
291
292 def pylab_activate(user_ns, gui=None, import_all=True):
292 def pylab_activate(user_ns, gui=None, import_all=True):
293 """Activate pylab mode in the user's namespace.
293 """Activate pylab mode in the user's namespace.
294
294
295 Loads and initializes numpy, matplotlib and friends for interactive use.
295 Loads and initializes numpy, matplotlib and friends for interactive use.
296
296
297 Parameters
297 Parameters
298 ----------
298 ----------
299 user_ns : dict
299 user_ns : dict
300 Namespace where the imports will occur.
300 Namespace where the imports will occur.
301
301
302 gui : optional, string
302 gui : optional, string
303 A valid gui name following the conventions of the %gui magic.
303 A valid gui name following the conventions of the %gui magic.
304
304
305 import_all : optional, boolean
305 import_all : optional, boolean
306 If true, an 'import *' is done from numpy and pylab.
306 If true, an 'import *' is done from numpy and pylab.
307
307
308 Returns
308 Returns
309 -------
309 -------
310 The actual gui used (if not given as input, it was obtained from matplotlib
310 The actual gui used (if not given as input, it was obtained from matplotlib
311 itself, and will be needed next to configure IPython's gui integration.
311 itself, and will be needed next to configure IPython's gui integration.
312 """
312 """
313 gui, backend = find_gui_and_backend(gui)
313 gui, backend = find_gui_and_backend(gui)
314 activate_matplotlib(backend)
314 activate_matplotlib(backend)
315 import_pylab(user_ns, backend, import_all)
315 import_pylab(user_ns, backend, import_all)
316
316
317 print """
317 print """
318 Welcome to pylab, a matplotlib-based Python environment [backend: %s].
318 Welcome to pylab, a matplotlib-based Python environment [backend: %s].
319 For more information, type 'help(pylab)'.""" % backend
319 For more information, type 'help(pylab)'.""" % backend
320
320
321 return gui
321 return gui
322
322
@@ -1,68 +1,68 b''
1 import __builtin__
1 import __builtin__
2 from base64 import encodestring
2 from base64 import encodestring
3
3
4 from IPython.core.displayhook import DisplayHook
4 from IPython.core.displayhook import DisplayHook
5 from IPython.utils.traitlets import Instance, Dict
5 from IPython.utils.traitlets import Instance, Dict
6 from session import extract_header, Session
6 from session import extract_header, Session
7
7
8 class ZMQDisplayHook(object):
8 class ZMQDisplayHook(object):
9 """A simple displayhook that publishes the object's repr over a ZeroMQ
9 """A simple displayhook that publishes the object's repr over a ZeroMQ
10 socket."""
10 socket."""
11 topic=None
11 topic=None
12
12
13 def __init__(self, session, pub_socket):
13 def __init__(self, session, pub_socket):
14 self.session = session
14 self.session = session
15 self.pub_socket = pub_socket
15 self.pub_socket = pub_socket
16 self.parent_header = {}
16 self.parent_header = {}
17
17
18 def __call__(self, obj):
18 def __call__(self, obj):
19 if obj is None:
19 if obj is None:
20 return
20 return
21
21
22 __builtin__._ = obj
22 __builtin__._ = obj
23 msg = self.session.send(self.pub_socket, u'pyout', {u'data':repr(obj)},
23 msg = self.session.send(self.pub_socket, u'pyout', {u'data':repr(obj)},
24 parent=self.parent_header, ident=self.topic)
24 parent=self.parent_header, ident=self.topic)
25
25
26 def set_parent(self, parent):
26 def set_parent(self, parent):
27 self.parent_header = extract_header(parent)
27 self.parent_header = extract_header(parent)
28
28
29
29
30 def _encode_binary(format_dict):
30 def _encode_binary(format_dict):
31 pngdata = format_dict.get('image/png')
31 pngdata = format_dict.get('image/png')
32 if pngdata is not None:
32 if pngdata is not None:
33 format_dict['image/png'] = encodestring(pngdata)
33 format_dict['image/png'] = encodestring(pngdata).decode('ascii')
34 jpegdata = format_dict.get('image/jpeg')
34 jpegdata = format_dict.get('image/jpeg')
35 if jpegdata is not None:
35 if jpegdata is not None:
36 format_dict['image/jpeg'] = encodestring(jpegdata)
36 format_dict['image/jpeg'] = encodestring(jpegdata).decode('ascii')
37
37
38
38
39 class ZMQShellDisplayHook(DisplayHook):
39 class ZMQShellDisplayHook(DisplayHook):
40 """A displayhook subclass that publishes data using ZeroMQ. This is intended
40 """A displayhook subclass that publishes data using ZeroMQ. This is intended
41 to work with an InteractiveShell instance. It sends a dict of different
41 to work with an InteractiveShell instance. It sends a dict of different
42 representations of the object."""
42 representations of the object."""
43
43
44 session = Instance(Session)
44 session = Instance(Session)
45 pub_socket = Instance('zmq.Socket')
45 pub_socket = Instance('zmq.Socket')
46 parent_header = Dict({})
46 parent_header = Dict({})
47
47
48 def set_parent(self, parent):
48 def set_parent(self, parent):
49 """Set the parent for outbound messages."""
49 """Set the parent for outbound messages."""
50 self.parent_header = extract_header(parent)
50 self.parent_header = extract_header(parent)
51
51
52 def start_displayhook(self):
52 def start_displayhook(self):
53 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
53 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
54
54
55 def write_output_prompt(self):
55 def write_output_prompt(self):
56 """Write the output prompt."""
56 """Write the output prompt."""
57 if self.do_full_cache:
57 if self.do_full_cache:
58 self.msg['content']['execution_count'] = self.prompt_count
58 self.msg['content']['execution_count'] = self.prompt_count
59
59
60 def write_format_data(self, format_dict):
60 def write_format_data(self, format_dict):
61 _encode_binary(format_dict)
61 _encode_binary(format_dict)
62 self.msg['content']['data'] = format_dict
62 self.msg['content']['data'] = format_dict
63
63
64 def finish_displayhook(self):
64 def finish_displayhook(self):
65 """Finish up all displayhook activities."""
65 """Finish up all displayhook activities."""
66 self.session.send(self.pub_socket, self.msg)
66 self.session.send(self.pub_socket, self.msg)
67 self.msg = None
67 self.msg = None
68
68
General Comments 0
You need to be logged in to leave comments. Login now