Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3f42311

Browse files
committed
nbagg widget refactor
nbagg widget refactor wip nbagg widget refactor wip ipython widget Display the static figure when closed Revert changes to uat notebook Add support for the toolbar Fix embed logic and show() logic Better handling of class variables Clean up imports and add timer support Add an nbinstall function Implement resize on start. Implement explicit export IPython 3 compat Remove debug in nbinstall Simplify define() Progress on distributing the js files Reorganize javascript files Update paths
1 parent 20c2961 commit 3f42311

9 files changed

Lines changed: 322 additions & 362 deletions

File tree

lib/matplotlib/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,17 @@ def tk_window_focus():
14541454
except (KeyError, ValueError):
14551455
pass
14561456

1457+
1458+
# Jupyter extension paths
1459+
def _jupyter_nbextension_paths():
1460+
return [{
1461+
'section': 'notebook',
1462+
'src': 'backends/web_backend/js',
1463+
'dest': 'matplotlib',
1464+
'require': 'matplotlib/mpl'
1465+
}]
1466+
1467+
14571468
default_test_modules = [
14581469
'matplotlib.tests.test_agg',
14591470
'matplotlib.tests.test_animation',

lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2161,7 +2161,8 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
21612161
origfacecolor = self.figure.get_facecolor()
21622162
origedgecolor = self.figure.get_edgecolor()
21632163

2164-
self.figure.dpi = dpi
2164+
if dpi != 'figure':
2165+
self.figure.dpi = dpi
21652166
self.figure.set_facecolor(facecolor)
21662167
self.figure.set_edgecolor(edgecolor)
21672168

lib/matplotlib/backends/backend_nbagg.py

Lines changed: 139 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@
33
# lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify
44
# that changes made maintain expected behaviour.
55

6-
import datetime
76
from base64 import b64encode
87
import json
98
import io
9+
from tempfile import mkdtemp
10+
import shutil
1011
import os
1112
from matplotlib.externals import six
1213
from uuid import uuid4 as uuid
1314

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
1717
try:
1818
# 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
2022
except ImportError:
2123
# 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
2327

2428
from matplotlib import rcParams
2529
from matplotlib.figure import Figure
@@ -33,6 +37,7 @@
3337

3438

3539
class Show(ShowBase):
40+
3641
def __call__(self, block=None):
3742
from matplotlib._pylab_helpers import Gcf
3843

@@ -98,6 +103,7 @@ def connection_info():
98103
'zoom_to_rect': 'fa fa-square-o icon-check-empty',
99104
'move': 'fa fa-arrows icon-move',
100105
'download': 'fa fa-floppy-o icon-save',
106+
'export': 'fa fa-file-picture-o icon-picture',
101107
None: None
102108
}
103109

@@ -109,84 +115,74 @@ class NavigationIPy(NavigationToolbar2WebAgg):
109115
_FONT_AWESOME_CLASSES[image_file], name_of_method)
110116
for text, tooltip_text, image_file, name_of_method
111117
in (NavigationToolbar2.toolitems +
112-
(('Download', 'Download plot', 'download', 'download'),))
118+
(('Download', 'Download plot', 'download', 'download'),
119+
('Export', 'Export plot', 'export', 'export')))
113120
if image_file in _FONT_AWESOME_CLASSES]
114121

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("nbextensions/matplotlib/nbagg_mpl", 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.
115160

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')
153171
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)
183173

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)})
187176

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})
188185

189-
class FigureCanvasNbAgg(FigureCanvasWebAggCore):
190186
def new_timer(self, *args, **kwargs):
191187
return TimerTornado(*args, **kwargs)
192188

@@ -197,6 +193,31 @@ def stop_event_loop(self):
197193
FigureCanvasBase.stop_event_loop_default(self)
198194

199195

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+
200221
def new_figure_manager(num, *args, **kwargs):
201222
"""
202223
Create a new figure manager instance
@@ -229,76 +250,46 @@ def closer(event):
229250
return manager
230251

231252

232-
class CommSocket(object):
253+
def nbinstall(overwrite=False, user=True):
233254
"""
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.
241268
"""
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('\nreturn 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()

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import io
1919
import json
2020
import os
21-
import time
21+
import datetime
2222
import warnings
2323

2424
import numpy as np
@@ -501,6 +501,7 @@ def get_javascript(cls, stream=None):
501501
with io.open(os.path.join(
502502
os.path.dirname(__file__),
503503
"web_backend",
504+
"js",
504505
"mpl.js"), encoding='utf8') as fd:
505506
output.write(fd.read())
506507

@@ -530,7 +531,7 @@ def get_javascript(cls, stream=None):
530531

531532
@classmethod
532533
def get_static_file_path(cls):
533-
return os.path.join(os.path.dirname(__file__), 'web_backend')
534+
return os.path.join(os.path.dirname(__file__), 'web_backend', 'js')
534535

535536
def _send_event(self, event_type, **kwargs):
536537
payload = {'type': event_type}
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)