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

Skip to content

Commit 9e477b3

Browse files
committed
Merge pull request #1878 from pelson/webagg_changes
Webagg changes
2 parents 1ccf29d + 786a6b4 commit 9e477b3

File tree

7 files changed

+521
-257
lines changed

7 files changed

+521
-257
lines changed

lib/matplotlib/_pylab_helpers.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sys, gc
77

88
import atexit
9-
import traceback
109

1110

1211
def error_msg(msg):

lib/matplotlib/backends/backend_webagg.py

Lines changed: 160 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,16 @@ def draw_if_interactive():
4444
class Show(backend_bases.ShowBase):
4545
def mainloop(self):
4646
WebAggApplication.initialize()
47-
for manager in Gcf.get_all_fig_managers():
48-
url = "http://127.0.0.1:{0}/{1}/".format(
49-
WebAggApplication.port, manager.num)
50-
if rcParams['webagg.open_in_browser']:
51-
import webbrowser
52-
webbrowser.open(url)
53-
else:
54-
print("To view figure, visit {0}".format(url))
47+
48+
url = "http://127.0.0.1:{port}{prefix}".format(
49+
port=WebAggApplication.port,
50+
prefix=WebAggApplication.url_prefix)
51+
52+
if rcParams['webagg.open_in_browser']:
53+
import webbrowser
54+
webbrowser.open(url)
55+
else:
56+
print("To view figure, visit {0}".format(url))
5557

5658
WebAggApplication.start()
5759

