Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7888942

Browse files
committed
FIX: destroy figs in python when destroyed in js
- only actually closes the figure (in the mpl sense) if all of the managers active comms are dead - closes figure when all visible copies are removed from dom - eliminates many of the [IPKernelApp] ERROR | No such comm: XXXX errors - may have caused a regression in figures not closing on kernel restart closes #4841
1 parent 72c82e8 commit 7888942

File tree

2 files changed

+38
-7
lines changed

2 files changed

+38
-7
lines changed

lib/matplotlib/backends/backend_nbagg.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,22 @@ def _create_comm(self):
160160

161161
def destroy(self):
162162
self._send_event('close')
163-
for comm in self.web_sockets.copy():
163+
# need to copy comms as callbacks will modify this list
164+
for comm in list(self.web_sockets):
164165
comm.on_close()
166+
self.clearup_closed()
165167

166168
def clearup_closed(self):
167169
"""Clear up any closed Comms."""
168170
self.web_sockets = set([socket for socket in self.web_sockets
169171
if socket.is_open()])
170172

171173
if len(self.web_sockets) == 0:
172-
self.manager.canvas.close_event()
174+
self.canvas.close_event()
175+
176+
def remove_comm(self, comm_id):
177+
self.web_sockets = set([socket for socket in self.web_sockets
178+
if not socket.comm.comm_id == comm_id])
173179

174180

175181
class TimerTornado(TimerBase):
@@ -267,15 +273,27 @@ def __init__(self, manager):
267273
self.comm.on_msg(self.on_message)
268274

269275
manager = self.manager
270-
self.comm.on_close(lambda close_message: manager.clearup_closed())
276+
self._ext_close = False
277+
278+
def _on_close(close_message):
279+
self._ext_close = True
280+
manager.remove_comm(close_message['content']['comm_id'])
281+
manager.clearup_closed()
282+
283+
self.comm.on_close(_on_close)
271284

272285
def is_open(self):
273-
return not self.comm._closed
286+
return not (self._ext_close or self.comm._closed)
274287

275288
def on_close(self):
276289
# When the socket is closed, deregister the websocket with
277290
# the FigureManager.
278-
self.comm.close()
291+
if self.is_open():
292+
try:
293+
self.comm.close()
294+
except KeyError:
295+
# apparently already cleaned it up?
296+
pass
279297

280298
def send_json(self, content):
281299
self.comm.send({'data': json.dumps(content)})
@@ -298,6 +316,7 @@ def on_message(self, message):
298316
message = json.loads(message['content']['data'])
299317
if message['type'] == 'closing':
300318
self.on_close()
319+
self.manager.clearup_closed()
301320
elif message['type'] == 'supports_binary':
302321
self.supports_binary = message['value']
303322
else:

lib/matplotlib/backends/web_backend/nbagg_mpl.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,21 @@ mpl.mpl_figure_comm = function(comm, msg) {
5555
};
5656

5757
mpl.figure.prototype.handle_close = function(fig, msg) {
58+
fig.root.unbind('remove')
59+
5860
// Update the output cell to use the data from the current canvas.
5961
fig.push_to_output();
6062
var dataURL = fig.canvas.toDataURL();
6163
// Re-enable the keyboard manager in IPython - without this line, in FF,
6264
// the notebook keyboard shortcuts fail.
6365
IPython.keyboard_manager.enable()
6466
$(fig.parent_element).html('<img src="' + dataURL + '">');
65-
fig.send_message('closing', {});
66-
fig.ws.close()
67+
fig.close_ws(fig, msg);
68+
}
69+
70+
mpl.figure.prototype.close_ws = function(fig, msg){
71+
fig.send_message('closing', msg);
72+
// fig.ws.close()
6773
}
6874

6975
mpl.figure.prototype.push_to_output = function(remove_interactive) {
@@ -126,6 +132,12 @@ mpl.figure.prototype._init_toolbar = function() {
126132
titlebar.prepend(buttongrp);
127133
}
128134

135+
mpl.figure.prototype._root_extra_style = function(el){
136+
var fig = this
137+
el.on("remove", function(){
138+
fig.close_ws(fig, {});
139+
});
140+
}
129141

130142
mpl.figure.prototype._canvas_extra_style = function(el){
131143
// this is important to make the div 'focusable

0 commit comments

Comments
 (0)