11"""Interactive figures in the IPython notebook"""
2+ # Note: There is a notebook in
3+ # lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify
4+ # that changes made maintain expected behaviour.
5+
26from base64 import b64encode
7+ from contextlib import contextmanager
38import json
49import io
510import os
611import six
712from uuid import uuid4 as uuid
813
14+ import tornado .ioloop
15+
916from IPython .display import display , Javascript , HTML
1017from IPython .kernel .comm import Comm
1118
19+ from matplotlib import rcParams
1220from matplotlib .figure import Figure
21+ from matplotlib .backends import backend_agg
1322from matplotlib .backends .backend_webagg_core import (FigureManagerWebAgg ,
1423 FigureCanvasWebAggCore ,
1524 NavigationToolbar2WebAgg )
16- from matplotlib .backend_bases import ShowBase , NavigationToolbar2
25+ from matplotlib .backend_bases import (ShowBase , NavigationToolbar2 ,
26+ TimerBase , FigureCanvasBase )
1727
1828
1929class Show (ShowBase ):
2030 def __call__ (self , block = None ):
21- import matplotlib ._pylab_helpers as pylab_helpers
31+ from matplotlib ._pylab_helpers import Gcf
2232 from matplotlib import is_interactive
2333
24- managers = pylab_helpers . Gcf .get_all_fig_managers ()
34+ managers = Gcf .get_all_fig_managers ()
2535 if not managers :
2636 return
2737
28- interactive = is_interactive ()
29-
3038 for manager in managers :
3139 manager .show ()
32- if not interactive and manager in pylab_helpers .Gcf ._activeQue :
33- pylab_helpers .Gcf ._activeQue .remove (manager )
40+
41+ if not is_interactive () and manager in Gcf ._activeQue :
42+ Gcf ._activeQue .remove (manager )
3443
3544
3645show = Show ()
@@ -49,19 +58,18 @@ def draw_if_interactive():
4958def connection_info ():
5059 """
5160 Return a string showing the figure and connection status for
52- the backend.
61+ the backend. This is intended as a diagnostic tool, and not for general
62+ use.
5363
5464 """
55- # TODO: Make this useful!
56- import matplotlib ._pylab_helpers as pylab_helpers
65+ from matplotlib ._pylab_helpers import Gcf
5766 result = []
58- for manager in pylab_helpers . Gcf .get_all_fig_managers ():
67+ for manager in Gcf .get_all_fig_managers ():
5968 fig = manager .canvas .figure
6069 result .append ('{} - {}' .format ((fig .get_label () or
6170 "Figure {0}" .format (manager .num )),
6271 manager .web_sockets ))
63- result .append ('Figures pending show: ' +
64- str (len (pylab_helpers .Gcf ._activeQue )))
72+ result .append ('Figures pending show: {}' .format (len (Gcf ._activeQue )))
6573 return '\n ' .join (result )
6674
6775
@@ -96,7 +104,8 @@ def __init__(self, canvas, num):
96104
97105 def display_js (self ):
98106 # XXX How to do this just once? It has to deal with multiple
99- # browser instances using the same kernel.
107+ # browser instances using the same kernel (require.js - but the
108+ # file isn't static?).
100109 display (Javascript (FigureManagerNbAgg .get_javascript ()))
101110
102111 def show (self ):
@@ -108,6 +117,10 @@ def show(self):
108117 self ._shown = True
109118
110119 def reshow (self ):
120+ """
121+ A special method to re-show the figure in the notebook.
122+
123+ """
111124 self ._shown = False
112125 self .show ()
113126
@@ -140,6 +153,49 @@ def destroy(self):
140153 for comm in self .web_sockets .copy ():
141154 comm .on_close ()
142155
156+ def clearup_closed (self ):
157+ """Clear up any closed Comms."""
158+ self .web_sockets = set ([socket for socket in self .web_sockets
159+ if not socket .is_open ()])
160+
161+
162+ class TimerTornado (TimerBase ):
163+ def _timer_start (self ):
164+ import datetime
165+ self ._timer_stop ()
166+ if self ._single :
167+ ioloop = tornado .ioloop .IOLoop .instance ()
168+ self ._timer = ioloop .add_timeout (
169+ datetime .timedelta (milliseconds = self .interval ),
170+ self ._on_timer )
171+ else :
172+ self ._timer = tornado .ioloop .PeriodicCallback (
173+ self ._on_timer ,
174+ self .interval )
175+ self ._timer .start ()
176+
177+ def _timer_stop (self ):
178+ if self ._timer is not None :
179+ self ._timer .stop ()
180+ self ._timer = None
181+
182+ def _timer_set_interval (self ):
183+ # Only stop and restart it if the timer has already been started
184+ if self ._timer is not None :
185+ self ._timer_stop ()
186+ self ._timer_start ()
187+
188+
189+ class FigureCanvasNbAgg (FigureCanvasWebAggCore ):
190+ def new_timer (self , * args , ** kwargs ):
191+ return TimerTornado (* args , ** kwargs )
192+
193+ def start_event_loop (self , timeout ):
194+ FigureCanvasBase .start_event_loop_default (self , timeout )
195+
196+ def stop_event_loop (self ):
197+ FigureCanvasBase .stop_event_loop_default (self )
198+
143199
144200def new_figure_manager (num , * args , ** kwargs ):
145201 """
@@ -154,7 +210,9 @@ def new_figure_manager_given_figure(num, figure):
154210 """
155211 Create a new figure manager instance for the given figure.
156212 """
157- canvas = FigureCanvasWebAggCore (figure )
213+ canvas = FigureCanvasNbAgg (figure )
214+ if rcParams ['nbagg.transparent' ]:
215+ figure .patch .set_alpha (0 )
158216 manager = FigureManagerNbAgg (canvas , num )
159217 return manager
160218
@@ -173,6 +231,8 @@ def __init__(self, manager):
173231 self .supports_binary = None
174232 self .manager = manager
175233 self .uuid = str (uuid ())
234+ # Publish an output area with a unique ID. The javascript can then
235+ # hook into this area.
176236 display (HTML ("<div id=%r></div>" % self .uuid ))
177237 try :
178238 self .comm = Comm ('matplotlib' , data = {'id' : self .uuid })
@@ -181,12 +241,17 @@ def __init__(self, manager):
181241 'instance. Are you in the IPython notebook?' )
182242 self .comm .on_msg (self .on_message )
183243
244+ manager = self .manager
245+ self .comm .on_close (lambda close_message : manager .clearup_closed ())
246+
247+ def is_open (self ):
248+ return not self .comm ._closed
249+
184250 def on_close (self ):
185251 # When the socket is closed, deregister the websocket with
186252 # the FigureManager.
187- if self .comm in self .manager .web_sockets :
188- self .manager .remove_web_socket (self )
189253 self .comm .close ()
254+ self .manager .clearup_closed ()
190255
191256 def send_json (self , content ):
192257 self .comm .send ({'data' : json .dumps (content )})
0 commit comments