@@ -161,9 +163,9 @@ def get_diff_image(self):
161163
# The buffer is created as type uint32 so that entire
162164
# pixels can be compared in one numpy call, rather than
163165
# needing to compare each plane separately.
164-
buffer = np.frombuffer(
166+
buff = np.frombuffer(
165167
self._renderer.buffer_rgba(), dtype=np.uint32)
166-
buffer.shape = (
168+
buff.shape = (
167169
self._renderer.height, self._renderer.width)
168170

169171
if not self._force_full:
@@ -172,10 +174,10 @@ def get_diff_image(self):
172174
last_buffer.shape = (
173175
self._renderer.height, self._renderer.width)
174176

175-
diff = buffer != last_buffer
176-
output = np.where(diff, buffer, 0)
177+
diff = buff != last_buffer
178+
output = np.where(diff, buff, 0)
177179
else:
178-
output = buffer
180+
output = buff
179181

180182
# Clear out the PNG data buffer rather than recreating it
181183
# each time. This reduces the number of memory
@@ -198,27 +200,30 @@ def get_diff_image(self):
198200
return self._png_buffer.getvalue()
199201

200202
def get_renderer(self):
201-
l, b, w, h = self.figure.bbox.bounds
203+
# Mirrors super.get_renderer, but caches the old one
204+
# so that we can do things such as prodce a diff image
205+
# in get_diff_image
206+
_, _, w, h = self.figure.bbox.bounds
202207
key = w, h, self.figure.dpi
203208
try:
204209
self._lastKey, self._renderer
205210
except AttributeError:
206211
need_new_renderer = True
207212
else:
208213
need_new_renderer = (self._lastKey != key)
209-
214+
210215
if need_new_renderer:
211216
self._renderer = backend_agg.RendererAgg(
212217
w, h, self.figure.dpi)
213218
self._last_renderer = backend_agg.RendererAgg(
214219
w, h, self.figure.dpi)
215220
self._lastKey = key
216-
221+
217222
return self._renderer
218223

219224
def handle_event(self, event):
220-
type = event['type']
221-
if type in ('button_press', 'button_release', 'motion_notify'):
225+
e_type = event['type']
226+
if e_type in ('button_press', 'button_release', 'motion_notify'):
222227
x = event['x']
223228
y = event['y']
224229
y = self.get_renderer().height - y
@@ -234,23 +239,24 @@ def handle_event(self, event):
234239
if button == 2:
235240
button = 3
236241

237-
if type == 'button_press':
242+
if e_type == 'button_press':
238243
self.button_press_event(x, y, button)
239-
elif type == 'button_release':
244+
elif e_type == 'button_release':
240245
self.button_release_event(x, y, button)
241-
elif type == 'motion_notify':
246+
elif e_type == 'motion_notify':
242247
self.motion_notify_event(x, y)
243-
elif type in ('key_press', 'key_release'):
248+
elif e_type in ('key_press', 'key_release'):
244249
key = event['key']
245250

246-
if type == 'key_press':
251+
if e_type == 'key_press':
247252
self.key_press_event(key)
248-
elif type == 'key_release':
253+
elif e_type == 'key_release':
249254
self.key_release_event(key)
250-
elif type == 'toolbar_button':
255+
elif e_type == 'toolbar_button':
256+
print('Toolbar button pressed: ', event['name'])
251257
# TODO: Be more suspicious of the input
252258
getattr(self.toolbar, event['name'])()
253-
elif type == 'refresh':
259+
elif e_type == 'refresh':
254260
self._force_full = True
255261
self.draw_idle()
256262

@@ -306,24 +312,27 @@ def resize(self, w, h):
306312

307313

308314
class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2):
309-
toolitems = list(backend_bases.NavigationToolbar2.toolitems[:6]) + [
310-
('Download', 'Download plot', 'filesave', 'download')
311-
]
315+
_jquery_icon_classes = {'home': 'ui-icon ui-icon-home',
316+
'back': 'ui-icon ui-icon-circle-arrow-w',
317+
'forward': 'ui-icon ui-icon-circle-arrow-e',
318+
'zoom_to_rect': 'ui-icon ui-icon-search',
319+
'move': 'ui-icon ui-icon-arrow-4',
320+
'download': 'ui-icon ui-icon-disk',
321+
None: None
322+
}
312323

313324
def _init_toolbar(self):
314-
jqueryui_icons = [
315-
'ui-icon ui-icon-home',
316-
'ui-icon ui-icon-circle-arrow-w',
317-
'ui-icon ui-icon-circle-arrow-e',
318-
None,
319-
'ui-icon ui-icon-arrow-4',
320-
'ui-icon ui-icon-search',
321-
'ui-icon ui-icon-disk'
322-
]
323-
for index, item in enumerate(self.toolitems):
324-
if item[0] is not None:
325-
self.toolitems[index] = (
326-
item[0], item[1], jqueryui_icons[index], item[3])
325+
# Use the standard toolbar items + download button
326+
toolitems = (backend_bases.NavigationToolbar2.toolitems +
327+
(('Download', 'Download plot', 'download', 'download'),))
328+
329+
NavigationToolbar2WebAgg.toolitems = \
330+
tuple(
331+
(text, tooltip_text, self._jquery_icon_classes[image_file],
332+
name_of_method)
333+
for text, tooltip_text, image_file, name_of_method
334+
in toolitems if image_file in self._jquery_icon_classes)
335+
327336
self.message = ''
328337
self.cursor = 0
329338

@@ -356,20 +365,71 @@ def release_zoom(self, event):
356365
class WebAggApplication(tornado.web.Application):
357366
initialized = False
358367
started = False
368+
369+
_mpl_data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),
370+
'mpl-data')
371+
_mpl_dirs = {'mpl-data': _mpl_data_path,
372+
'images': os.path.join(_mpl_data_path, 'images'),
373+
'web_backend': os.path.join(os.path.dirname(__file__),
374+
'web_backend')}
359375

360376
class FavIcon(tornado.web.RequestHandler):
361377
def get(self):
362378
self.set_header('Content-Type', 'image/png')
363-
with open(os.path.join(
364-
os.path.dirname(__file__),
365-
'../mpl-data/images/matplotlib.png')) as fd:
379+
with open(os.path.join(WebAggApplication._mpl_dirs['images'],
380+
'matplotlib.png')) as fd:
366381
self.write(fd.read())
367382

