10
10
import os
11
11
import random
12
12
import socket
13
+ import threading
13
14
14
15
import numpy as np
15
16
20
21
import tornado .web
21
22
import tornado .ioloop
22
23
import tornado .websocket
23
- import tornado .template
24
24
25
25
import matplotlib
26
26
from matplotlib import rcParams
30
30
from matplotlib ._pylab_helpers import Gcf
31
31
from matplotlib import _png
32
32
33
+ # TODO: This should really only be set for the IPython notebook, but
34
+ # I'm not sure how to detect that.
35
+ try :
36
+ __IPYTHON__
37
+ except :
38
+ _in_ipython = False
39
+ else :
40
+ _in_ipython = True
41
+
33
42
34
43
def draw_if_interactive ():
35
44
"""
@@ -46,8 +55,8 @@ def mainloop(self):
46
55
WebAggApplication .initialize ()
47
56
48
57
url = "http://127.0.0.1:{port}{prefix}" .format (
49
- port = WebAggApplication .port ,
50
- prefix = WebAggApplication .url_prefix )
58
+ port = WebAggApplication .port ,
59
+ prefix = WebAggApplication .url_prefix )
51
60
52
61
if rcParams ['webagg.open_in_browser' ]:
53
62
import webbrowser
@@ -57,7 +66,25 @@ def mainloop(self):
57
66
58
67
WebAggApplication .start ()
59
68
60
- show = Show ()
69
+
70
+ if not _in_ipython :
71
+ show = Show ()
72
+ else :
73
+ def show ():
74
+ from IPython .display import display_html
75
+
76
+ result = []
77
+ import matplotlib ._pylab_helpers as pylab_helpers
78
+ for manager in pylab_helpers .Gcf ().get_all_fig_managers ():
79
+ result .append (ipython_inline_display (manager .canvas .figure ))
80
+ return display_html ('\n ' .join (result ), raw = True )
81
+
82
+
83
+ class ServerThread (threading .Thread ):
84
+ def run (self ):
85
+ tornado .ioloop .IOLoop .instance ().start ()
86
+
87
+ server_thread = ServerThread ()
61
88
62
89
63
90
def new_figure_manager (num , * args , ** kwargs ):
@@ -127,6 +154,16 @@ def __init__(self, *args, **kwargs):
127
154
# messages from piling up.
128
155
self ._pending_draw = None
129
156
157
+ # TODO: I'd like to dynamically add the _repr_html_ method
158
+ # to the figure in the right context, but then IPython doesn't
159
+ # use it, for some reason.
160
+
161
+ # Add the _repr_html_ member to the figure for IPython inline
162
+ # support
163
+ # if _in_ipython:
164
+ # self.figure._repr_html_ = types.MethodType(
165
+ # ipython_inline_display, self.figure, self.figure.__class__)
166
+
130
167
def show (self ):
131
168
# show the figure window
132
169
show ()
@@ -199,7 +236,7 @@ def get_diff_image(self):
199
236
self ._png_is_old = False
200
237
return self ._png_buffer .getvalue ()
201
238
202
- def get_renderer (self , cleared = False ):
239
+ def get_renderer (self , cleared = None ):
203
240
# Mirrors super.get_renderer, but caches the old one
204
241
# so that we can do things such as prodce a diff image
205
242
# in get_diff_image
@@ -269,12 +306,12 @@ def start_event_loop(self, timeout):
269
306
backend_bases .FigureCanvasBase .start_event_loop_default (
270
307
self , timeout )
271
308
start_event_loop .__doc__ = \
272
- backend_bases .FigureCanvasBase .start_event_loop_default .__doc__
309
+ backend_bases .FigureCanvasBase .start_event_loop_default .__doc__
273
310
274
311
def stop_event_loop (self ):
275
312
backend_bases .FigureCanvasBase .stop_event_loop_default (self )
276
313
stop_event_loop .__doc__ = \
277
- backend_bases .FigureCanvasBase .stop_event_loop_default .__doc__
314
+ backend_bases .FigureCanvasBase .stop_event_loop_default .__doc__
278
315
279
316
280
317
class FigureManagerWebAgg (backend_bases .FigureManagerBase ):
@@ -313,26 +350,29 @@ def resize(self, w, h):
313
350
314
351
315
352
class NavigationToolbar2WebAgg (backend_bases .NavigationToolbar2 ):
316
- _jquery_icon_classes = {'home' : 'ui-icon ui-icon-home' ,
317
- 'back' : 'ui-icon ui-icon-circle-arrow-w' ,
318
- 'forward' : 'ui-icon ui-icon-circle-arrow-e' ,
319
- 'zoom_to_rect' : 'ui-icon ui-icon-search' ,
320
- 'move' : 'ui-icon ui-icon-arrow-4' ,
321
- 'download' : 'ui-icon ui-icon-disk' ,
322
- None : None
323
- }
353
+ _jquery_icon_classes = {
354
+ 'home' : 'ui-icon ui-icon-home' ,
355
+ 'back' : 'ui-icon ui-icon-circle-arrow-w' ,
356
+ 'forward' : 'ui-icon ui-icon-circle-arrow-e' ,
357
+ 'zoom_to_rect' : 'ui-icon ui-icon-search' ,
358
+ 'move' : 'ui-icon ui-icon-arrow-4' ,
359
+ 'download' : 'ui-icon ui-icon-disk' ,
360
+ None : None
361
+ }
324
362
325
363
def _init_toolbar (self ):
326
364
# Use the standard toolbar items + download button
327
- toolitems = (backend_bases .NavigationToolbar2 .toolitems +
328
- (('Download' , 'Download plot' , 'download' , 'download' ),))
365
+ toolitems = (
366
+ backend_bases .NavigationToolbar2 .toolitems +
367
+ (('Download' , 'Download plot' , 'download' , 'download' ),)
368
+ )
329
369
330
370
NavigationToolbar2WebAgg .toolitems = \
331
371
tuple (
332
- (text , tooltip_text , self ._jquery_icon_classes [image_file ],
333
- name_of_method )
334
- for text , tooltip_text , image_file , name_of_method
335
- in toolitems if image_file in self ._jquery_icon_classes )
372
+ (text , tooltip_text , self ._jquery_icon_classes [image_file ],
373
+ name_of_method )
374
+ for text , tooltip_text , image_file , name_of_method
375
+ in toolitems if image_file in self ._jquery_icon_classes )
336
376
337
377
self .message = ''
338
378
self .cursor = 0
@@ -388,22 +428,18 @@ def __init__(self, application, request, **kwargs):
388
428
request , ** kwargs )
389
429
390
430
def get (self , fignum ):
391
- with open (os .path .join (WebAggApplication ._mpl_dirs ['web_backend' ],
392
- 'single_figure.html' )) as fd :
393
- tpl = fd .read ()
394
-
395
431
fignum = int (fignum )
396
432
manager = Gcf .get_fig_manager (fignum )
397
433
398
434
ws_uri = 'ws://{req.host}{prefix}/' .format (req = self .request ,
399
435
prefix = self .url_prefix )
400
- t = tornado . template . Template ( tpl )
401
- self . write ( t . generate (
436
+ self . render (
437
+ "single_figure.html" ,
402
438
prefix = self .url_prefix ,
403
439
ws_uri = ws_uri ,
404
440
fig_id = fignum ,
405
441
toolitems = NavigationToolbar2WebAgg .toolitems ,
406
- canvas = manager .canvas ))
442
+ canvas = manager .canvas )
407
443
408
444
class AllFiguresPage (tornado .web .RequestHandler ):
409
445
def __init__ (self , application , request , ** kwargs ):
@@ -412,34 +448,27 @@ def __init__(self, application, request, **kwargs):
412
448
request , ** kwargs )
413
449
414
450
def get (self ):
415
- with open (os .path .join (WebAggApplication ._mpl_dirs ['web_backend' ],
416
- 'all_figures.html' )) as fd :
417
- tpl = fd .read ()
418
-
419
451
ws_uri = 'ws://{req.host}{prefix}/' .format (req = self .request ,
420
452
prefix = self .url_prefix )
421
- t = tornado .template .Template (tpl )
422
-
423
- self .write (t .generate (
453
+ self .render (
454
+ "all_figures.html" ,
424
455
prefix = self .url_prefix ,
425
456
ws_uri = ws_uri ,
426
- figures = sorted (list ( Gcf . figs . items ()), key = lambda item : item [ 0 ]),
427
- toolitems = NavigationToolbar2WebAgg . toolitems ))
428
-
457
+ figures = sorted (
458
+ list ( Gcf . figs . items ()), key = lambda item : item [ 0 ]),
459
+ toolitems = NavigationToolbar2WebAgg . toolitems )
429
460
430
461
class MPLInterfaceJS (tornado .web .RequestHandler ):
431
- def get (self , fignum ):
432
- with open (os .path .join (WebAggApplication ._mpl_dirs ['web_backend' ],
433
- 'mpl_interface.js' )) as fd :
434
- tpl = fd .read ()
462
+ def get (self ):
463
+ manager = Gcf .get_fig_manager (1 )
464
+ canvas = manager .canvas
435
465
436
- fignum = int (fignum )
437
- manager = Gcf .get_fig_manager (fignum )
466
+ self .set_header ('Content-Type' , 'application/javascript' )
438
467
439
- t = tornado . template . Template ( tpl )
440
- self . write ( t . generate (
468
+ self . render (
469
+ "mpl_interface.js" ,
441
470
toolitems = NavigationToolbar2WebAgg .toolitems ,
442
- canvas = manager . canvas ) )
471
+ canvas = canvas )
443
472
444
473
class Download (tornado .web .RequestHandler ):
445
474
def get (self , fignum , fmt ):
@@ -516,7 +545,7 @@ def send_diff_image(self, diff):
516
545
def __init__ (self , url_prefix = '' ):
517
546
if url_prefix :
518
547
assert url_prefix [0 ] == '/' and url_prefix [- 1 ] != '/' , \
519
- 'url_prefix must start with a "/" and not end with one.'
548
+ 'url_prefix must start with a "/" and not end with one.'
520
549
521
550
super (WebAggApplication , self ).__init__ ([
522
551
# Static files for the CSS and JS
@@ -539,11 +568,13 @@ def __init__(self, url_prefix=''):
539
568
{'path' : os .path .join (self ._mpl_dirs ['web_backend' ], 'jquery' ,
540
569
'css' , 'themes' , 'base' , 'images' )}),
541
570
542
- (url_prefix + r'/_static/jquery/js/(.*)' , tornado .web .StaticFileHandler ,
571
+ (url_prefix + r'/_static/jquery/js/(.*)' ,
572
+ tornado .web .StaticFileHandler ,
543
573
{'path' : os .path .join (self ._mpl_dirs ['web_backend' ],
544
574
'jquery' , 'js' )}),
545
575
546
- (url_prefix + r'/_static/css/(.*)' , tornado .web .StaticFileHandler ,
576
+ (url_prefix + r'/_static/css/(.*)' ,
577
+ tornado .web .StaticFileHandler ,
547
578
{'path' : os .path .join (self ._mpl_dirs ['web_backend' ], 'css' )}),
548
579
549
580
# An MPL favicon
@@ -553,19 +584,20 @@ def __init__(self, url_prefix=''):
553
584
(url_prefix + r'/([0-9]+)' , self .SingleFigurePage ,
554
585
{'url_prefix' : url_prefix }),
555
586
556
- (url_prefix + r'/([0-9]+)/ mpl_interface.js' , self .MPLInterfaceJS ),
587
+ (url_prefix + r'/mpl_interface.js' , self .MPLInterfaceJS ),
557
588
558
589
# Sends images and events to the browser, and receives
559
590
# events from the browser
560
591
(url_prefix + r'/([0-9]+)/ws' , self .WebSocket ),
561
592
562
593
# Handles the downloading (i.e., saving) of static images
563
- (url_prefix + r'/([0-9]+)/download.([a-z ]+)' , self .Download ),
594
+ (url_prefix + r'/([0-9]+)/download.([a-z0-9. ]+)' , self .Download ),
564
595
565
596
# The page that contains all of the figures
566
597
(url_prefix + r'/?' , self .AllFiguresPage ,
567
598
{'url_prefix' : url_prefix }),
568
- ])
599
+ ],
600
+ template_path = self ._mpl_dirs ['web_backend' ])
569
601
570
602
@classmethod
571
603
def initialize (cls , url_prefix = '' ):
@@ -623,3 +655,27 @@ def start(cls):
623
655
print ("Server stopped" )
624
656
625
657
cls .started = True
658
+
659
+
660
+ def ipython_inline_display (figure ):
661
+ import matplotlib ._pylab_helpers as pylab_helpers
662
+ import tornado .template
663
+
664
+ WebAggApplication .initialize ()
665
+ if not server_thread .is_alive ():
666
+ server_thread .start ()
667
+
668
+ with open (os .path .join (
669
+ WebAggApplication ._mpl_dirs ['web_backend' ],
670
+ 'ipython_inline_figure.html' )) as fd :
671
+ tpl = fd .read ()
672
+
673
+ fignum = figure .number
674
+
675
+ t = tornado .template .Template (tpl )
676
+ return t .generate (
677
+ prefix = WebAggApplication .url_prefix ,
678
+ fig_id = fignum ,
679
+ toolitems = NavigationToolbar2WebAgg .toolitems ,
680
+ canvas = figure .canvas ,
681
+ port = WebAggApplication .port )
0 commit comments