1010import os
1111import random
1212import socket
13+ import threading
1314
1415import numpy as np
1516
2021import tornado .web
2122import tornado .ioloop
2223import tornado .websocket
23- import tornado .template
2424
2525import matplotlib
2626from matplotlib import rcParams
3030from matplotlib ._pylab_helpers import Gcf
3131from matplotlib import _png
3232
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+
3342
3443def draw_if_interactive ():
3544 """
@@ -46,8 +55,8 @@ def mainloop(self):
4655 WebAggApplication .initialize ()
4756
4857 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 )
5160
5261 if rcParams ['webagg.open_in_browser' ]:
5362 import webbrowser
@@ -57,7 +66,25 @@ def mainloop(self):
5766
5867 WebAggApplication .start ()
5968
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 ()
6188
6289
6390def new_figure_manager (num , * args , ** kwargs ):
@@ -127,6 +154,16 @@ def __init__(self, *args, **kwargs):
127154 # messages from piling up.
128155 self ._pending_draw = None
129156
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+
130167 def show (self ):
131168 # show the figure window
132169 show ()
@@ -199,7 +236,7 @@ def get_diff_image(self):
199236 self ._png_is_old = False
200237 return self ._png_buffer .getvalue ()
201238
202- def get_renderer (self , cleared = False ):
239+ def get_renderer (self , cleared = None ):
203240 # Mirrors super.get_renderer, but caches the old one
204241 # so that we can do things such as prodce a diff image
205242 # in get_diff_image
@@ -269,12 +306,12 @@ def start_event_loop(self, timeout):
269306 backend_bases .FigureCanvasBase .start_event_loop_default (
270307 self , timeout )
271308 start_event_loop .__doc__ = \
272- backend_bases .FigureCanvasBase .start_event_loop_default .__doc__
309+ backend_bases .FigureCanvasBase .start_event_loop_default .__doc__
273310
274311 def stop_event_loop (self ):
275312 backend_bases .FigureCanvasBase .stop_event_loop_default (self )
276313 stop_event_loop .__doc__ = \
277- backend_bases .FigureCanvasBase .stop_event_loop_default .__doc__
314+ backend_bases .FigureCanvasBase .stop_event_loop_default .__doc__
278315
279316
280317class FigureManagerWebAgg (backend_bases .FigureManagerBase ):
@@ -313,26 +350,29 @@ def resize(self, w, h):
313350
314351
315352class 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+ }
324362
325363 def _init_toolbar (self ):
326364 # 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+ )
329369
330370 NavigationToolbar2WebAgg .toolitems = \
331371 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 )
336376
337377 self .message = ''
338378 self .cursor = 0
@@ -388,22 +428,18 @@ def __init__(self, application, request, **kwargs):
388428 request , ** kwargs )
389429
390430 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-
395431 fignum = int (fignum )
396432 manager = Gcf .get_fig_manager (fignum )
397433
398434 ws_uri = 'ws://{req.host}{prefix}/' .format (req = self .request ,
399435 prefix = self .url_prefix )
400- t = tornado . template . Template ( tpl )
401- self . write ( t . generate (
436+ self . render (
437+ "single_figure.html" ,
402438 prefix = self .url_prefix ,
403439 ws_uri = ws_uri ,
404440 fig_id = fignum ,
405441 toolitems = NavigationToolbar2WebAgg .toolitems ,
406- canvas = manager .canvas ))
442+ canvas = manager .canvas )
407443
408444 class AllFiguresPage (tornado .web .RequestHandler ):
409445 def __init__ (self , application , request , ** kwargs ):
@@ -412,34 +448,27 @@ def __init__(self, application, request, **kwargs):
412448 request , ** kwargs )
413449
414450 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-
419451 ws_uri = 'ws://{req.host}{prefix}/' .format (req = self .request ,
420452 prefix = self .url_prefix )
421- t = tornado .template .Template (tpl )
422-
423- self .write (t .generate (
453+ self .render (
454+ "all_figures.html" ,
424455 prefix = self .url_prefix ,
425456 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 )
429460
430461 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
435465
436- fignum = int (fignum )
437- manager = Gcf .get_fig_manager (fignum )
466+ self .set_header ('Content-Type' , 'application/javascript' )
438467
439- t = tornado . template . Template ( tpl )
440- self . write ( t . generate (
468+ self . render (
469+ "mpl_interface.js" ,
441470 toolitems = NavigationToolbar2WebAgg .toolitems ,
442- canvas = manager . canvas ) )
471+ canvas = canvas )
443472
444473 class Download (tornado .web .RequestHandler ):
445474 def get (self , fignum , fmt ):
@@ -516,7 +545,7 @@ def send_diff_image(self, diff):
516545 def __init__ (self , url_prefix = '' ):
517546 if url_prefix :
518547 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.'
520549
521550 super (WebAggApplication , self ).__init__ ([
522551 # Static files for the CSS and JS
@@ -539,11 +568,13 @@ def __init__(self, url_prefix=''):
539568 {'path' : os .path .join (self ._mpl_dirs ['web_backend' ], 'jquery' ,
540569 'css' , 'themes' , 'base' , 'images' )}),
541570
542- (url_prefix + r'/_static/jquery/js/(.*)' , tornado .web .StaticFileHandler ,
571+ (url_prefix + r'/_static/jquery/js/(.*)' ,
572+ tornado .web .StaticFileHandler ,
543573 {'path' : os .path .join (self ._mpl_dirs ['web_backend' ],
544574 'jquery' , 'js' )}),
545575
546- (url_prefix + r'/_static/css/(.*)' , tornado .web .StaticFileHandler ,
576+ (url_prefix + r'/_static/css/(.*)' ,
577+ tornado .web .StaticFileHandler ,
547578 {'path' : os .path .join (self ._mpl_dirs ['web_backend' ], 'css' )}),
548579
549580 # An MPL favicon
@@ -553,19 +584,20 @@ def __init__(self, url_prefix=''):
553584 (url_prefix + r'/([0-9]+)' , self .SingleFigurePage ,
554585 {'url_prefix' : url_prefix }),
555586
556- (url_prefix + r'/([0-9]+)/ mpl_interface.js' , self .MPLInterfaceJS ),
587+ (url_prefix + r'/mpl_interface.js' , self .MPLInterfaceJS ),
557588
558589 # Sends images and events to the browser, and receives
559590 # events from the browser
560591 (url_prefix + r'/([0-9]+)/ws' , self .WebSocket ),
561592
562593 # 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 ),
564595
565596 # The page that contains all of the figures
566597 (url_prefix + r'/?' , self .AllFiguresPage ,
567598 {'url_prefix' : url_prefix }),
568- ])
599+ ],
600+ template_path = self ._mpl_dirs ['web_backend' ])
569601
570602 @classmethod
571603 def initialize (cls , url_prefix = '' ):
@@ -623,3 +655,27 @@ def start(cls):
623655 print ("Server stopped" )
624656
625657 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