368-
class IndexPage(tornado.web.RequestHandler):
383+
class SingleFigurePage(tornado.web.RequestHandler):
384+
def __init__(self, application, request, **kwargs):
385+
self.url_prefix = kwargs.pop('url_prefix', '')
386+
return tornado.web.RequestHandler.__init__(self, application,
387+
request, **kwargs)
388+
389+
def get(self, fignum):
390+
with open(os.path.join(WebAggApplication._mpl_dirs['web_backend'],
391+
'single_figure.html')) as fd:
392+
tpl = fd.read()
393+
394+
fignum = int(fignum)
395+
manager = Gcf.get_fig_manager(fignum)
396+
397+
ws_uri = 'ws://{req.host}{prefix}/'.format(req=self.request,
398+
prefix=self.url_prefix)
399+
t = tornado.template.Template(tpl)
400+
self.write(t.generate(
401+
prefix=self.url_prefix,
402+
ws_uri=ws_uri,
403+
fig_id=fignum,
404+
toolitems=NavigationToolbar2WebAgg.toolitems,
405+
canvas=manager.canvas))
406+
407+
class AllFiguresPage(tornado.web.RequestHandler):
408+
def __init__(self, application, request, **kwargs):
409+
self.url_prefix = kwargs.pop('url_prefix', '')
410+
return tornado.web.RequestHandler.__init__(self, application,
411+
request, **kwargs)
412+
413+
def get(self):
414+
with open(os.path.join(WebAggApplication._mpl_dirs['web_backend'],
415+
'all_figures.html')) as fd:
416+
tpl = fd.read()
417+
418+
ws_uri = 'ws://{req.host}{prefix}/'.format(req=self.request,
419+
prefix=self.url_prefix)
420+
t = tornado.template.Template(tpl)
421+
422+
self.write(t.generate(
423+
prefix=self.url_prefix,
424+
ws_uri=ws_uri,
425+
figures = sorted(list(Gcf.figs.items()), key=lambda item: item[0]),
426+
toolitems=NavigationToolbar2WebAgg.toolitems))
427+
428+
429+
class MPLInterfaceJS(tornado.web.RequestHandler):
369430
def get(self, fignum):
370-
with open(os.path.join(
371-
os.path.dirname(__file__),
372-
'web_backend', 'index.html')) as fd:
431+
with open(os.path.join(WebAggApplication._mpl_dirs['web_backend'],
432+
'mpl_interface.js')) as fd:
373433
tpl = fd.read()
374434

375435
fignum = int(fignum)
@@ -381,7 +441,7 @@ def get(self, fignum):
381441
canvas=manager.canvas))
382442

383443
class Download(tornado.web.RequestHandler):
384-
def get(self, fignum, format):
444+
def get(self, fignum, fmt):
385445
self.fignum = int(fignum)
386446
manager = Gcf.get_fig_manager(self.fignum)
387447

@@ -397,11 +457,11 @@ def get(self, fignum, format):
397457
'emf': 'application/emf'
398458
}
399459

400-
self.set_header('Content-Type', mimetypes.get(format, 'binary'))
460+
self.set_header('Content-Type', mimetypes.get(fmt, 'binary'))
401461

402-
buffer = io.BytesIO()
403-
manager.canvas.print_figure(buffer, format=format)
404-
self.write(buffer.getvalue())
462+
buff = io.BytesIO()
463+
manager.canvas.print_figure(buff, format=fmt)
464+
self.write(buff.getvalue())
405465

406466
class WebSocket(tornado.websocket.WebSocketHandler):
407467
supports_binary = True
@@ -410,7 +470,7 @@ def open(self, fignum):
410470
self.fignum = int(fignum)
411471
manager = Gcf.get_fig_manager(self.fignum)
412472
manager.add_web_socket(self)
413-
l, b, w, h = manager.canvas.figure.bbox.bounds
473+
_, _, w, h = manager.canvas.figure.bbox.bounds
414474
manager.resize(w, h)
415475
self.on_message('{"type":"refresh"}')
416476

@@ -443,52 +503,69 @@ def send_image(self):
443503
diff.encode('base64').replace('\n', ''))
444504
self.write_message(data_uri)
445505

