Skip to content

Commit 31f305e

Browse files
committed
Extended the nbagg backend to support animation.
1 parent 53b6797 commit 31f305e

File tree

4 files changed

+437
-45
lines changed

4 files changed

+437
-45
lines changed

lib/matplotlib/backends/backend_nbagg.py

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,32 @@
55
import os
66
from uuid import uuid4 as uuid
77

8+
import tornado.ioloop
9+
810
from IPython.display import display, Javascript, HTML
911
from IPython.kernel.comm import Comm
1012

1113
from matplotlib.figure import Figure
1214
from matplotlib.backends.backend_webagg_core import (FigureManagerWebAgg,
1315
FigureCanvasWebAggCore,
1416
NavigationToolbar2WebAgg)
15-
from matplotlib.backend_bases import ShowBase, NavigationToolbar2
17+
from matplotlib.backend_bases import ShowBase, NavigationToolbar2, TimerBase
1618

1719

1820
class Show(ShowBase):
1921
def __call__(self, block=None):
20-
import matplotlib._pylab_helpers as pylab_helpers
22+
from matplotlib._pylab_helpers import Gcf
2123
from matplotlib import is_interactive
2224

23-
managers = pylab_helpers.Gcf.get_all_fig_managers()
25+
managers = Gcf.get_all_fig_managers()
2426
if not managers:
2527
return
2628

27-
interactive = is_interactive()
28-
2929
for manager in managers:
3030
manager.show()
31-
if not interactive and manager in pylab_helpers.Gcf._activeQue:
32-
pylab_helpers.Gcf._activeQue.remove(manager)
31+
32+
if not is_interactive() and manager in Gcf._activeQue:
33+
Gcf._activeQue.remove(manager)
3334

3435

3536
show = Show()
@@ -48,19 +49,18 @@ def draw_if_interactive():
4849
def connection_info():
4950
"""
5051
Return a string showing the figure and connection status for
51-
the backend.
52+
the backend. This is intended as a diagnostic tool, and not for general
53+
use.
5254
5355
"""
54-
# TODO: Make this useful!
55-
import matplotlib._pylab_helpers as pylab_helpers
56+
from matplotlib._pylab_helpers import Gcf
5657
result = []
57-
for manager in pylab_helpers.Gcf.get_all_fig_managers():
58+
for manager in Gcf.get_all_fig_managers():
5859
fig = manager.canvas.figure
5960
result.append('{} - {}'.format((fig.get_label() or
6061
"Figure {0}".format(manager.num)),
6162
manager.web_sockets))
62-
result.append('Figures pending show: ' +
63-
str(len(pylab_helpers.Gcf._activeQue)))
63+
result.append('Figures pending show: {}'.format(len(Gcf._activeQue)))
6464
return '\n'.join(result)
6565

6666

@@ -93,7 +93,8 @@ def __init__(self, canvas, num):
9393

9494
def display_js(self):
9595
# XXX How to do this just once? It has to deal with multiple
96-
# browser instances using the same kernel.
96+
# browser instances using the same kernel (require.js - but the
97+
# file isn't static?).
9798
display(Javascript(FigureManagerNbAgg.get_javascript()))
9899

99100
def show(self):
@@ -105,6 +106,10 @@ def show(self):
105106
self._shown = True
106107

107108
def reshow(self):
109+
"""
110+
A special method to re-show the figure in the notebook.
111+
112+
"""
108113
self._shown = False
109114
self.show()
110115

@@ -137,6 +142,43 @@ def destroy(self):
137142
for comm in self.web_sockets.copy():
138143
comm.on_close()
139144

145+
def clearup_closed(self):
146+
"""Clear up any closed Comms."""
147+
self.web_sockets = set([socket for socket in self.web_sockets
148+
if not socket.is_open()])
149+
150+
151+
class TimerTornado(TimerBase):
152+
def _timer_start(self):
153+
import datetime
154+
self._timer_stop()
155+
if self._single:
156+
ioloop = tornado.ioloop.IOLoop.instance()
157+
self._timer = ioloop.add_timeout(
158+
datetime.timedelta(milliseconds=self.interval),
159+
self._on_timer)
160+
else:
161+
self._timer = tornado.ioloop.PeriodicCallback(
162+
self._on_timer,
163+
self.interval)
164+
self._timer.start()
165+
166+
def _timer_stop(self):
167+
if self._timer is not None:
168+
self._timer.stop()
169+
self._timer = None
170+
171+
def _timer_set_interval(self):
172+
# Only stop and restart it if the timer has already been started
173+
if self._timer is not None:
174+
self._timer_stop()
175+
self._timer_start()
176+
177+
178+
class FigureCanvasNbAgg(FigureCanvasWebAggCore):
179+
def new_timer(self, *args, **kwargs):
180+
return TimerTornado(*args, **kwargs)
181+
140182

