19
19
import io
20
20
import json
21
21
from base64 import b64encode
22
+ from threading import Lock
22
23
23
24
try :
24
25
from collections .abc import Iterable
67
68
cursors .WAIT : 'wait' ,
68
69
}
69
70
71
+ # threading.Lock to prevent multiple threads from accessing globals such as Gcf
72
+ _lock = Lock ()
73
+
74
+
70
75
71
76
def connection_info ():
72
77
"""
@@ -75,18 +80,19 @@ def connection_info():
75
80
use.
76
81
77
82
"""
78
- result = []
79
- for manager in Gcf .get_all_fig_managers ():
80
- fig = manager .canvas .figure
81
- result .append (
82
- '{} - {}' .format (
83
- (fig .get_label () or f"Figure { manager .num } " ),
84
- manager .web_sockets ,
83
+ with _lock :
84
+ result = []
85
+ for manager in Gcf .get_all_fig_managers ():
86
+ fig = manager .canvas .figure
87
+ result .append (
88
+ '{} - {}' .format (
89
+ (fig .get_label () or f"Figure { manager .num } " ),
90
+ manager .web_sockets ,
91
+ )
85
92
)
86
- )
87
- if not is_interactive ():
88
- result .append (f'Figures pending show: { len (Gcf ._activeQue )} ' )
89
- return '\n ' .join (result )
93
+ if not is_interactive ():
94
+ result .append (f'Figures pending show: { len (Gcf ._activeQue )} ' )
95
+ return '\n ' .join (result )
90
96
91
97
92
98
class Toolbar (DOMWidget , NavigationToolbar2WebAgg ):
@@ -134,7 +140,8 @@ def export(self):
134
140
width = pwidth / self .canvas ._dpi_ratio
135
141
data = "<img src='data:image/png;base64,{0}' width={1}/>"
136
142
data = data .format (b64encode (buf .getvalue ()).decode ('utf-8' ), width )
137
- display (HTML (data ))
143
+ with _lock :
144
+ display (HTML (data ))
138
145
139
146
@default ('toolitems' )
140
147
def _default_toolitems (self ):
@@ -397,7 +404,8 @@ def __init__(self, canvas, num):
397
404
def show (self ):
398
405
if self .canvas ._closed :
399
406
self .canvas ._closed = False
400
- display (self .canvas )
407
+ with _lock :
408
+ display (self .canvas )
401
409
else :
402
410
self .canvas .draw_idle ()
403
411
@@ -415,83 +423,86 @@ class _Backend_ipympl(_Backend):
415
423
416
424
@staticmethod
417
425
def new_figure_manager_given_figure (num , figure ):
418
- canvas = Canvas (figure )
419
- if 'nbagg.transparent' in rcParams and rcParams ['nbagg.transparent' ]:
420
- figure .patch .set_alpha (0 )
421
- manager = FigureManager (canvas , num )
422
-
423
- if is_interactive ():
424
- _Backend_ipympl ._to_show .append (figure )
425
- figure .canvas .draw_idle ()
426
-
427
- def destroy (event ):
428
- canvas .mpl_disconnect (cid )
429
- Gcf .destroy (manager )
430
-
431
- cid = canvas .mpl_connect ('close_event' , destroy )
432
-
433
- # Only register figure for showing when in interactive mode (otherwise
434
- # we'll generate duplicate plots, since a user who set ioff() manually
435
- # expects to make separate draw/show calls).
436
- if is_interactive ():
437
- # ensure current figure will be drawn.
438
- try :
439
- _Backend_ipympl ._to_show .remove (figure )
440
- except ValueError :
441
- # ensure it only appears in the draw list once
442
- pass
443
- # Queue up the figure for drawing in next show() call
444
- _Backend_ipympl ._to_show .append (figure )
445
- _Backend_ipympl ._draw_called = True
446
-
447
- return manager
426
+ with _lock :
427
+ canvas = Canvas (figure )
428
+ if 'nbagg.transparent' in rcParams and rcParams ['nbagg.transparent' ]:
429
+ figure .patch .set_alpha (0 )
430
+ manager = FigureManager (canvas , num )
431
+
432
+ if is_interactive ():
433
+ _Backend_ipympl ._to_show .append (figure )
434
+ figure .canvas .draw_idle ()
435
+
436
+ def destroy (event ):
437
+ canvas .mpl_disconnect (cid )
438
+ Gcf .destroy (manager )
439
+
440
+ cid = canvas .mpl_connect ('close_event' , destroy )
441
+
442
+ # Only register figure for showing when in interactive mode (otherwise
443
+ # we'll generate duplicate plots, since a user who set ioff() manually
444
+ # expects to make separate draw/show calls).
445
+ if is_interactive ():
446
+ # ensure current figure will be drawn.
447
+ try :
448
+ _Backend_ipympl ._to_show .remove (figure )
449
+ except ValueError :
450
+ # ensure it only appears in the draw list once
451
+ pass
452
+ # Queue up the figure for drawing in next show() call
453
+ _Backend_ipympl ._to_show .append (figure )
454
+ _Backend_ipympl ._draw_called = True
455
+
456
+ return manager
448
457
449
458
@staticmethod
450
459
def show (block = None ):
451
- # # TODO: something to do when keyword block==False ?
452
- interactive = is_interactive ()
460
+ with _lock :
461
+ # # TODO: something to do when keyword block==False ?
462
+ interactive = is_interactive ()
453
463
454
- manager = Gcf .get_active ()
455
- if manager is None :
456
- return
464
+ manager = Gcf .get_active ()
465
+ if manager is None :
466
+ return
457
467
458
- try :
459
- display (manager .canvas )
460
- # metadata=_fetch_figure_metadata(manager.canvas.figure)
468
+ try :
469
+ display (manager .canvas )
470
+ # metadata=_fetch_figure_metadata(manager.canvas.figure)
461
471
462
- # plt.figure adds an event which makes the figure in focus the
463
- # active one. Disable this behaviour, as it results in
464
- # figures being put as the active figure after they have been
465
- # shown, even in non-interactive mode.
466
- if hasattr (manager , '_cidgcf' ):
467
- manager .canvas .mpl_disconnect (manager ._cidgcf )
472
+ # plt.figure adds an event which makes the figure in focus the
473
+ # active one. Disable this behaviour, as it results in
474
+ # figures being put as the active figure after they have been
475
+ # shown, even in non-interactive mode.
476
+ if hasattr (manager , '_cidgcf' ):
477
+ manager .canvas .mpl_disconnect (manager ._cidgcf )
468
478
469
- if not interactive :
470
- Gcf .figs .pop (manager .num , None )
471
- finally :
472
- if manager .canvas .figure in _Backend_ipympl ._to_show :
473
- _Backend_ipympl ._to_show .remove (manager .canvas .figure )
479
+ if not interactive :
480
+ Gcf .figs .pop (manager .num , None )
481
+ finally :
482
+ if manager .canvas .figure in _Backend_ipympl ._to_show :
483
+ _Backend_ipympl ._to_show .remove (manager .canvas .figure )
474
484
475
485
476
486
def flush_figures ():
477
- backend = matplotlib .get_backend ()
478
- if backend in ('widget' , 'ipympl' , 'module://ipympl.backend_nbagg' ):
479
- if not _Backend_ipympl ._draw_called :
480
- return
481
-
482
- try :
483
- # exclude any figures that were closed:
484
- active = {fm .canvas .figure for fm in Gcf .get_all_fig_managers ()}
485
-
486
- for fig in [fig for fig in _Backend_ipympl ._to_show if fig in active ]:
487
- # display(fig.canvas, metadata=_fetch_figure_metadata(fig))
488
- display (fig .canvas )
489
- finally :
490
- # clear flags for next round
491
- _Backend_ipympl ._to_show = []
492
- _Backend_ipympl ._draw_called = False
493
-
494
-
495
- ip = get_ipython ()
496
- if ip is not None :
497
- ip .events .register ('post_execute' , flush_figures )
487
+ with _lock :
488
+ backend = matplotlib .get_backend ()
489
+ if backend in ('widget' , 'ipympl' , 'module://ipympl.backend_nbagg' ):
490
+ if not _Backend_ipympl ._draw_called :
491
+ return
492
+
493
+ try :
494
+ # exclude any figures that were closed:
495
+ active = {fm .canvas .figure for fm in Gcf .get_all_fig_managers ()}
496
+ for fig in [fig for fig in _Backend_ipympl ._to_show if fig in active ]:
497
+ # display(fig.canvas, metadata=_fetch_figure_metadata(fig))
498
+ display (fig .canvas )
499
+ finally :
500
+ # clear flags for next round
501
+ _Backend_ipympl ._to_show = []
502
+ _Backend_ipympl ._draw_called = False
503
+
504
+
505
+ with _lock :
506
+ ip = get_ipython ()
507
+ if ip is not None :
508
+ ip .events .register ('post_execute' , flush_figures )
0 commit comments