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 a31fac2

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

File tree

Expand file treeCollapse file tree

8 files changed

+185
-47
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+185
-47
lines changed

‎examples/user_interfaces/embedding_webagg_sgskip.py

Copy file name to clipboardExpand all lines: examples/user_interfaces/embedding_webagg_sgskip.py
+9-2Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_webagg.py
+7-3Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_webagg_core.py
+33-14Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/web_backend/all_figures.html
+1Lines 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>
+63Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/web_backend/js/mpl.js
+50-21Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.