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

Skip to content

Commit a31fac2

Browse files
authored
Merge pull request #17078 from QuLogic/webagg-toolbar
ENH: Improve nbAgg & WebAgg toolbars
2 parents 158d579 + a635ff3 commit a31fac2

File tree

8 files changed

+185
-47
lines changed

8 files changed

+185
-47
lines changed

examples/user_interfaces/embedding_webagg_sgskip.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"""
1313

1414
import io
15+
import json
1516
import mimetypes
17+
from pathlib import Path
1618

1719
try:
1820
import tornado
@@ -24,14 +26,13 @@
2426
import tornado.websocket
2527

2628

29+
import matplotlib as mpl
2730
from matplotlib.backends.backend_webagg_core import (
2831
FigureManagerWebAgg, new_figure_manager_given_figure)
2932
from matplotlib.figure import Figure
3033

3134
import numpy as np
3235

33-
import json
34-
3536

3637
def create_figure():
3738
"""
@@ -58,6 +59,7 @@ def create_figure():
5859
<link rel="stylesheet" href="_static/css/boilerplate.css"
5960
type="text/css" />
6061
<link rel="stylesheet" href="_static/css/fbm.css" type="text/css" />
62+
<link rel="stylesheet" href="_static/css/mpl.css" type="text/css">
6163
<link rel="stylesheet" href="_static/jquery-ui-1.12.1/jquery-ui.min.css" />
6264
<script src="_static/jquery-ui-1.12.1/external/jquery/jquery.js"></script>
6365
<script src="_static/jquery-ui-1.12.1/jquery-ui.min.js"></script>
@@ -219,6 +221,11 @@ def __init__(self, figure):
219221
tornado.web.StaticFileHandler,
220222
{'path': FigureManagerWebAgg.get_static_file_path()}),
221223

224+
# Static images for the toolbar
225+
(r'/_images/(.*)',
226+
tornado.web.StaticFileHandler,
227+
{'path': Path(mpl.get_data_path(), 'images')}),
228+
222229
# The page that contains all of the pieces
223230
('/', self.MainPage),
224231

lib/matplotlib/backends/backend_webagg.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import tornado.websocket
3434

3535
import matplotlib as mpl
36-
from matplotlib import cbook
3736
from matplotlib.backend_bases import _Backend
3837
from matplotlib._pylab_helpers import Gcf
3938
from . import backend_webagg_core as core
@@ -64,8 +63,8 @@ class WebAggApplication(tornado.web.Application):
6463
class FavIcon(tornado.web.RequestHandler):
6564
def get(self):
6665
self.set_header('Content-Type', 'image/png')
67-
self.write(
68-
cbook._get_data_path('images/matplotlib.png').read_bytes())
66+
self.write(Path(mpl.get_data_path(),
67+
'images/matplotlib.png').read_bytes())
6968

7069
class SingleFigurePage(tornado.web.RequestHandler):
7170
def __init__(self, application, request, *, url_prefix='', **kwargs):
@@ -170,6 +169,11 @@ def __init__(self, url_prefix=''):
170169
tornado.web.StaticFileHandler,
171170
{'path': core.FigureManagerWebAgg.get_static_file_path()}),
172171

172+
# Static images for the toolbar
173+
(url_prefix + r'/_images/(.*)',
174+
tornado.web.StaticFileHandler,
175+
{'path': Path(mpl.get_data_path(), 'images')}),
176+
173177
# A Matplotlib favicon
174178
(url_prefix + r'/favicon.ico', self.FavIcon),
175179

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ def handle_refresh(self, event):
306306
figure_label = "Figure {0}".format(self.manager.num)
307307
self.send_event('figure_label', label=figure_label)
308308
self._force_full = True
309+
if self.toolbar:
310+
# Normal toolbar init would refresh this, but it happens before the
311+
# browser canvas is set up.
312+
self.toolbar.set_history_buttons()
309313
self.draw_idle()
310314

311315
def handle_resize(self, event):
@@ -341,26 +345,27 @@ def send_event(self, event_type, **kwargs):
341345
self.manager._send_event(event_type, **kwargs)
342346

343347

344-
_JQUERY_ICON_CLASSES = {
345-
'home': 'ui-icon ui-icon-home',
346-
'back': 'ui-icon ui-icon-circle-arrow-w',
347-
'forward': 'ui-icon ui-icon-circle-arrow-e',
348-
'zoom_to_rect': 'ui-icon ui-icon-search',
349-
'move': 'ui-icon ui-icon-arrow-4',
350-
'download': 'ui-icon ui-icon-disk',
351-
None: None,
348+
_ALLOWED_TOOL_ITEMS = {
349+
'home',
350+
'back',
351+
'forward',
352+
'pan',
353+
'zoom',
354+
'download',
355+
None,
352356
}
353357

354358

355359
class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2):
356360