141183
def new_figure_manager(num, *args, **kwargs):
142184
"""
@@ -151,7 +193,7 @@ def new_figure_manager_given_figure(num, figure):
151193
"""
152194
Create a new figure manager instance for the given figure.
153195
"""
154-
canvas = FigureCanvasWebAggCore(figure)
196+
canvas = FigureCanvasNbAgg(figure)
155197
manager = FigureManagerNbAgg(canvas, num)
156198
return manager
157199

@@ -170,6 +212,8 @@ def __init__(self, manager):
170212
self.supports_binary = None
171213
self.manager = manager
172214
self.uuid = str(uuid())
215+
# Publish an output area with a unique ID. The javascript can then
216+
# hook into this area.
173217
display(HTML("<div id=%r></div>" % self.uuid))
174218
try:
175219
self.comm = Comm('matplotlib', data={'id': self.uuid})
@@ -178,12 +222,17 @@ def __init__(self, manager):
178222
'instance. Are you in the IPython notebook?')
179223
self.comm.on_msg(self.on_message)
180224

225+
manager = self.manager
226+
self.comm.on_close(lambda close_message: manager.clearup_closed())
227+
228+
def is_open(self):
229+
return not self.comm._closed
230+
181231
def on_close(self):
182232
# When the socket is closed, deregister the websocket with
183233
# the FigureManager.
184-
if self.comm in self.manager.web_sockets:
185-
self.manager.remove_web_socket(self)
186234
self.comm.close()
235+
self.manager.clearup_closed()
187236

188237
def send_json(self, content):
189238
self.comm.send({'data': json.dumps(content)})

lib/matplotlib/backends/backend_webagg.py

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from matplotlib.figure import Figure
4040
from matplotlib._pylab_helpers import Gcf
4141
from . import backend_webagg_core as core
42+
from . import backend_nbagg
4243

4344

4445
def new_figure_manager(num, *args, **kwargs):
@@ -96,39 +97,13 @@ def run(self):
9697
webagg_server_thread = ServerThread()
9798

9899

99-
class TimerTornado(backend_bases.TimerBase):
100-
def _timer_start(self):
101-
self._timer_stop()
102-
if self._single:
103-
ioloop = tornado.ioloop.IOLoop.instance()
104-
self._timer = ioloop.add_timeout(
105-
datetime.timedelta(milliseconds=self.interval),
106-
self._on_timer)
107-
else:
108-
self._timer = tornado.ioloop.PeriodicCallback(
109-
self._on_timer,
110-
self.interval)
111-
self._timer.start()
112-
113-
def _timer_stop(self):
114-
if self._timer is not None:
115-
self._timer.stop()
116-
self._timer = None
117-
118-
def _timer_set_interval(self):
119-
# Only stop and restart it if the timer has already been started
120-
if self._timer is not None:
121-
self._timer_stop()
122-
self._timer_start()
123-
124-
125100
class FigureCanvasWebAgg(core.FigureCanvasWebAggCore):
126101
def show(self):
127102
# show the figure window
128103
show()
129104

130105
def new_timer(self, *args, **kwargs):
131-
return TimerTornado(*args, **kwargs)
106+
return backend_nbagg.TimerTornado(*args, **kwargs)
132107

133108
def start_event_loop(self, timeout):
134109
backend_bases.FigureCanvasBase.start_event_loop_default(

lib/matplotlib/backends/web_backend/nbagg_mpl.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ mpl.mpl_figure_comm = function(comm, msg) {
2525
// starts-up an IPython Comm through the "matplotlib" channel.
2626

2727
var id = msg.content.data.id;
28+
// Get hold of the div created by the display call when the Comm
29+
// socket was opened in Python.
2830
var element = $("#" + id);
2931
var ws_proxy = comm_websocket_adapter(comm)
3032

@@ -44,7 +46,7 @@ mpl.mpl_figure_comm = function(comm, msg) {
4446

4547
// Disable right mouse context menu.
4648
$(fig.rubberband_canvas).bind("contextmenu",function(e){
47-
return false;
49+
return false;
4850
});
4951

5052
};
@@ -59,6 +61,7 @@ mpl.figure.prototype.handle_close = function(fig, msg) {
5961
}
6062

6163
mpl.figure.prototype.push_to_output = function(remove_interactive) {
64+
// Turn the data on the canvas into data in the output cell.
6265
var dataURL = this.canvas.toDataURL();
6366
this.cell_info[1]['text/html'] = '<img src="' + dataURL + '">';
6467
}

0 commit comments

Comments
 (0)