14
14
import tornado .ioloop
15
15
16
16
from IPython .display import display , Javascript , HTML
17
- from IPython .kernel .comm import Comm
17
+ try :
18
+ # Jupyter/IPython 4.x or later
19
+ from ipykernel .comm import Comm
20
+ except ImportError :
21
+ # Jupyter/IPython 3.x or earlier
22
+ from IPython .kernel .comm import Comm
18
23
19
24
from matplotlib import rcParams
20
25
from matplotlib .figure import Figure
@@ -73,9 +78,10 @@ def connection_info():
73
78
for manager in Gcf .get_all_fig_managers ():
74
79
fig = manager .canvas .figure
75
80
result .append ('{0} - {0}' .format ((fig .get_label () or
76
- "Figure {0}" .format (manager .num )),
77
- manager .web_sockets ))
78
- result .append ('Figures pending show: {0}' .format (len (Gcf ._activeQue )))
81
+ "Figure {0}" .format (manager .num )),
82
+ manager .web_sockets ))
83
+ if not is_interactive ():
84
+ result .append ('Figures pending show: {0}' .format (len (Gcf ._activeQue )))
79
85
return '\n ' .join (result )
80
86
81
87
@@ -161,13 +167,22 @@ def _create_comm(self):
161
167
162
168
def destroy (self ):
163
169
self ._send_event ('close' )
164
- for comm in self .web_sockets .copy ():
170
+ # need to copy comms as callbacks will modify this list
171
+ for comm in list (self .web_sockets ):
165
172
comm .on_close ()
173
+ self .clearup_closed ()
166
174
167
175
def clearup_closed (self ):
168
176
"""Clear up any closed Comms."""
169
177
self .web_sockets = set ([socket for socket in self .web_sockets
170
- if not socket .is_open ()])
178
+ if socket .is_open ()])
179
+
180
+ if len (self .web_sockets ) == 0 :
181
+ self .canvas .close_event ()
182
+
183
+ def remove_comm (self , comm_id ):
184
+ self .web_sockets = set ([socket for socket in self .web_sockets
185
+ if not socket .comm .comm_id == comm_id ])
171
186
172
187
173
188
class TimerTornado (TimerBase ):
@@ -226,13 +241,22 @@ def new_figure_manager_given_figure(num, figure):
226
241
"""
227
242
Create a new figure manager instance for the given figure.
228
243
"""
244
+ from .._pylab_helpers import Gcf
245
+
246
+ def closer (event ):
247
+ Gcf .destroy (num )
248
+
229
249
canvas = FigureCanvasNbAgg (figure )
230
250
if rcParams ['nbagg.transparent' ]:
231
251
figure .patch .set_alpha (0 )
232
252
manager = FigureManagerNbAgg (canvas , num )
253
+
233
254
if is_interactive ():
234
255
manager .show ()
235
256
figure .canvas .draw_idle ()
257
+
258
+ canvas .mpl_connect ('close_event' , closer )
259
+
236
260
return manager
237
261
238
262
@@ -261,16 +285,27 @@ def __init__(self, manager):
261
285
self .comm .on_msg (self .on_message )
262
286
263
287
manager = self .manager
264
- self .comm .on_close (lambda close_message : manager .clearup_closed ())
288
+ self ._ext_close = False
289
+
290
+ def _on_close (close_message ):
291
+ self ._ext_close = True
292
+ manager .remove_comm (close_message ['content' ]['comm_id' ])
293
+ manager .clearup_closed ()
294
+
295
+ self .comm .on_close (_on_close )
265
296
266
297
def is_open (self ):
267
- return not self .comm ._closed
298
+ return not ( self ._ext_close or self . comm ._closed )
268
299
269
300
def on_close (self ):
270
301
# When the socket is closed, deregister the websocket with
271
302
# the FigureManager.
272
- self .comm .close ()
273
- self .manager .clearup_closed ()
303
+ if self .is_open ():
304
+ try :
305
+ self .comm .close ()
306
+ except KeyError :
307
+ # apparently already cleaned it up?
308
+ pass
274
309
275
310
def send_json (self , content ):
276
311
self .comm .send ({'data' : json .dumps (content )})
@@ -293,6 +328,7 @@ def on_message(self, message):
293
328
message = json .loads (message ['content' ]['data' ])
294
329
if message ['type' ] == 'closing' :
295
330
self .on_close ()
331
+ self .manager .clearup_closed ()
296
332
elif message ['type' ] == 'supports_binary' :
297
333
self .supports_binary = message ['value' ]
298
334
else :
0 commit comments