Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bdfdbdf

Browse filesBrowse files
committed
Merge pull request #2054 from mdboom/ipython-webagg-integration
Ipython/Webagg integration
2 parents b44bdfe + bdb85ec commit bdfdbdf
Copy full SHA for bdfdbdf

File tree

8 files changed

+356
-300
lines changed
Filter options

8 files changed

+356
-300
lines changed

‎lib/matplotlib/backends/backend_webagg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_webagg.py
+109-53Lines changed: 109 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import os
1111
import random
1212
import socket
13+
import threading
1314

1415
import numpy as np
1516

@@ -20,7 +21,6 @@
2021
import tornado.web
2122
import tornado.ioloop
2223
import tornado.websocket
23-
import tornado.template
2424

2525
import matplotlib
2626
from matplotlib import rcParams
@@ -30,6 +30,15 @@
3030
from matplotlib._pylab_helpers import Gcf
3131
from 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

3443
def 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

6390
def 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

280317
class FigureManagerWebAgg(backend_bases.FigureManagerBase):
@@ -313,26 +350,29 @@ def resize(self, w, h):
313350

314351

315352
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+
}
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

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.