357361
# Use the standard toolbar items + download button
358-
toolitems = [(text, tooltip_text, _JQUERY_ICON_CLASSES[image_file],
359-
name_of_method)
360-
for text, tooltip_text, image_file, name_of_method
361-
in (backend_bases.NavigationToolbar2.toolitems +
362-
(('Download', 'Download plot', 'download', 'download'),))
363-
if image_file in _JQUERY_ICON_CLASSES]
362+
toolitems = [
363+
(text, tooltip_text, image_file, name_of_method)
364+
for text, tooltip_text, image_file, name_of_method
365+
in (*backend_bases.NavigationToolbar2.toolitems,
366+
('Download', 'Download plot', 'filesave', 'download'))
367+
if name_of_method in _ALLOWED_TOOL_ITEMS
368+
]
364369

365370
def _init_toolbar(self):
366371
self.message = ''
@@ -389,6 +394,20 @@ def save_figure(self, *args):
389394
"""Save the current figure"""
390395
self.canvas.send_event('save')
391396

397+
def pan(self):
398+
super().pan()
399+
self.canvas.send_event('navigate_mode', mode=self.mode.name)
400+
401+
def zoom(self):
402+
super().zoom()
403+
self.canvas.send_event('navigate_mode', mode=self.mode.name)
404+
405+
def set_history_buttons(self):
406+
can_backward = self._nav_stack._pos > 0
407+
can_forward = self._nav_stack._pos < len(self._nav_stack._elements) - 1
408+
self.canvas.send_event('history_buttons',
409+
Back=can_backward, Forward=can_forward)
410+
392411

393412
class FigureManagerWebAgg(backend_bases.FigureManagerBase):
394413
ToolbarCls = NavigationToolbar2WebAgg

lib/matplotlib/backends/web_backend/all_figures.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<link rel="stylesheet" href="{{ prefix }}/_static/css/page.css" type="text/css">
44
<link rel="stylesheet" href="{{ prefix }}/_static/css/boilerplate.css" type="text/css" />
55
<link rel="stylesheet" href="{{ prefix }}/_static/css/fbm.css" type="text/css" />
6+
<link rel="stylesheet" href="{{ prefix }}/_static/css/mpl.css" type="text/css">
67
<link rel="stylesheet" href="{{ prefix }}/_static/jquery-ui-1.12.1/jquery-ui.min.css" >
78
<script src="{{ prefix }}/_static/jquery-ui-1.12.1/external/jquery/jquery.js"></script>
89
<script src="{{ prefix }}/_static/jquery-ui-1.12.1/jquery-ui.min.js"></script>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* Toolbar and items */
2+
.mpl-toolbar {
3+
width: 100%;
4+
}
5+
6+
.mpl-toolbar div.mpl-button-group {
7+
display: inline-block;
8+
}
9+
10+
.mpl-button-group + .mpl-button-group {
11+
margin-left: 0.5em;
12+
}
13+
14+
.mpl-widget {
15+
background-color: #fff;
16+
border: 1px solid #ccc;
17+
display: inline-block;
18+
cursor: pointer;
19+
color: #333;
20+
padding: 6px;
21+
vertical-align: middle;
22+
}
23+
24+
.mpl-widget:disabled,
25+
.mpl-widget[disabled] {
26+
background-color: #ddd;
27+
border-color: #ddd !important;
28+
cursor: not-allowed;
29+
}
30+
31+
.mpl-widget:disabled img,
32+
.mpl-widget[disabled] img {
33+
/* Convert black to grey */
34+
filter: contrast(0%);
35+
}
36+
37+
.mpl-widget.active img {
38+
/* Convert black to tab:blue, approximately */
39+
filter: invert(34%) sepia(97%) saturate(468%) hue-rotate(162deg) brightness(96%) contrast(91%);
40+
}
41+
42+
button.mpl-widget:focus,
43+
button.mpl-widget:hover {
44+
background-color: #ddd;
45+
border-color: #aaa;
46+
}
47+
48+
.mpl-button-group button.mpl-widget {
49+
margin-left: -1px;
50+
}
51+
.mpl-button-group button.mpl-widget:first-child {
52+
border-top-left-radius: 6px;
53+
border-bottom-left-radius: 6px;
54+
margin-left: 0px;
55+
}
56+
.mpl-button-group button.mpl-widget:last-child {
57+
border-top-right-radius: 6px;
58+
border-bottom-right-radius: 6px;
59+
}
60+
61+
select.mpl-widget {
62+
cursor: default;
63+
}

