3
3
# lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify
4
4
# that changes made maintain expected behaviour.
5
5
6
- import datetime
7
6
from base64 import b64encode
8
7
import json
9
8
import io
9
+ from tempfile import mkdtemp
10
+ import shutil
10
11
import os
11
12
from matplotlib .externals import six
12
13
from uuid import uuid4 as uuid
13
14
14
- import tornado .ioloop
15
-
16
- from IPython .display import display , Javascript , HTML
15
+ from IPython .display import display , HTML
16
+ from IPython import version_info
17
17
try :
18
18
# Jupyter/IPython 4.x or later
19
- from ipykernel .comm import Comm
19
+ from ipywidgets import DOMWidget
20
+ from traitlets import Unicode , Bool , Float , List , Any
21
+ from notebook .nbextensions import install_nbextension , check_nbextension
20
22
except ImportError :
21
23
# Jupyter/IPython 3.x or earlier
22
- from IPython .kernel .comm import Comm
24
+ from IPython .html .widgets import DOMWidget
25
+ from IPython .utils .traitlets import Unicode , Bool , Float , List , Any
26
+ from IPython .html .nbextensions import install_nbextension
23
27
24
28
from matplotlib import rcParams
25
29
from matplotlib .figure import Figure
33
37
34
38
35
39
class Show (ShowBase ):
40
+
36
41
def __call__ (self , block = None ):
37
42
from matplotlib ._pylab_helpers import Gcf
38
43
@@ -98,6 +103,7 @@ def connection_info():
98
103
'zoom_to_rect' : 'fa fa-square-o icon-check-empty' ,
99
104
'move' : 'fa fa-arrows icon-move' ,
100
105
'download' : 'fa fa-floppy-o icon-save' ,
106
+ 'export' : 'fa fa-file-picture-o icon-picture' ,
101
107
None : None
102
108
}
103
109
@@ -109,84 +115,74 @@ class NavigationIPy(NavigationToolbar2WebAgg):
109
115
_FONT_AWESOME_CLASSES [image_file ], name_of_method )
110
116
for text , tooltip_text , image_file , name_of_method
111
117
in (NavigationToolbar2 .toolitems +
112
- (('Download' , 'Download plot' , 'download' , 'download' ),))
118
+ (('Download' , 'Download plot' , 'download' , 'download' ),
119
+ ('Export' , 'Export plot' , 'export' , 'export' )))
113
120
if image_file in _FONT_AWESOME_CLASSES ]
114
121
122
+ def export (self ):
123
+ buf = io .BytesIO ()
124
+ self .canvas .figure .savefig (buf , format = 'png' , dpi = 'figure' )
125
+ data = "<img src='data:image/png;base64,{0}'/>"
126
+ data = data .format (b64encode (buf .getvalue ()).decode ('utf-8' ))
127
+ display (HTML (data ))
128
+
129
+
130
+ class FigureCanvasNbAgg (DOMWidget , FigureCanvasWebAggCore ):
131
+ _view_module = Unicode ("matplotlib" , sync = True )
132
+ _view_name = Unicode ('MPLCanvasView' , sync = True )
133
+ _toolbar_items = List (sync = True )
134
+ _closed = Bool (True )
135
+ _id = Unicode ('' , sync = True )
136
+
137
+ # Must declare the superclass private members.
138
+ _png_is_old = Bool ()
139
+ _force_full = Bool ()
140
+ _current_image_mode = Unicode ()
141
+ _dpi_ratio = Float (1.0 )
142
+ _is_idle_drawing = Bool ()
143
+ _is_saving = Bool ()
144
+ _button = Any ()
145
+ _key = Any ()
146
+ _lastx = Any ()
147
+ _lasty = Any ()
148
+ _is_idle_drawing = Bool ()
149
+
150
+ def __init__ (self , figure , * args , ** kwargs ):
151
+ super (FigureCanvasWebAggCore , self ).__init__ (figure , * args , ** kwargs )
152
+ super (DOMWidget , self ).__init__ (* args , ** kwargs )
153
+ self ._uid = uuid ().hex
154
+ self .on_msg (self ._handle_message )
155
+
156
+ def _handle_message (self , object , message , buffers ):
157
+ # The 'supports_binary' message is relevant to the
158
+ # websocket itself. The other messages get passed along
159
+ # to matplotlib as-is.
115
160
116
- class FigureManagerNbAgg (FigureManagerWebAgg ):
117
- ToolbarCls = NavigationIPy
118
-
119
- def __init__ (self , canvas , num ):
120
- self ._shown = False
121
- FigureManagerWebAgg .__init__ (self , canvas , num )
122
-
123
- def display_js (self ):
124
- # XXX How to do this just once? It has to deal with multiple
125
- # browser instances using the same kernel (require.js - but the
126
- # file isn't static?).
127
- display (Javascript (FigureManagerNbAgg .get_javascript ()))
128
-
129
- def show (self ):
130
- if not self ._shown :
131
- self .display_js ()
132
- self ._create_comm ()
133
- else :
134
- self .canvas .draw_idle ()
135
- self ._shown = True
136
-
137
- def reshow (self ):
138
- """
139
- A special method to re-show the figure in the notebook.
140
-
141
- """
142
- self ._shown = False
143
- self .show ()
144
-
145
- @property
146
- def connected (self ):
147
- return bool (self .web_sockets )
148
-
149
- @classmethod
150
- def get_javascript (cls , stream = None ):
151
- if stream is None :
152
- output = io .StringIO ()
161
+ # Every message has a "type" and a "figure_id".
162
+ message = json .loads (message )
163
+ if message ['type' ] == 'closing' :
164
+ self ._closed = True
165
+ elif message ['type' ] == 'supports_binary' :
166
+ self .supports_binary = message ['value' ]
167
+ elif message ['type' ] == 'initialized' :
168
+ _ , _ , w , h = self .figure .bbox .bounds
169
+ self .manager .resize (w , h )
170
+ self .send_json ('refresh' )
153
171
else :
154
- output = stream
155
- super (FigureManagerNbAgg , cls ).get_javascript (stream = output )
156
- with io .open (os .path .join (
157
- os .path .dirname (__file__ ),
158
- "web_backend" ,
159
- "nbagg_mpl.js" ), encoding = 'utf8' ) as fd :
160
- output .write (fd .read ())
161
- if stream is None :
162
- return output .getvalue ()
163
-
164
- def _create_comm (self ):
165
- comm = CommSocket (self )
166
- self .add_web_socket (comm )
167
- return comm
168
-
169
- def destroy (self ):
170
- self ._send_event ('close' )
171
- # need to copy comms as callbacks will modify this list
172
- for comm in list (self .web_sockets ):
173
- comm .on_close ()
174
- self .clearup_closed ()
175
-
176
- def clearup_closed (self ):
177
- """Clear up any closed Comms."""
178
- self .web_sockets = set ([socket for socket in self .web_sockets
179
- if socket .is_open ()])
180
-
181
- if len (self .web_sockets ) == 0 :
182
- self .canvas .close_event ()
172
+ self .manager .handle_json (message )
183
173
184
- def remove_comm (self , comm_id ):
185
- self .web_sockets = set ([socket for socket in self .web_sockets
186
- if not socket .comm .comm_id == comm_id ])
174
+ def send_json (self , content ):
175
+ self .send ({'data' : json .dumps (content )})
187
176
177
+ def send_binary (self , blob ):
178
+ # The comm is ascii, so we always send the image in base64
179
+ # encoded data URL form.
180
+ data = b64encode (blob )
181
+ if six .PY3 :
182
+ data = data .decode ('ascii' )
183
+ data_uri = "data:image/png;base64,{0}" .format (data )
184
+ self .send ({'data' : data_uri })
188
185
189
- class FigureCanvasNbAgg (FigureCanvasWebAggCore ):
190
186
def new_timer (self , * args , ** kwargs ):
191
187
return TimerTornado (* args , ** kwargs )
192
188
@@ -197,6 +193,31 @@ def stop_event_loop(self):
197
193
FigureCanvasBase .stop_event_loop_default (self )
198
194
199
195
196
+ class FigureManagerNbAgg (FigureManagerWebAgg ):
197
+ ToolbarCls = NavigationIPy
198
+
199
+ def __init__ (self , canvas , num ):
200
+ FigureManagerWebAgg .__init__ (self , canvas , num )
201
+ toolitems = []
202
+ for name , tooltip , image , method in self .ToolbarCls .toolitems :
203
+ if name is None :
204
+ toolitems .append (['' , '' , '' , '' ])
205
+ else :
206
+ toolitems .append ([name , tooltip , image , method ])
207
+ canvas ._toolbar_items = toolitems
208
+ self .web_sockets = [self .canvas ]
209
+
210
+ def show (self ):
211
+ if self .canvas ._closed :
212
+ self .canvas ._closed = False
213
+ display (self .canvas )
214
+ else :
215
+ self .canvas .draw_idle ()
216
+
217
+ def destroy (self ):
218
+ self ._send_event ('close' )
219
+
220
+
200
221
def new_figure_manager (num , * args , ** kwargs ):
201
222
"""
202
223
Create a new figure manager instance
@@ -229,76 +250,46 @@ def closer(event):
229
250
return manager
230
251
231
252
232
- class CommSocket ( object ):
253
+ def nbinstall ( overwrite = False , user = True ):
233
254
"""
234
- Manages the Comm connection between IPython and the browser (client).
235
-
236
- Comms are 2 way, with the CommSocket being able to publish a message
237
- via the send_json method, and handle a message with on_message. On the
238
- JS side figure.send_message and figure.ws.onmessage do the sending and
239
- receiving respectively.
240
-
255
+ Copies javascript dependencies to the '/nbextensions' folder in
256
+ your IPython directory.
257
+
258
+ Parameters
259
+ ----------
260
+
261
+ overwrite : bool
262
+ If True, always install the files, regardless of what mayœ already be
263
+ installed. Defaults to False.
264
+ user : bool
265
+ Whether to install to the user's .ipython/nbextensions directory.
266
+ Otherwise do a system-wide install
267
+ (e.g. /usr/local/share/jupyter/nbextensions). Defaults to False.
241
268
"""
242
- def __init__ (self , manager ):
243
- self .supports_binary = None
244
- self .manager = manager
245
- self .uuid = str (uuid ())
246
- # Publish an output area with a unique ID. The javascript can then
247
- # hook into this area.
248
- display (HTML ("<div id=%r></div>" % self .uuid ))
249
- try :
250
- self .comm = Comm ('matplotlib' , data = {'id' : self .uuid })
251
- except AttributeError :
252
- raise RuntimeError ('Unable to create an IPython notebook Comm '
253
- 'instance. Are you in the IPython notebook?' )
254
- self .comm .on_msg (self .on_message )
255
-
256
- manager = self .manager
257
- self ._ext_close = False
258
-
259
- def _on_close (close_message ):
260
- self ._ext_close = True
261
- manager .remove_comm (close_message ['content' ]['comm_id' ])
262
- manager .clearup_closed ()
263
-
264
- self .comm .on_close (_on_close )
265
-
266
- def is_open (self ):
267
- return not (self ._ext_close or self .comm ._closed )
268
-
269
- def on_close (self ):
270
- # When the socket is closed, deregister the websocket with
271
- # the FigureManager.
272
- if self .is_open ():
273
- try :
274
- self .comm .close ()
275
- except KeyError :
276
- # apparently already cleaned it up?
277
- pass
278
-
279
- def send_json (self , content ):
280
- self .comm .send ({'data' : json .dumps (content )})
281
-
282
- def send_binary (self , blob ):
283
- # The comm is ascii, so we always send the image in base64
284
- # encoded data URL form.
285
- data = b64encode (blob )
286
- if six .PY3 :
287
- data = data .decode ('ascii' )
288
- data_uri = "data:image/png;base64,{0}" .format (data )
289
- self .comm .send ({'data' : data_uri })
290
-
291
- def on_message (self , message ):
292
- # The 'supports_binary' message is relevant to the
293
- # websocket itself. The other messages get passed along
294
- # to matplotlib as-is.
295
-
296
- # Every message has a "type" and a "figure_id".
297
- message = json .loads (message ['content' ]['data' ])
298
- if message ['type' ] == 'closing' :
299
- self .on_close ()
300
- self .manager .clearup_closed ()
301
- elif message ['type' ] == 'supports_binary' :
302
- self .supports_binary = message ['value' ]
303
- else :
304
- self .manager .handle_json (message )
269
+ if (check_nbextension ('matplotlib' ) or
270
+ check_nbextension ('matplotlib' , True )):
271
+ return
272
+
273
+ # Make a temporary directory so we can wrap mpl.js in a requirejs define().
274
+ tempdir = mkdtemp ()
275
+ path = os .path .join (os .path .dirname (__file__ ), "web_backend" )
276
+ shutil .copy2 (os .path .join (path , "nbagg_mpl.js" ), tempdir )
277
+
278
+ with open (os .path .join (path , 'mpl.js' )) as fid :
279
+ contents = fid .read ()
280
+
281
+ with open (os .path .join (tempdir , 'mpl.js' ), 'w' ) as fid :
282
+ fid .write ('define(["jquery"], function($) {\n ' )
283
+ fid .write (contents )
284
+ fid .write ('\n return mpl;\n });' )
285
+
286
+ install_nbextension (
287
+ tempdir ,
288
+ overwrite = overwrite ,
289
+ symlink = False ,
290
+ destination = 'matplotlib' ,
291
+ verbose = 0 ,
292
+ ** ({'user' : user } if version_info >= (3 , 0 , 0 , '' ) else {})
293
+ )
294
+
295
+ #nbinstall()
0 commit comments