446-
def __init__(self):
506+
def __init__(self, url_prefix=''):
507+
if url_prefix:
508+
assert url_prefix[0] == '/' and url_prefix[-1] != '/', \
509+
'url_prefix must start with a "/" and not end with one.'
510+
447511
super(WebAggApplication, self).__init__([
448512
# Static files for the CSS and JS
449-
(r'/static/(.*)',
513+
(url_prefix + r'/_static/(.*)',
450514
tornado.web.StaticFileHandler,
451-
{'path':
452-
os.path.join(os.path.dirname(__file__), 'web_backend')}),
515+
{'path': self._mpl_dirs['web_backend']}),
516+
453517
# Static images for toolbar buttons
454-
(r'/images/(.*)',
518+
(url_prefix + r'/_static/images/(.*)',
455519
tornado.web.StaticFileHandler,
456-
{'path':
457-
os.path.join(os.path.dirname(__file__), '../mpl-data/images')}),
458-
(r'/static/jquery/css/themes/base/(.*)',
520+
{'path': self._mpl_dirs['images']}),
521+
522+
(url_prefix + r'/_static/jquery/css/themes/base/(.*)',
459523
tornado.web.StaticFileHandler,
460-
{'path':
461-
os.path.join(os.path.dirname(__file__),
462-
'web_backend/jquery/css/themes/base')}),
463-
(r'/static/jquery/css/themes/base/images/(.*)',
524+
{'path': os.path.join(self._mpl_dirs['web_backend'], 'jquery',
525+
'css', 'themes', 'base')}),
526+
527+
(url_prefix + r'/_static/jquery/css/themes/base/images/(.*)',
464528
tornado.web.StaticFileHandler,
465-
{'path':
466-
os.path.join(os.path.dirname(__file__),
467-
'web_backend/jquery/css/themes/base/images')}),
468-
(r'/static/jquery/js/(.*)', tornado.web.StaticFileHandler,
469-
{'path':
470-
os.path.join(os.path.dirname(__file__),
471-
'web_backend/jquery/js')}),
472-
(r'/static/css/(.*)', tornado.web.StaticFileHandler,
473-
{'path':
474-
os.path.join(os.path.dirname(__file__), 'web_backend/css')}),
529+
{'path': os.path.join(self._mpl_dirs['web_backend'], 'jquery',
530+
'css', 'themes', 'base', 'images')}),
531+
532+
(url_prefix + r'/_static/jquery/js/(.*)', tornado.web.StaticFileHandler,
533+
{'path': os.path.join(self._mpl_dirs['web_backend'],
534+
'jquery', 'js')}),
535+
536+
(url_prefix + r'/_static/css/(.*)', tornado.web.StaticFileHandler,
537+
{'path': os.path.join(self._mpl_dirs['web_backend'], 'css')}),
538+
475539
# An MPL favicon
476-
(r'/favicon.ico', self.FavIcon),
540+
(url_prefix + r'/favicon.ico', self.FavIcon),
541+
477542
# The page that contains all of the pieces
478-
(r'/([0-9]+)/', self.IndexPage),
543+
(url_prefix + r'/([0-9]+)', self.SingleFigurePage,
544+
{'url_prefix': url_prefix}),
545+
546+
(url_prefix + r'/([0-9]+)/mpl_interface.js', self.MPLInterfaceJS),
547+
479548
# Sends images and events to the browser, and receives
480549
# events from the browser
481-
(r'/([0-9]+)/ws', self.WebSocket),
550+
(url_prefix + r'/([0-9]+)/ws', self.WebSocket),
551+
482552
# Handles the downloading (i.e., saving) of static images
483-
(r'/([0-9]+)/download.([a-z]+)', self.Download)
553+
(url_prefix + r'/([0-9]+)/download.([a-z]+)', self.Download),
554+
555+
# The page that contains all of the figures
556+
(url_prefix + r'/?', self.AllFiguresPage,
557+
{'url_prefix': url_prefix}),
484558
])
485559

486560
@classmethod
487-
def initialize(cls):
561+
def initialize(cls, url_prefix=''):
488562
if cls.initialized:
489563
return
490564

491-
app = cls()
565+
# Create the class instance
566+
app = cls(url_prefix=url_prefix)
567+
568+
cls.url_prefix = url_prefix
492569

493570
# This port selection algorithm is borrowed, more or less
494571
# verbatim, from IPython.

0 commit comments

Comments
 (0)