1
1
"""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
+
2
6
from base64 import b64encode
7
+ from contextlib import contextmanager
3
8
import json
4
9
import io
5
10
import os
6
11
import six
7
12
from uuid import uuid4 as uuid
8
13
14
+ import tornado .ioloop
15
+
9
16
from IPython .display import display , Javascript , HTML
10
17
from IPython .kernel .comm import Comm
11
18
19
+ from matplotlib import rcParams
12
20
from matplotlib .figure import Figure
21
+ from matplotlib .backends import backend_agg
13
22
from matplotlib .backends .backend_webagg_core import (FigureManagerWebAgg ,
14
23
FigureCanvasWebAggCore ,
15
24
NavigationToolbar2WebAgg )
16
- from matplotlib .backend_bases import ShowBase , NavigationToolbar2
25
+ from matplotlib .backend_bases import (ShowBase , NavigationToolbar2 ,
26
+ TimerBase , FigureCanvasBase )
17
27
18
28
19
29
class Show (ShowBase ):
20
30
def __call__ (self , block = None ):
21
- import matplotlib ._pylab_helpers as pylab_helpers
31
+ from matplotlib ._pylab_helpers import Gcf
22
32
from matplotlib import is_interactive
23
33
24
- managers = pylab_helpers . Gcf .get_all_fig_managers ()
34
+ managers = Gcf .get_all_fig_managers ()
25
35
if not managers :
26
36
return
27
37
28
- interactive = is_interactive ()
29
-
30
38
for manager in managers :
31
39
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 )
34
43
35
44
36
45
show = Show ()
@@ -49,19 +58,18 @@ def draw_if_interactive():
49
58
def connection_info ():
50
59
"""
51
60
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.
53
63
54
64
"""
55
- # TODO: Make this useful!
56
- import matplotlib ._pylab_helpers as pylab_helpers
65
+ from matplotlib ._pylab_helpers import Gcf
57
66
result = []
58
- for manager in pylab_helpers . Gcf .get_all_fig_managers ():
67
+ for manager in Gcf .get_all_fig_managers ():
59
68
fig = manager .canvas .figure
60
69
result .append ('{} - {}' .format ((fig .get_label () or
61
70
"Figure {0}" .format (manager .num )),
62
71
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 )))
65
73
return '\n ' .join (result )
66
74
67
75
@@ -96,7 +104,8 @@ def __init__(self, canvas, num):
96
104
97
105
def display_js (self ):
98
106
# 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?).
100
109
display (Javascript (FigureManagerNbAgg .get_javascript ()))
101
110
102
111
def show (self ):
@@ -108,6 +117,10 @@ def show(self):
108
117
self ._shown = True
109
118
110
119
def reshow (self ):
120
+ """
121
+ A special method to re-show the figure in the notebook.
122
+
123
+ """
111
124
self ._shown = False
112
125
self .show ()
113
126
@@ -140,6 +153,49 @@ def destroy(self):
140
153
for comm in self .web_sockets .copy ():
141
154
comm .on_close ()
142
155
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
+
143
199
144
200
def new_figure_manager (num , * args , ** kwargs ):
145
201
"""
@@ -154,7 +210,9 @@ def new_figure_manager_given_figure(num, figure):
154
210
"""
155
211
Create a new figure manager instance for the given figure.
156
212
"""
157
- canvas = FigureCanvasWebAggCore (figure )
213
+ canvas = FigureCanvasNbAgg (figure )
214
+ if rcParams ['nbagg.transparent' ]:
215
+ figure .patch .set_alpha (0 )
158
216
manager = FigureManagerNbAgg (canvas , num )
159
217
return manager
160
218
@@ -173,6 +231,8 @@ def __init__(self, manager):
173
231
self .supports_binary = None
174
232
self .manager = manager
175
233
self .uuid = str (uuid ())
234
+ # Publish an output area with a unique ID. The javascript can then
235
+ # hook into this area.
176
236
display (HTML ("<div id=%r></div>" % self .uuid ))
177
237
try :
178
238
self .comm = Comm ('matplotlib' , data = {'id' : self .uuid })
@@ -181,12 +241,17 @@ def __init__(self, manager):
181
241
'instance. Are you in the IPython notebook?' )
182
242
self .comm .on_msg (self .on_message )
183
243
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
+
184
250
def on_close (self ):
185
251
# When the socket is closed, deregister the websocket with
186
252
# the FigureManager.
187
- if self .comm in self .manager .web_sockets :
188
- self .manager .remove_web_socket (self )
189
253
self .comm .close ()
254
+ self .manager .clearup_closed ()
190
255
191
256
def send_json (self , content ):
192
257
self .comm .send ({'data' : json .dumps (content )})
0 commit comments