lib/matplotlib/backends/web_backend/js/mpl.js

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ mpl.figure.prototype._init_toolbar = function () {
257257
var fig = this;
258258

259259
var toolbar = document.createElement('div');
260-
toolbar.setAttribute('style', 'width: 100%');
260+
toolbar.classList = 'mpl-toolbar';
261261
this.root.appendChild(toolbar);
262262

263263
function on_click_closure(name) {
@@ -267,49 +267,55 @@ mpl.figure.prototype._init_toolbar = function () {
267267
}
268268

269269
function on_mouseover_closure(tooltip) {
270-
return function (_event) {
271-
return fig.toolbar_button_onmouseover(tooltip);
270+
return function (event) {
271+
if (!event.currentTarget.disabled) {
272+
return fig.toolbar_button_onmouseover(tooltip);
273+
}
272274
};
273275
}
274276

277+
fig.buttons = {};
278+
var buttonGroup = document.createElement('div');
279+
buttonGroup.classList = 'mpl-button-group';
275280
for (var toolbar_ind in mpl.toolbar_items) {
276281
var name = mpl.toolbar_items[toolbar_ind][0];
277282
var tooltip = mpl.toolbar_items[toolbar_ind][1];
278283
var image = mpl.toolbar_items[toolbar_ind][2];
279284
var method_name = mpl.toolbar_items[toolbar_ind][3];
280285

281286
if (!name) {
282-
// put a spacer in here.
287+
/* Instead of a spacer, we start a new button group. */
288+
if (buttonGroup.hasChildNodes()) {
289+
toolbar.appendChild(buttonGroup);
290+
}
291+
buttonGroup = document.createElement('div');
292+
buttonGroup.classList = 'mpl-button-group';
283293
continue;
284294
}
285-
var button = document.createElement('button');
286-
button.classList =
287-
'ui-button ui-widget ui-state-default ui-corner-all ui-button-icon-only';
295+
296+
var button = (fig.buttons[name] = document.createElement('button'));
297+
button.classList = 'mpl-widget';
288298
button.setAttribute('role', 'button');
289299
button.setAttribute('aria-disabled', 'false');
290300
button.addEventListener('click', on_click_closure(method_name));
291301
button.addEventListener('mouseover', on_mouseover_closure(tooltip));
292302

293-
var icon_img = document.createElement('span');
294-
icon_img.classList =
295-
'ui-button-icon-primary ui-icon ' + image + ' ui-corner-all';
296-
297-
var tooltip_span = document.createElement('span');
298-
tooltip_span.classList = 'ui-button-text';
299-
tooltip_span.innerHTML = tooltip;
300-
303+
var icon_img = document.createElement('img');
304+
icon_img.src = '_images/' + image + '.png';
305+
icon_img.srcset = '_images/' + image + '_large.png 2x';
306+
icon_img.alt = tooltip;
301307
button.appendChild(icon_img);
302-
button.appendChild(tooltip_span);
303308

304-
toolbar.appendChild(button);
309+
buttonGroup.appendChild(button);
305310
}
306311

307-
var fmt_picker_span = document.createElement('span');
312+
if (buttonGroup.hasChildNodes()) {
313+
toolbar.appendChild(buttonGroup);
314+
}
308315

309316
var fmt_picker = document.createElement('select');
310-
fmt_picker.classList = 'mpl-toolbar-option ui-widget ui-widget-content';
311-
fmt_picker_span.appendChild(fmt_picker);
312-
toolbar.appendChild(fmt_picker_span);
317+
fmt_picker.classList = 'mpl-widget';
318+
toolbar.appendChild(fmt_picker);
313319
this.format_dropdown = fmt_picker;
314320

315321
for (var ind in mpl.extensions) {
@@ -420,6 +426,29 @@ mpl.figure.prototype.handle_image_mode = function (fig, msg) {
420426
fig.image_mode = msg['mode'];
421427
};
422428

429+
mpl.figure.prototype.handle_history_buttons = function (fig, msg) {
430+
for (var key in msg) {
431+
if (!(key in fig.buttons)) {
432+
continue;
433+
}
434+
fig.buttons[key].disabled = !msg[key];
435+
fig.buttons[key].setAttribute('aria-disabled', !msg[key]);
436+
}
437+
};
438+
439+
mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {
440+
if (msg['mode'] === 'PAN') {
441+
fig.buttons['Pan'].classList.add('active');
442+
fig.buttons['Zoom'].classList.remove('active');
443+
} else if (msg['mode'] === 'ZOOM') {
444+
fig.buttons['Pan'].classList.remove('active');
445+
fig.buttons['Zoom'].classList.add('active');
446+
} else {
447+
fig.buttons['Pan'].classList.remove('active');
448+
fig.buttons['Zoom'].classList.remove('active');
449+
}
450+
};
451+
423452
mpl.figure.prototype.updated_canvas_event = function () {
424453
// Called whenever the canvas gets updated.
425454
this.send_message('ack', {});

0 commit comments

Comments
 (0)