Skip to content

Improve nbAgg & WebAgg toolbars #17078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions examples/user_interfaces/embedding_webagg_sgskip.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"""

import io
import json
import mimetypes
from pathlib import Path

try:
import tornado
Expand All @@ -24,14 +26,13 @@
import tornado.websocket


import matplotlib as mpl
from matplotlib.backends.backend_webagg_core import (
FigureManagerWebAgg, new_figure_manager_given_figure)
from matplotlib.figure import Figure

import numpy as np

import json


def create_figure():
"""
Expand All @@ -58,6 +59,7 @@ def create_figure():
<link rel="stylesheet" href="_static/css/boilerplate.css"
type="text/css" />
<link rel="stylesheet" href="_static/css/fbm.css" type="text/css" />
<link rel="stylesheet" href="_static/css/mpl.css" type="text/css">
<link rel="stylesheet" href="_static/jquery-ui-1.12.1/jquery-ui.min.css" />
<script src="_static/jquery-ui-1.12.1/external/jquery/jquery.js"></script>
<script src="_static/jquery-ui-1.12.1/jquery-ui.min.js"></script>
Expand Down Expand Up @@ -219,6 +221,11 @@ def __init__(self, figure):
tornado.web.StaticFileHandler,
{'path': FigureManagerWebAgg.get_static_file_path()}),

# Static images for the toolbar
(r'/_images/(.*)',
tornado.web.StaticFileHandler,
{'path': Path(mpl.get_data_path(), 'images')}),

# The page that contains all of the pieces
('/', self.MainPage),

Expand Down
10 changes: 7 additions & 3 deletions lib/matplotlib/backends/backend_webagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import tornado.websocket

import matplotlib as mpl
from matplotlib import cbook
from matplotlib.backend_bases import _Backend
from matplotlib._pylab_helpers import Gcf
from . import backend_webagg_core as core
Expand Down Expand Up @@ -64,8 +63,8 @@ class WebAggApplication(tornado.web.Application):
class FavIcon(tornado.web.RequestHandler):
def get(self):
self.set_header('Content-Type', 'image/png')
self.write(
cbook._get_data_path('images/matplotlib.png').read_bytes())
self.write(Path(mpl.get_data_path(),
'images/matplotlib.png').read_bytes())

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

# Static images for the toolbar
(url_prefix + r'/_images/(.*)',
tornado.web.StaticFileHandler,
{'path': Path(mpl.get_data_path(), 'images')}),

# A Matplotlib favicon
(url_prefix + r'/favicon.ico', self.FavIcon),

Expand Down
47 changes: 33 additions & 14 deletions lib/matplotlib/backends/backend_webagg_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ def handle_refresh(self, event):
figure_label = "Figure {0}".format(self.manager.num)
self.send_event('figure_label', label=figure_label)
self._force_full = True
if self.toolbar:
# Normal toolbar init would refresh this, but it happens before the
# browser canvas is set up.
self.toolbar.set_history_buttons()
self.draw_idle()

def handle_resize(self, event):
Expand Down Expand Up @@ -345,26 +349,27 @@ def send_event(self, event_type, **kwargs):
self.manager._send_event(event_type, **kwargs)


_JQUERY_ICON_CLASSES = {
'home': 'ui-icon ui-icon-home',
'back': 'ui-icon ui-icon-circle-arrow-w',
'forward': 'ui-icon ui-icon-circle-arrow-e',
'zoom_to_rect': 'ui-icon ui-icon-search',
'move': 'ui-icon ui-icon-arrow-4',
'download': 'ui-icon ui-icon-disk',
None: None,
_ALLOWED_TOOL_ITEMS = {
'home',
'back',
'forward',
'pan',
'zoom',
'download',
None,
}


class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2):

# Use the standard toolbar items + download button
toolitems = [(text, tooltip_text, _JQUERY_ICON_CLASSES[image_file],
name_of_method)
for text, tooltip_text, image_file, name_of_method
in (backend_bases.NavigationToolbar2.toolitems +
(('Download', 'Download plot', 'download', 'download'),))
if image_file in _JQUERY_ICON_CLASSES]
toolitems = [
(text, tooltip_text, image_file, name_of_method)
for text, tooltip_text, image_file, name_of_method
in (*backend_bases.NavigationToolbar2.toolitems,
('Download', 'Download plot', 'filesave', 'download'))
if name_of_method in _ALLOWED_TOOL_ITEMS
]

def _init_toolbar(self):
self.message = ''
Expand Down Expand Up @@ -393,6 +398,20 @@ def save_figure(self, *args):
"""Save the current figure"""
self.canvas.send_event('save')

def pan(self):
super().pan()
self.canvas.send_event('navigate_mode', mode=self.mode.name)

def zoom(self):
super().zoom()
self.canvas.send_event('navigate_mode', mode=self.mode.name)

def set_history_buttons(self):
can_backward = self._nav_stack._pos > 0
can_forward = self._nav_stack._pos < len(self._nav_stack._elements) - 1
self.canvas.send_event('history_buttons',
Back=can_backward, Forward=can_forward)


class FigureManagerWebAgg(backend_bases.FigureManagerBase):
ToolbarCls = NavigationToolbar2WebAgg
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/backends/web_backend/all_figures.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<link rel="stylesheet" href="{{ prefix }}/_static/css/page.css" type="text/css">
<link rel="stylesheet" href="{{ prefix }}/_static/css/boilerplate.css" type="text/css" />
<link rel="stylesheet" href="{{ prefix }}/_static/css/fbm.css" type="text/css" />
<link rel="stylesheet" href="{{ prefix }}/_static/css/mpl.css" type="text/css">
<link rel="stylesheet" href="{{ prefix }}/_static/jquery-ui-1.12.1/jquery-ui.min.css" >
<script src="{{ prefix }}/_static/jquery-ui-1.12.1/external/jquery/jquery.js"></script>
<script src="{{ prefix }}/_static/jquery-ui-1.12.1/jquery-ui.min.js"></script>
Expand Down
63 changes: 63 additions & 0 deletions lib/matplotlib/backends/web_backend/css/mpl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Toolbar and items */
.mpl-toolbar {
width: 100%;
}

.mpl-toolbar div.mpl-button-group {
display: inline-block;
}

.mpl-button-group + .mpl-button-group {
margin-left: 0.5em;
}

.mpl-widget {
background-color: #fff;
border: 1px solid #ccc;
display: inline-block;
cursor: pointer;
color: #333;
padding: 6px;
vertical-align: middle;
}

.mpl-widget:disabled,
.mpl-widget[disabled] {
background-color: #ddd;
border-color: #ddd !important;
cursor: not-allowed;
}

.mpl-widget:disabled img,
.mpl-widget[disabled] img {
/* Convert black to grey */
filter: contrast(0%);
}

.mpl-widget.active img {
/* Convert black to tab:blue, approximately */
filter: invert(34%) sepia(97%) saturate(468%) hue-rotate(162deg) brightness(96%) contrast(91%);
}

button.mpl-widget:focus,
button.mpl-widget:hover {
background-color: #ddd;
border-color: #aaa;
}

.mpl-button-group button.mpl-widget {
margin-left: -1px;
}
.mpl-button-group button.mpl-widget:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
margin-left: 0px;
}
.mpl-button-group button.mpl-widget:last-child {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}

select.mpl-widget {
cursor: default;
}
71 changes: 50 additions & 21 deletions lib/matplotlib/backends/web_backend/js/mpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ mpl.figure.prototype._init_toolbar = function () {
var fig = this;

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

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

function on_mouseover_closure(tooltip) {
return function (_event) {
return fig.toolbar_button_onmouseover(tooltip);
return function (event) {
if (!event.currentTarget.disabled) {
return fig.toolbar_button_onmouseover(tooltip);
}
};
}

fig.buttons = {};
var buttonGroup = document.createElement('div');
buttonGroup.classList = 'mpl-button-group';
for (var toolbar_ind in mpl.toolbar_items) {
var name = mpl.toolbar_items[toolbar_ind][0];
var tooltip = mpl.toolbar_items[toolbar_ind][1];
var image = mpl.toolbar_items[toolbar_ind][2];
var method_name = mpl.toolbar_items[toolbar_ind][3];

if (!name) {
// put a spacer in here.
/* Instead of a spacer, we start a new button group. */
if (buttonGroup.hasChildNodes()) {
toolbar.appendChild(buttonGroup);
}
buttonGroup = document.createElement('div');
buttonGroup.classList = 'mpl-button-group';
continue;
}
var button = document.createElement('button');
button.classList =
'ui-button ui-widget ui-state-default ui-corner-all ui-button-icon-only';

var button = (fig.buttons[name] = document.createElement('button'));
button.classList = 'mpl-widget';
button.setAttribute('role', 'button');
button.setAttribute('aria-disabled', 'false');
button.addEventListener('click', on_click_closure(method_name));
button.addEventListener('mouseover', on_mouseover_closure(tooltip));

var icon_img = document.createElement('span');
icon_img.classList =
'ui-button-icon-primary ui-icon ' + image + ' ui-corner-all';

var tooltip_span = document.createElement('span');
tooltip_span.classList = 'ui-button-text';
tooltip_span.innerHTML = tooltip;

var icon_img = document.createElement('img');
icon_img.src = '_images/' + image + '.png';
icon_img.srcset = '_images/' + image + '_large.png 2x';
icon_img.alt = tooltip;
button.appendChild(icon_img);
button.appendChild(tooltip_span);

toolbar.appendChild(button);
buttonGroup.appendChild(button);
}

var fmt_picker_span = document.createElement('span');
if (buttonGroup.hasChildNodes()) {
toolbar.appendChild(buttonGroup);
}

var fmt_picker = document.createElement('select');
fmt_picker.classList = 'mpl-toolbar-option ui-widget ui-widget-content';
fmt_picker_span.appendChild(fmt_picker);
toolbar.appendChild(fmt_picker_span);
fmt_picker.classList = 'mpl-widget';
toolbar.appendChild(fmt_picker);
this.format_dropdown = fmt_picker;

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

mpl.figure.prototype.handle_history_buttons = function (fig, msg) {
for (var key in msg) {
if (!(key in fig.buttons)) {
continue;
}
fig.buttons[key].disabled = !msg[key];
fig.buttons[key].setAttribute('aria-disabled', !msg[key]);
}
};

mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {
if (msg['mode'] === 'PAN') {
fig.buttons['Pan'].classList.add('active');
fig.buttons['Zoom'].classList.remove('active');
} else if (msg['mode'] === 'ZOOM') {
fig.buttons['Pan'].classList.remove('active');
fig.buttons['Zoom'].classList.add('active');
} else {
fig.buttons['Pan'].classList.remove('active');
fig.buttons['Zoom'].classList.remove('active');
}
};

mpl.figure.prototype.updated_canvas_event = function () {
// Called whenever the canvas gets updated.
this.send_message('ack', {});
Expand Down
Loading