Skip to content

Commit 0d9598e

Browse files
committed
Merge branch 'nbagg_backend'
Conflicts: lib/matplotlib/rcsetup.py Manually merged adding two backends (Qt5Agg and nbAgg)
2 parents e03cda1 + 0c11e32 commit 0d9598e

File tree

9 files changed

+588
-184
lines changed

9 files changed

+588
-184
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2514,7 +2514,7 @@ class NonGuiException(Exception):
25142514
pass
25152515

25162516

2517-
class FigureManagerBase:
2517+
class FigureManagerBase(object):
25182518
"""
25192519
Helper class for pyplot mode, wraps everything up into a neat bundle
25202520
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
"""Interactive figures in the IPython notebook"""
2+
from base64 import b64encode
3+
import json
4+
import io
5+
import os
6+
from uuid import uuid4 as uuid
7+
8+
from IPython.display import display,Javascript,HTML
9+
from IPython.kernel.comm import Comm
10+
11+
from matplotlib.figure import Figure
12+
from matplotlib.backends.backend_webagg_core import (FigureManagerWebAgg,
13+
FigureCanvasWebAggCore,
14+
NavigationToolbar2WebAgg)
15+
from matplotlib.backend_bases import ShowBase, NavigationToolbar2
16+
17+
18+
class Show(ShowBase):
19+
def display_js(self):
20+
# XXX How to do this just once? It has to deal with multiple
21+
# browser instances using the same kernel.
22+
display(Javascript(FigureManagerNbAgg.get_javascript()))
23+
24+
def __call__(self, block=None):
25+
from matplotlib import is_interactive
26+
import matplotlib._pylab_helpers as pylab_helpers
27+
28+
queue = pylab_helpers.Gcf._activeQue
29+
for manager in queue[:]:
30+
if not manager.shown:
31+
self.display_js()
32+
33+
manager.show()
34+
# If we are not interactive, disable the figure from
35+
# the active queue, but don't destroy it.
36+
if not is_interactive():
37+
queue.remove(manager)
38+
manager.canvas.draw_idle()
39+
40+
41+
show = Show()
42+
43+
44+
def draw_if_interactive():
45+
from matplotlib import is_interactive
46+
import matplotlib._pylab_helpers as pylab_helpers
47+
48+
if is_interactive():
49+
manager = pylab_helpers.Gcf.get_active()
50+
if manager is not None:
51+
if not manager.shown:
52+
manager.show()
53+
manager.canvas.draw_idle()
54+
55+
56+
def connection_info():
57+
"""
58+
Return a string showing the figure and connection status for
59+
the backend.
60+
61+
"""
62+
# TODO: Make this useful!
63+
import matplotlib._pylab_helpers as pylab_helpers
64+
result = []
65+
for manager in pylab_helpers.Gcf.get_all_fig_managers():
66+
fig = manager.canvas.figure
67+
result.append('{} - {}'.format(fig.get_label() or "Figure {0}".format(manager.num),
68+
manager.web_sockets))
69+
result.append('Figures pending show: ' + str(len(pylab_helpers.Gcf._activeQue)))
70+
return '\n'.join(result)
71+
72+
73+
class NavigationIPy(NavigationToolbar2WebAgg):
74+
# Note: Version 3.2 icons, not the later 4.0 ones.
75+
# http://fontawesome.io/3.2.1/icons/
76+
_font_awesome_classes = {
77+
'home': 'icon-home',
78+
'back': 'icon-arrow-left',
79+
'forward': 'icon-arrow-right',
80+
'zoom_to_rect': 'icon-check-empty',
81+
'move': 'icon-move',
82+
None: None
83+
}
84+
85+
# Use the standard toolbar items + download button
86+
toolitems = [(text, tooltip_text, _font_awesome_classes[image_file], name_of_method)
87+
for text, tooltip_text, image_file, name_of_method
88+
in NavigationToolbar2.toolitems
89+
if image_file in _font_awesome_classes]
90+
91+
92+
class FigureManagerNbAgg(FigureManagerWebAgg):
93+
ToolbarCls = NavigationIPy
94+
95+
def __init__(self, canvas, num):
96+
self.shown = False
97+
FigureManagerWebAgg.__init__(self, canvas, num)
98+
99+
def show(self):
100+
if not self.shown:
101+
self._create_comm()
102+
self.shown = True
103+
104+
def reshow(self):
105+
self.shown = False
106+
self.show()
107+
108+
@property
109+
def connected(self):
110+
return bool(self.web_sockets)
111+
112+
@classmethod
113+
def get_javascript(cls, stream=None):
114+
if stream is None:
115+
output = io.StringIO()
116+
else:
117+
output = stream
118+
super(FigureManagerNbAgg, cls).get_javascript(stream=output)
119+
with io.open(os.path.join(
120+
os.path.dirname(__file__),
121+
"web_backend",
122+
"nbagg_mpl.js"), encoding='utf8') as fd:
123+
output.write(fd.read())
124+
if stream is None:
125+
return output.getvalue()
126+
127+
def _create_comm(self):
128+
comm = CommSocket(self)
129+
self.add_web_socket(comm)
130+
return comm
131+
132+
def destroy(self):
133+
self._send_event('close')
134+
for comm in self.web_sockets.copy():
135+
comm.on_close()
136+
137+
138+
def new_figure_manager(num, *args, **kwargs):
139+
"""
140+
Create a new figure manager instance
141+
"""
142+
FigureClass = kwargs.pop('FigureClass', Figure)
143+
thisFig = FigureClass(*args, **kwargs)
144+
return new_figure_manager_given_figure(num, thisFig)
145+
146+
147+
def new_figure_manager_given_figure(num, figure):
148+
"""
149+
Create a new figure manager instance for the given figure.
150+
"""
151+
canvas = FigureCanvasWebAggCore(figure)
152+
manager = FigureManagerNbAgg(canvas, num)
153+
return manager
154+
155+
156+
class CommSocket(object):
157+
"""
158+
Manages the Comm connection between IPython and the browser (client).
159+
160+
Comms are 2 way, with the CommSocket being able to publish a message
161+
via the send_json method, and handle a message with on_message. On the
162+
JS side figure.send_message and figure.ws.onmessage do the sending and
163+
receiving respectively.
164+
165+
"""
166+
def __init__(self, manager):
167+
self.supports_binary = None
168+
self.manager = manager
169+
self.uuid = str(uuid())
170+
display(HTML("<div id=%r></div>" % self.uuid))
171+
try:
172+
self.comm = Comm('matplotlib', data={'id': self.uuid})
173+
except AttributeError:
174+
raise RuntimeError('Unable to create an IPython notebook Comm '
175+
'instance. Are you in the IPython notebook?')
176+
self.comm.on_msg(self.on_message)
177+
178+
def on_close(self):
179+
# When the socket is closed, deregister the websocket with
180+
# the FigureManager.
181+
if self.comm in self.manager.web_sockets:
182+
self.manager.remove_web_socket(self)
183+
self.comm.close()
184+
185+
def send_json(self, content):
186+
self.comm.send({'data': json.dumps(content)})
187+
188+
def send_binary(self, blob):
189+
# The comm is ascii, so we always send the image in base64
190+
# encoded data URL form.
191+
data_uri = "data:image/png;base64,{0}".format(b64encode(blob))
192+
self.comm.send({'data': data_uri})
193+
194+
def on_message(self, message):
195+
# The 'supports_binary' message is relevant to the
196+
# websocket itself. The other messages get passed along
197+
# to matplotlib as-is.
198+
199+
# Every message has a "type" and a "figure_id".
200+
message = json.loads(message['content']['data'])
201+
if message['type'] == 'closing':
202+
self.on_close()
203+
elif message['type'] == 'supports_binary':
204+
self.supports_binary = message['value']
205+
else:
206+
self.manager.handle_json(message)

lib/matplotlib/backends/backend_webagg.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,13 @@ class WebSocket(tornado.websocket.WebSocketHandler):
230230

231231
def open(self, fignum):
232232
self.fignum = int(fignum)
233-
manager = Gcf.get_fig_manager(self.fignum)
234-
manager.add_web_socket(self)
233+
self.manager = Gcf.get_fig_manager(self.fignum)
234+
self.manager.add_web_socket(self)
235235
if hasattr(self, 'set_nodelay'):
236236
self.set_nodelay(True)
237237

238238
def on_close(self):
239-
manager = Gcf.get_fig_manager(self.fignum)
240-
if manager is not None:
241-
manager.remove_web_socket(self)
239+
self.manager.remove_web_socket(self)
242240

243241
def on_message(self, message):
244242
message = json.loads(message)

0 commit comments

Comments
 (0)