some new features
This commit is contained in:
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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">
|
||||
<script src="{{ prefix }}/_static/js/mpl_tornado.js"></script>
|
||||
<script src="{{ prefix }}/js/mpl.js"></script>
|
||||
|
||||
<script>
|
||||
function ready(fn) {
|
||||
if (document.readyState != "loading") {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", fn);
|
||||
}
|
||||
}
|
||||
|
||||
function figure_ready(fig_id) {
|
||||
return function () {
|
||||
var main_div = document.querySelector("div#figures");
|
||||
var figure_div = document.createElement("div");
|
||||
figure_div.id = "figure-div";
|
||||
main_div.appendChild(figure_div);
|
||||
var websocket_type = mpl.get_websocket_type();
|
||||
var uri = "{{ ws_uri }}" + fig_id + "/ws";
|
||||
if (window.location.protocol === "https:") uri = uri.replace('ws:', 'wss:')
|
||||
var websocket = new websocket_type(uri);
|
||||
var fig = new mpl.figure(fig_id, websocket, mpl_ondownload, figure_div);
|
||||
|
||||
fig.focus_on_mouseover = true;
|
||||
|
||||
fig.canvas.setAttribute("tabindex", fig_id);
|
||||
}
|
||||
};
|
||||
|
||||
{% for (fig_id, fig_manager) in figures %}
|
||||
ready(figure_ready({{ str(fig_id) }}));
|
||||
{% end %}
|
||||
</script>
|
||||
|
||||
<title>MPL | WebAgg current figures</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="mpl-warnings" class="mpl-warnings"></div>
|
||||
|
||||
<div id="figures" style="margin: 10px 10px;"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* HTML5 ✰ Boilerplate
|
||||
*
|
||||
* style.css contains a reset, font normalization and some base styles.
|
||||
*
|
||||
* Credit is left where credit is due.
|
||||
* Much inspiration was taken from these projects:
|
||||
* - yui.yahooapis.com/2.8.1/build/base/base.css
|
||||
* - camendesign.com/design/
|
||||
* - praegnanz.de/weblog/htmlcssjs-kickstart
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
|
||||
* v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
|
||||
* html5doctor.com/html-5-reset-stylesheet/
|
||||
*/
|
||||
|
||||
html, body, div, span, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
|
||||
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup { vertical-align: super; }
|
||||
sub { vertical-align: sub; }
|
||||
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
blockquote, q { quotes: none; }
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after { content: ""; content: none; }
|
||||
|
||||
ins { background-color: #ff9; color: #000; text-decoration: none; }
|
||||
|
||||
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
|
||||
|
||||
del { text-decoration: line-through; }
|
||||
|
||||
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
|
||||
|
||||
table { border-collapse: collapse; border-spacing: 0; }
|
||||
|
||||
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
|
||||
|
||||
input, select { vertical-align: middle; }
|
||||
|
||||
|
||||
/**
|
||||
* Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
|
||||
*/
|
||||
|
||||
body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
|
||||
select, input, textarea, button { font:99% sans-serif; }
|
||||
|
||||
/* Normalize monospace sizing:
|
||||
en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
|
||||
pre, code, kbd, samp { font-family: monospace, sans-serif; }
|
||||
|
||||
em,i { font-style: italic; }
|
||||
b,strong { font-weight: bold; }
|
||||
@ -0,0 +1,97 @@
|
||||
|
||||
/* Flexible box model classes */
|
||||
/* Taken from Alex Russell https://infrequently.org/2009/08/css-3-progress/ */
|
||||
|
||||
.hbox {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-align: stretch;
|
||||
|
||||
display: -moz-box;
|
||||
-moz-box-orient: horizontal;
|
||||
-moz-box-align: stretch;
|
||||
|
||||
display: box;
|
||||
box-orient: horizontal;
|
||||
box-align: stretch;
|
||||
}
|
||||
|
||||
.hbox > * {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
}
|
||||
|
||||
.vbox {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-align: stretch;
|
||||
|
||||
display: -moz-box;
|
||||
-moz-box-orient: vertical;
|
||||
-moz-box-align: stretch;
|
||||
|
||||
display: box;
|
||||
box-orient: vertical;
|
||||
box-align: stretch;
|
||||
}
|
||||
|
||||
.vbox > * {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
}
|
||||
|
||||
.reverse {
|
||||
-webkit-box-direction: reverse;
|
||||
-moz-box-direction: reverse;
|
||||
box-direction: reverse;
|
||||
}
|
||||
|
||||
.box-flex0 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
}
|
||||
|
||||
.box-flex1, .box-flex {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
box-flex: 1;
|
||||
}
|
||||
|
||||
.box-flex2 {
|
||||
-webkit-box-flex: 2;
|
||||
-moz-box-flex: 2;
|
||||
box-flex: 2;
|
||||
}
|
||||
|
||||
.box-group1 {
|
||||
-webkit-box-flex-group: 1;
|
||||
-moz-box-flex-group: 1;
|
||||
box-flex-group: 1;
|
||||
}
|
||||
|
||||
.box-group2 {
|
||||
-webkit-box-flex-group: 2;
|
||||
-moz-box-flex-group: 2;
|
||||
box-flex-group: 2;
|
||||
}
|
||||
|
||||
.start {
|
||||
-webkit-box-pack: start;
|
||||
-moz-box-pack: start;
|
||||
box-pack: start;
|
||||
}
|
||||
|
||||
.end {
|
||||
-webkit-box-pack: end;
|
||||
-moz-box-pack: end;
|
||||
box-pack: end;
|
||||
}
|
||||
|
||||
.center {
|
||||
-webkit-box-pack: center;
|
||||
-moz-box-pack: center;
|
||||
box-pack: center;
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
/* General styling */
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.ui-widget-header {
|
||||
border: 1px solid #dddddd;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
background: #e9e9e9;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Primary styles
|
||||
*
|
||||
* Author: IPython Development Team
|
||||
*/
|
||||
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
/* This makes sure that the body covers the entire window and needs to
|
||||
be in a different element than the display: box in wrapper below */
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
div#header {
|
||||
/* Initially hidden to prevent FLOUC */
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
span#ipython_notebook {
|
||||
position: absolute;
|
||||
padding: 2px 2px 2px 5px;
|
||||
}
|
||||
|
||||
span#ipython_notebook img {
|
||||
font-family: Verdana, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
|
||||
height: 24px;
|
||||
text-decoration:none;
|
||||
display: inline;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#site {
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* We set the fonts by hand here to override the values in the theme */
|
||||
.ui-widget {
|
||||
font-family: "Lucinda Grande", "Lucinda Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {
|
||||
font-family: "Lucinda Grande", "Lucinda Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* Smaller buttons */
|
||||
.ui-button .ui-button-text {
|
||||
padding: 0.2em 0.8em;
|
||||
font-size: 77%;
|
||||
}
|
||||
|
||||
input.ui-button {
|
||||
padding: 0.3em 0.9em;
|
||||
}
|
||||
|
||||
span#login_widget {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.border-box-sizing {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
#figure-div {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
<!-- Within the kernel, we don't know the address of the matplotlib
|
||||
websocket server, so we have to get in client-side and fetch our
|
||||
resources that way. -->
|
||||
<script>
|
||||
// We can't proceed until these JavaScript files are fetched, so
|
||||
// we fetch them synchronously
|
||||
$.ajaxSetup({async: false});
|
||||
$.getScript("http://" + window.location.hostname + ":{{ port }}{{prefix}}/_static/js/mpl_tornado.js");
|
||||
$.getScript("http://" + window.location.hostname + ":{{ port }}{{prefix}}/js/mpl.js");
|
||||
$.ajaxSetup({async: true});
|
||||
|
||||
function init_figure{{ fig_id }}(e) {
|
||||
$('div.output').off('resize');
|
||||
|
||||
var output_div = e.target.querySelector('div.output_subarea');
|
||||
var websocket_type = mpl.get_websocket_type();
|
||||
var websocket = new websocket_type(
|
||||
"ws://" + window.location.hostname + ":{{ port }}{{ prefix}}/" +
|
||||
{{ repr(str(fig_id)) }} + "/ws");
|
||||
|
||||
var fig = new mpl.figure(
|
||||
{{repr(str(fig_id))}}, websocket, mpl_ondownload, output_div);
|
||||
|
||||
// Fetch the first image
|
||||
fig.context.drawImage(fig.imageObj, 0, 0);
|
||||
|
||||
fig.focus_on_mouseover = true;
|
||||
}
|
||||
|
||||
// We can't initialize the figure contents until our content
|
||||
// has been added to the DOM. This is a bit of hack to get an
|
||||
// event for that.
|
||||
$('div.output').resize(init_figure{{ fig_id }});
|
||||
</script>
|
||||
@ -0,0 +1,705 @@
|
||||
/* Put everything inside the global mpl namespace */
|
||||
/* global mpl */
|
||||
window.mpl = {};
|
||||
|
||||
mpl.get_websocket_type = function () {
|
||||
if (typeof WebSocket !== 'undefined') {
|
||||
return WebSocket;
|
||||
} else if (typeof MozWebSocket !== 'undefined') {
|
||||
return MozWebSocket;
|
||||
} else {
|
||||
alert(
|
||||
'Your browser does not have WebSocket support. ' +
|
||||
'Please try Chrome, Safari or Firefox ≥ 6. ' +
|
||||
'Firefox 4 and 5 are also supported but you ' +
|
||||
'have to enable WebSockets in about:config.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
mpl.figure = function (figure_id, websocket, ondownload, parent_element) {
|
||||
this.id = figure_id;
|
||||
|
||||
this.ws = websocket;
|
||||
|
||||
this.supports_binary = this.ws.binaryType !== undefined;
|
||||
|
||||
if (!this.supports_binary) {
|
||||
var warnings = document.getElementById('mpl-warnings');
|
||||
if (warnings) {
|
||||
warnings.style.display = 'block';
|
||||
warnings.textContent =
|
||||
'This browser does not support binary websocket messages. ' +
|
||||
'Performance may be slow.';
|
||||
}
|
||||
}
|
||||
|
||||
this.imageObj = new Image();
|
||||
|
||||
this.context = undefined;
|
||||
this.message = undefined;
|
||||
this.canvas = undefined;
|
||||
this.rubberband_canvas = undefined;
|
||||
this.rubberband_context = undefined;
|
||||
this.format_dropdown = undefined;
|
||||
|
||||
this.image_mode = 'full';
|
||||
|
||||
this.root = document.createElement('div');
|
||||
this.root.setAttribute('style', 'display: inline-block');
|
||||
this._root_extra_style(this.root);
|
||||
|
||||
parent_element.appendChild(this.root);
|
||||
|
||||
this._init_header(this);
|
||||
this._init_canvas(this);
|
||||
this._init_toolbar(this);
|
||||
|
||||
var fig = this;
|
||||
|
||||
this.waiting = false;
|
||||
|
||||
this.ws.onopen = function () {
|
||||
fig.send_message('supports_binary', { value: fig.supports_binary });
|
||||
fig.send_message('send_image_mode', {});
|
||||
if (fig.ratio !== 1) {
|
||||
fig.send_message('set_device_pixel_ratio', {
|
||||
device_pixel_ratio: fig.ratio,
|
||||
});
|
||||
}
|
||||
fig.send_message('refresh', {});
|
||||
};
|
||||
|
||||
this.imageObj.onload = function () {
|
||||
if (fig.image_mode === 'full') {
|
||||
// Full images could contain transparency (where diff images
|
||||
// almost always do), so we need to clear the canvas so that
|
||||
// there is no ghosting.
|
||||
fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);
|
||||
}
|
||||
fig.context.drawImage(fig.imageObj, 0, 0);
|
||||
};
|
||||
|
||||
this.imageObj.onunload = function () {
|
||||
fig.ws.close();
|
||||
};
|
||||
|
||||
this.ws.onmessage = this._make_on_message_function(this);
|
||||
|
||||
this.ondownload = ondownload;
|
||||
};
|
||||
|
||||
mpl.figure.prototype._init_header = function () {
|
||||
var titlebar = document.createElement('div');
|
||||
titlebar.classList =
|
||||
'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';
|
||||
var titletext = document.createElement('div');
|
||||
titletext.classList = 'ui-dialog-title';
|
||||
titletext.setAttribute(
|
||||
'style',
|
||||
'width: 100%; text-align: center; padding: 3px;'
|
||||
);
|
||||
titlebar.appendChild(titletext);
|
||||
this.root.appendChild(titlebar);
|
||||
this.header = titletext;
|
||||
};
|
||||
|
||||
mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};
|
||||
|
||||
mpl.figure.prototype._root_extra_style = function (_canvas_div) {};
|
||||
|
||||
mpl.figure.prototype._init_canvas = function () {
|
||||
var fig = this;
|
||||
|
||||
var canvas_div = (this.canvas_div = document.createElement('div'));
|
||||
canvas_div.setAttribute('tabindex', '0');
|
||||
canvas_div.setAttribute(
|
||||
'style',
|
||||
'border: 1px solid #ddd;' +
|
||||
'box-sizing: content-box;' +
|
||||
'clear: both;' +
|
||||
'min-height: 1px;' +
|
||||
'min-width: 1px;' +
|
||||
'outline: 0;' +
|
||||
'overflow: hidden;' +
|
||||
'position: relative;' +
|
||||
'resize: both;' +
|
||||
'z-index: 2;'
|
||||
);
|
||||
|
||||
function on_keyboard_event_closure(name) {
|
||||
return function (event) {
|
||||
return fig.key_event(event, name);
|
||||
};
|
||||
}
|
||||
|
||||
canvas_div.addEventListener(
|
||||
'keydown',
|
||||
on_keyboard_event_closure('key_press')
|
||||
);
|
||||
canvas_div.addEventListener(
|
||||
'keyup',
|
||||
on_keyboard_event_closure('key_release')
|
||||
);
|
||||
|
||||
this._canvas_extra_style(canvas_div);
|
||||
this.root.appendChild(canvas_div);
|
||||
|
||||
var canvas = (this.canvas = document.createElement('canvas'));
|
||||
canvas.classList.add('mpl-canvas');
|
||||
canvas.setAttribute(
|
||||
'style',
|
||||
'box-sizing: content-box;' +
|
||||
'pointer-events: none;' +
|
||||
'position: relative;' +
|
||||
'z-index: 0;'
|
||||
);
|
||||
|
||||
this.context = canvas.getContext('2d');
|
||||
|
||||
var backingStore =
|
||||
this.context.backingStorePixelRatio ||
|
||||
this.context.webkitBackingStorePixelRatio ||
|
||||
this.context.mozBackingStorePixelRatio ||
|
||||
this.context.msBackingStorePixelRatio ||
|
||||
this.context.oBackingStorePixelRatio ||
|
||||
this.context.backingStorePixelRatio ||
|
||||
1;
|
||||
|
||||
this.ratio = (window.devicePixelRatio || 1) / backingStore;
|
||||
|
||||
var rubberband_canvas = (this.rubberband_canvas = document.createElement(
|
||||
'canvas'
|
||||
));
|
||||
rubberband_canvas.setAttribute(
|
||||
'style',
|
||||
'box-sizing: content-box;' +
|
||||
'left: 0;' +
|
||||
'pointer-events: none;' +
|
||||
'position: absolute;' +
|
||||
'top: 0;' +
|
||||
'z-index: 1;'
|
||||
);
|
||||
|
||||
// Apply a ponyfill if ResizeObserver is not implemented by browser.
|
||||
if (this.ResizeObserver === undefined) {
|
||||
if (window.ResizeObserver !== undefined) {
|
||||
this.ResizeObserver = window.ResizeObserver;
|
||||
} else {
|
||||
var obs = _JSXTOOLS_RESIZE_OBSERVER({});
|
||||
this.ResizeObserver = obs.ResizeObserver;
|
||||
}
|
||||
}
|
||||
|
||||
this.resizeObserverInstance = new this.ResizeObserver(function (entries) {
|
||||
// There's no need to resize if the WebSocket is not connected:
|
||||
// - If it is still connecting, then we will get an initial resize from
|
||||
// Python once it connects.
|
||||
// - If it has disconnected, then resizing will clear the canvas and
|
||||
// never get anything back to refill it, so better to not resize and
|
||||
// keep something visible.
|
||||
if (fig.ws.readyState != 1) {
|
||||
return;
|
||||
}
|
||||
var nentries = entries.length;
|
||||
for (var i = 0; i < nentries; i++) {
|
||||
var entry = entries[i];
|
||||
var width, height;
|
||||
if (entry.contentBoxSize) {
|
||||
if (entry.contentBoxSize instanceof Array) {
|
||||
// Chrome 84 implements new version of spec.
|
||||
width = entry.contentBoxSize[0].inlineSize;
|
||||
height = entry.contentBoxSize[0].blockSize;
|
||||
} else {
|
||||
// Firefox implements old version of spec.
|
||||
width = entry.contentBoxSize.inlineSize;
|
||||
height = entry.contentBoxSize.blockSize;
|
||||
}
|
||||
} else {
|
||||
// Chrome <84 implements even older version of spec.
|
||||
width = entry.contentRect.width;
|
||||
height = entry.contentRect.height;
|
||||
}
|
||||
|
||||
// Keep the size of the canvas and rubber band canvas in sync with
|
||||
// the canvas container.
|
||||
if (entry.devicePixelContentBoxSize) {
|
||||
// Chrome 84 implements new version of spec.
|
||||
canvas.setAttribute(
|
||||
'width',
|
||||
entry.devicePixelContentBoxSize[0].inlineSize
|
||||
);
|
||||
canvas.setAttribute(
|
||||
'height',
|
||||
entry.devicePixelContentBoxSize[0].blockSize
|
||||
);
|
||||
} else {
|
||||
canvas.setAttribute('width', width * fig.ratio);
|
||||
canvas.setAttribute('height', height * fig.ratio);
|
||||
}
|
||||
/* This rescales the canvas back to display pixels, so that it
|
||||
* appears correct on HiDPI screens. */
|
||||
canvas.style.width = width + 'px';
|
||||
canvas.style.height = height + 'px';
|
||||
|
||||
rubberband_canvas.setAttribute('width', width);
|
||||
rubberband_canvas.setAttribute('height', height);
|
||||
|
||||
// And update the size in Python. We ignore the initial 0/0 size
|
||||
// that occurs as the element is placed into the DOM, which should
|
||||
// otherwise not happen due to the minimum size styling.
|
||||
if (width != 0 && height != 0) {
|
||||
fig.request_resize(width, height);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.resizeObserverInstance.observe(canvas_div);
|
||||
|
||||
function on_mouse_event_closure(name) {
|
||||
/* User Agent sniffing is bad, but WebKit is busted:
|
||||
* https://bugs.webkit.org/show_bug.cgi?id=144526
|
||||
* https://bugs.webkit.org/show_bug.cgi?id=181818
|
||||
* The worst that happens here is that they get an extra browser
|
||||
* selection when dragging, if this check fails to catch them.
|
||||
*/
|
||||
var UA = navigator.userAgent;
|
||||
var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);
|
||||
if(isWebKit) {
|
||||
return function (event) {
|
||||
/* This prevents the web browser from automatically changing to
|
||||
* the text insertion cursor when the button is pressed. We
|
||||
* want to control all of the cursor setting manually through
|
||||
* the 'cursor' event from matplotlib */
|
||||
event.preventDefault()
|
||||
return fig.mouse_event(event, name);
|
||||
};
|
||||
} else {
|
||||
return function (event) {
|
||||
return fig.mouse_event(event, name);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
canvas_div.addEventListener(
|
||||
'mousedown',
|
||||
on_mouse_event_closure('button_press')
|
||||
);
|
||||
canvas_div.addEventListener(
|
||||
'mouseup',
|
||||
on_mouse_event_closure('button_release')
|
||||
);
|
||||
canvas_div.addEventListener(
|
||||
'dblclick',
|
||||
on_mouse_event_closure('dblclick')
|
||||
);
|
||||
// Throttle sequential mouse events to 1 every 20ms.
|
||||
canvas_div.addEventListener(
|
||||
'mousemove',
|
||||
on_mouse_event_closure('motion_notify')
|
||||
);
|
||||
|
||||
canvas_div.addEventListener(
|
||||
'mouseenter',
|
||||
on_mouse_event_closure('figure_enter')
|
||||
);
|
||||
canvas_div.addEventListener(
|
||||
'mouseleave',
|
||||
on_mouse_event_closure('figure_leave')
|
||||
);
|
||||
|
||||
canvas_div.addEventListener('wheel', function (event) {
|
||||
if (event.deltaY < 0) {
|
||||
event.step = 1;
|
||||
} else {
|
||||
event.step = -1;
|
||||
}
|
||||
on_mouse_event_closure('scroll')(event);
|
||||
});
|
||||
|
||||
canvas_div.appendChild(canvas);
|
||||
canvas_div.appendChild(rubberband_canvas);
|
||||
|
||||
this.rubberband_context = rubberband_canvas.getContext('2d');
|
||||
this.rubberband_context.strokeStyle = '#000000';
|
||||
|
||||
this._resize_canvas = function (width, height, forward) {
|
||||
if (forward) {
|
||||
canvas_div.style.width = width + 'px';
|
||||
canvas_div.style.height = height + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
// Disable right mouse context menu.
|
||||
canvas_div.addEventListener('contextmenu', function (_e) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
function set_focus() {
|
||||
canvas.focus();
|
||||
canvas_div.focus();
|
||||
}
|
||||
|
||||
window.setTimeout(set_focus, 100);
|
||||
};
|
||||
|
||||
mpl.figure.prototype._init_toolbar = function () {
|
||||
var fig = this;
|
||||
|
||||
var toolbar = document.createElement('div');
|
||||
toolbar.classList = 'mpl-toolbar';
|
||||
this.root.appendChild(toolbar);
|
||||
|
||||
function on_click_closure(name) {
|
||||
return function (_event) {
|
||||
return fig.toolbar_button_onclick(name);
|
||||
};
|
||||
}
|
||||
|
||||
function on_mouseover_closure(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) {
|
||||
/* 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 = (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('img');
|
||||
icon_img.src = '_images/' + image + '.png';
|
||||
icon_img.srcset = '_images/' + image + '_large.png 2x';
|
||||
icon_img.alt = tooltip;
|
||||
button.appendChild(icon_img);
|
||||
|
||||
buttonGroup.appendChild(button);
|
||||
}
|
||||
|
||||
if (buttonGroup.hasChildNodes()) {
|
||||
toolbar.appendChild(buttonGroup);
|
||||
}
|
||||
|
||||
var fmt_picker = document.createElement('select');
|
||||
fmt_picker.classList = 'mpl-widget';
|
||||
toolbar.appendChild(fmt_picker);
|
||||
this.format_dropdown = fmt_picker;
|
||||
|
||||
for (var ind in mpl.extensions) {
|
||||
var fmt = mpl.extensions[ind];
|
||||
var option = document.createElement('option');
|
||||
option.selected = fmt === mpl.default_extension;
|
||||
option.innerHTML = fmt;
|
||||
fmt_picker.appendChild(option);
|
||||
}
|
||||
|
||||
var status_bar = document.createElement('span');
|
||||
status_bar.classList = 'mpl-message';
|
||||
toolbar.appendChild(status_bar);
|
||||
this.message = status_bar;
|
||||
};
|
||||
|
||||
mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {
|
||||
// Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,
|
||||
// which will in turn request a refresh of the image.
|
||||
this.send_message('resize', { width: x_pixels, height: y_pixels });
|
||||
};
|
||||
|
||||
mpl.figure.prototype.send_message = function (type, properties) {
|
||||
properties['type'] = type;
|
||||
properties['figure_id'] = this.id;
|
||||
this.ws.send(JSON.stringify(properties));
|
||||
};
|
||||
|
||||
mpl.figure.prototype.send_draw_message = function () {
|
||||
if (!this.waiting) {
|
||||
this.waiting = true;
|
||||
this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));
|
||||
}
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_save = function (fig, _msg) {
|
||||
var format_dropdown = fig.format_dropdown;
|
||||
var format = format_dropdown.options[format_dropdown.selectedIndex].value;
|
||||
fig.ondownload(fig, format);
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_resize = function (fig, msg) {
|
||||
var size = msg['size'];
|
||||
if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {
|
||||
fig._resize_canvas(size[0], size[1], msg['forward']);
|
||||
fig.send_message('refresh', {});
|
||||
}
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_rubberband = function (fig, msg) {
|
||||
var x0 = msg['x0'] / fig.ratio;
|
||||
var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;
|
||||
var x1 = msg['x1'] / fig.ratio;
|
||||
var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;
|
||||
x0 = Math.floor(x0) + 0.5;
|
||||
y0 = Math.floor(y0) + 0.5;
|
||||
x1 = Math.floor(x1) + 0.5;
|
||||
y1 = Math.floor(y1) + 0.5;
|
||||
var min_x = Math.min(x0, x1);
|
||||
var min_y = Math.min(y0, y1);
|
||||
var width = Math.abs(x1 - x0);
|
||||
var height = Math.abs(y1 - y0);
|
||||
|
||||
fig.rubberband_context.clearRect(
|
||||
0,
|
||||
0,
|
||||
fig.canvas.width / fig.ratio,
|
||||
fig.canvas.height / fig.ratio
|
||||
);
|
||||
|
||||
fig.rubberband_context.strokeRect(min_x, min_y, width, height);
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_figure_label = function (fig, msg) {
|
||||
// Updates the figure title.
|
||||
fig.header.textContent = msg['label'];
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_cursor = function (fig, msg) {
|
||||
fig.canvas_div.style.cursor = msg['cursor'];
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_message = function (fig, msg) {
|
||||
fig.message.textContent = msg['message'];
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_draw = function (fig, _msg) {
|
||||
// Request the server to send over a new figure.
|
||||
fig.send_draw_message();
|
||||
};
|
||||
|
||||
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', {});
|
||||
};
|
||||
|
||||
// A function to construct a web socket function for onmessage handling.
|
||||
// Called in the figure constructor.
|
||||
mpl.figure.prototype._make_on_message_function = function (fig) {
|
||||
return function socket_on_message(evt) {
|
||||
if (evt.data instanceof Blob) {
|
||||
var img = evt.data;
|
||||
if (img.type !== 'image/png') {
|
||||
/* FIXME: We get "Resource interpreted as Image but
|
||||
* transferred with MIME type text/plain:" errors on
|
||||
* Chrome. But how to set the MIME type? It doesn't seem
|
||||
* to be part of the websocket stream */
|
||||
img.type = 'image/png';
|
||||
}
|
||||
|
||||
/* Free the memory for the previous frames */
|
||||
if (fig.imageObj.src) {
|
||||
(window.URL || window.webkitURL).revokeObjectURL(
|
||||
fig.imageObj.src
|
||||
);
|
||||
}
|
||||
|
||||
fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(
|
||||
img
|
||||
);
|
||||
fig.updated_canvas_event();
|
||||
fig.waiting = false;
|
||||
return;
|
||||
} else if (
|
||||
typeof evt.data === 'string' &&
|
||||
evt.data.slice(0, 21) === 'data:image/png;base64'
|
||||
) {
|
||||
fig.imageObj.src = evt.data;
|
||||
fig.updated_canvas_event();
|
||||
fig.waiting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = JSON.parse(evt.data);
|
||||
var msg_type = msg['type'];
|
||||
|
||||
// Call the "handle_{type}" callback, which takes
|
||||
// the figure and JSON message as its only arguments.
|
||||
try {
|
||||
var callback = fig['handle_' + msg_type];
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"No handler for the '" + msg_type + "' message type: ",
|
||||
msg
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
try {
|
||||
// console.log("Handling '" + msg_type + "' message: ", msg);
|
||||
callback(fig, msg);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Exception inside the 'handler_" + msg_type + "' callback:",
|
||||
e,
|
||||
e.stack,
|
||||
msg
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getModifiers(event) {
|
||||
var mods = [];
|
||||
if (event.ctrlKey) {
|
||||
mods.push('ctrl');
|
||||
}
|
||||
if (event.altKey) {
|
||||
mods.push('alt');
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
mods.push('shift');
|
||||
}
|
||||
if (event.metaKey) {
|
||||
mods.push('meta');
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
/*
|
||||
* return a copy of an object with only non-object keys
|
||||
* we need this to avoid circular references
|
||||
* https://stackoverflow.com/a/24161582/3208463
|
||||
*/
|
||||
function simpleKeys(original) {
|
||||
return Object.keys(original).reduce(function (obj, key) {
|
||||
if (typeof original[key] !== 'object') {
|
||||
obj[key] = original[key];
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
mpl.figure.prototype.mouse_event = function (event, name) {
|
||||
if (name === 'button_press') {
|
||||
this.canvas.focus();
|
||||
this.canvas_div.focus();
|
||||
}
|
||||
|
||||
// from https://stackoverflow.com/q/1114465
|
||||
var boundingRect = this.canvas.getBoundingClientRect();
|
||||
var x = (event.clientX - boundingRect.left) * this.ratio;
|
||||
var y = (event.clientY - boundingRect.top) * this.ratio;
|
||||
|
||||
this.send_message(name, {
|
||||
x: x,
|
||||
y: y,
|
||||
button: event.button,
|
||||
step: event.step,
|
||||
buttons: event.buttons,
|
||||
modifiers: getModifiers(event),
|
||||
guiEvent: simpleKeys(event),
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
mpl.figure.prototype._key_event_extra = function (_event, _name) {
|
||||
// Handle any extra behaviour associated with a key event
|
||||
};
|
||||
|
||||
mpl.figure.prototype.key_event = function (event, name) {
|
||||
// Prevent repeat events
|
||||
if (name === 'key_press') {
|
||||
if (event.key === this._key) {
|
||||
return;
|
||||
} else {
|
||||
this._key = event.key;
|
||||
}
|
||||
}
|
||||
if (name === 'key_release') {
|
||||
this._key = null;
|
||||
}
|
||||
|
||||
var value = '';
|
||||
if (event.ctrlKey && event.key !== 'Control') {
|
||||
value += 'ctrl+';
|
||||
}
|
||||
else if (event.altKey && event.key !== 'Alt') {
|
||||
value += 'alt+';
|
||||
}
|
||||
else if (event.shiftKey && event.key !== 'Shift') {
|
||||
value += 'shift+';
|
||||
}
|
||||
|
||||
value += 'k' + event.key;
|
||||
|
||||
this._key_event_extra(event, name);
|
||||
|
||||
this.send_message(name, { key: value, guiEvent: simpleKeys(event) });
|
||||
return false;
|
||||
};
|
||||
|
||||
mpl.figure.prototype.toolbar_button_onclick = function (name) {
|
||||
if (name === 'download') {
|
||||
this.handle_save(this, null);
|
||||
} else {
|
||||
this.send_message('toolbar_button', { name: name });
|
||||
}
|
||||
};
|
||||
|
||||
mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {
|
||||
this.message.textContent = tooltip;
|
||||
};
|
||||
|
||||
///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////
|
||||
// prettier-ignore
|
||||
var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError("Constructor requires 'new' operator");i.set(this,e)}function h(){throw new TypeError("Function is not a constructor")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line
|
||||
@ -0,0 +1,8 @@
|
||||
/* This .js file contains functions for matplotlib's built-in
|
||||
tornado-based server, that are not relevant when embedding WebAgg
|
||||
in another web application. */
|
||||
|
||||
/* exported mpl_ondownload */
|
||||
function mpl_ondownload(figure, format) {
|
||||
window.open(figure.id + '/download.' + format, '_blank');
|
||||
}
|
||||
@ -0,0 +1,275 @@
|
||||
/* global mpl */
|
||||
|
||||
var comm_websocket_adapter = function (comm) {
|
||||
// Create a "websocket"-like object which calls the given IPython comm
|
||||
// object with the appropriate methods. Currently this is a non binary
|
||||
// socket, so there is still some room for performance tuning.
|
||||
var ws = {};
|
||||
|
||||
ws.binaryType = comm.kernel.ws.binaryType;
|
||||
ws.readyState = comm.kernel.ws.readyState;
|
||||
function updateReadyState(_event) {
|
||||
if (comm.kernel.ws) {
|
||||
ws.readyState = comm.kernel.ws.readyState;
|
||||
} else {
|
||||
ws.readyState = 3; // Closed state.
|
||||
}
|
||||
}
|
||||
comm.kernel.ws.addEventListener('open', updateReadyState);
|
||||
comm.kernel.ws.addEventListener('close', updateReadyState);
|
||||
comm.kernel.ws.addEventListener('error', updateReadyState);
|
||||
|
||||
ws.close = function () {
|
||||
comm.close();
|
||||
};
|
||||
ws.send = function (m) {
|
||||
//console.log('sending', m);
|
||||
comm.send(m);
|
||||
};
|
||||
// Register the callback with on_msg.
|
||||
comm.on_msg(function (msg) {
|
||||
//console.log('receiving', msg['content']['data'], msg);
|
||||
var data = msg['content']['data'];
|
||||
if (data['blob'] !== undefined) {
|
||||
data = {
|
||||
data: new Blob(msg['buffers'], { type: data['blob'] }),
|
||||
};
|
||||
}
|
||||
// Pass the mpl event to the overridden (by mpl) onmessage function.
|
||||
ws.onmessage(data);
|
||||
});
|
||||
return ws;
|
||||
};
|
||||
|
||||
mpl.mpl_figure_comm = function (comm, msg) {
|
||||
// This is the function which gets called when the mpl process
|
||||
// starts-up an IPython Comm through the "matplotlib" channel.
|
||||
|
||||
var id = msg.content.data.id;
|
||||
// Get hold of the div created by the display call when the Comm
|
||||
// socket was opened in Python.
|
||||
var element = document.getElementById(id);
|
||||
var ws_proxy = comm_websocket_adapter(comm);
|
||||
|
||||
function ondownload(figure, _format) {
|
||||
window.open(figure.canvas.toDataURL());
|
||||
}
|
||||
|
||||
var fig = new mpl.figure(id, ws_proxy, ondownload, element);
|
||||
|
||||
// Call onopen now - mpl needs it, as it is assuming we've passed it a real
|
||||
// web socket which is closed, not our websocket->open comm proxy.
|
||||
ws_proxy.onopen();
|
||||
|
||||
fig.parent_element = element;
|
||||
fig.cell_info = mpl.find_output_cell("<div id='" + id + "'></div>");
|
||||
if (!fig.cell_info) {
|
||||
console.error('Failed to find cell for figure', id, fig);
|
||||
return;
|
||||
}
|
||||
fig.cell_info[0].output_area.element.on(
|
||||
'cleared',
|
||||
{ fig: fig },
|
||||
fig._remove_fig_handler
|
||||
);
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_close = function (fig, msg) {
|
||||
var width = fig.canvas.width / fig.ratio;
|
||||
fig.cell_info[0].output_area.element.off(
|
||||
'cleared',
|
||||
fig._remove_fig_handler
|
||||
);
|
||||
fig.resizeObserverInstance.unobserve(fig.canvas_div);
|
||||
|
||||
// Update the output cell to use the data from the current canvas.
|
||||
fig.push_to_output();
|
||||
var dataURL = fig.canvas.toDataURL();
|
||||
// Re-enable the keyboard manager in IPython - without this line, in FF,
|
||||
// the notebook keyboard shortcuts fail.
|
||||
IPython.keyboard_manager.enable();
|
||||
fig.parent_element.innerHTML =
|
||||
'<img src="' + dataURL + '" width="' + width + '">';
|
||||
fig.close_ws(fig, msg);
|
||||
};
|
||||
|
||||
mpl.figure.prototype.close_ws = function (fig, msg) {
|
||||
fig.send_message('closing', msg);
|
||||
// fig.ws.close()
|
||||
};
|
||||
|
||||
mpl.figure.prototype.push_to_output = function (_remove_interactive) {
|
||||
// Turn the data on the canvas into data in the output cell.
|
||||
var width = this.canvas.width / this.ratio;
|
||||
var dataURL = this.canvas.toDataURL();
|
||||
this.cell_info[1]['text/html'] =
|
||||
'<img src="' + dataURL + '" width="' + width + '">';
|
||||
};
|
||||
|
||||
mpl.figure.prototype.updated_canvas_event = function () {
|
||||
// Tell IPython that the notebook contents must change.
|
||||
IPython.notebook.set_dirty(true);
|
||||
this.send_message('ack', {});
|
||||
var fig = this;
|
||||
// Wait a second, then push the new image to the DOM so
|
||||
// that it is saved nicely (might be nice to debounce this).
|
||||
setTimeout(function () {
|
||||
fig.push_to_output();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
mpl.figure.prototype._init_toolbar = function () {
|
||||
var fig = this;
|
||||
|
||||
var toolbar = document.createElement('div');
|
||||
toolbar.classList = 'btn-toolbar';
|
||||
this.root.appendChild(toolbar);
|
||||
|
||||
function on_click_closure(name) {
|
||||
return function (_event) {
|
||||
return fig.toolbar_button_onclick(name);
|
||||
};
|
||||
}
|
||||
|
||||
function on_mouseover_closure(tooltip) {
|
||||
return function (event) {
|
||||
if (!event.currentTarget.disabled) {
|
||||
return fig.toolbar_button_onmouseover(tooltip);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fig.buttons = {};
|
||||
var buttonGroup = document.createElement('div');
|
||||
buttonGroup.classList = 'btn-group';
|
||||
var button;
|
||||
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) {
|
||||
/* Instead of a spacer, we start a new button group. */
|
||||
if (buttonGroup.hasChildNodes()) {
|
||||
toolbar.appendChild(buttonGroup);
|
||||
}
|
||||
buttonGroup = document.createElement('div');
|
||||
buttonGroup.classList = 'btn-group';
|
||||
continue;
|
||||
}
|
||||
|
||||
button = fig.buttons[name] = document.createElement('button');
|
||||
button.classList = 'btn btn-default';
|
||||
button.href = '#';
|
||||
button.title = name;
|
||||
button.innerHTML = '<i class="fa ' + image + ' fa-lg"></i>';
|
||||
button.addEventListener('click', on_click_closure(method_name));
|
||||
button.addEventListener('mouseover', on_mouseover_closure(tooltip));
|
||||
buttonGroup.appendChild(button);
|
||||
}
|
||||
|
||||
if (buttonGroup.hasChildNodes()) {
|
||||
toolbar.appendChild(buttonGroup);
|
||||
}
|
||||
|
||||
// Add the status bar.
|
||||
var status_bar = document.createElement('span');
|
||||
status_bar.classList = 'mpl-message pull-right';
|
||||
toolbar.appendChild(status_bar);
|
||||
this.message = status_bar;
|
||||
|
||||
// Add the close button to the window.
|
||||
var buttongrp = document.createElement('div');
|
||||
buttongrp.classList = 'btn-group inline pull-right';
|
||||
button = document.createElement('button');
|
||||
button.classList = 'btn btn-mini btn-primary';
|
||||
button.href = '#';
|
||||
button.title = 'Stop Interaction';
|
||||
button.innerHTML = '<i class="fa fa-power-off icon-remove icon-large"></i>';
|
||||
button.addEventListener('click', function (_evt) {
|
||||
fig.handle_close(fig, {});
|
||||
});
|
||||
button.addEventListener(
|
||||
'mouseover',
|
||||
on_mouseover_closure('Stop Interaction')
|
||||
);
|
||||
buttongrp.appendChild(button);
|
||||
var titlebar = this.root.querySelector('.ui-dialog-titlebar');
|
||||
titlebar.insertBefore(buttongrp, titlebar.firstChild);
|
||||
};
|
||||
|
||||
mpl.figure.prototype._remove_fig_handler = function (event) {
|
||||
var fig = event.data.fig;
|
||||
if (event.target !== this) {
|
||||
// Ignore bubbled events from children.
|
||||
return;
|
||||
}
|
||||
fig.close_ws(fig, {});
|
||||
};
|
||||
|
||||
mpl.figure.prototype._root_extra_style = function (el) {
|
||||
el.style.boxSizing = 'content-box'; // override notebook setting of border-box.
|
||||
};
|
||||
|
||||
mpl.figure.prototype._canvas_extra_style = function (el) {
|
||||
// this is important to make the div 'focusable
|
||||
el.setAttribute('tabindex', 0);
|
||||
// reach out to IPython and tell the keyboard manager to turn it's self
|
||||
// off when our div gets focus
|
||||
|
||||
// location in version 3
|
||||
if (IPython.notebook.keyboard_manager) {
|
||||
IPython.notebook.keyboard_manager.register_events(el);
|
||||
} else {
|
||||
// location in version 2
|
||||
IPython.keyboard_manager.register_events(el);
|
||||
}
|
||||
};
|
||||
|
||||
mpl.figure.prototype._key_event_extra = function (event, _name) {
|
||||
// Check for shift+enter
|
||||
if (event.shiftKey && event.which === 13) {
|
||||
this.canvas_div.blur();
|
||||
// select the cell after this one
|
||||
var index = IPython.notebook.find_cell_index(this.cell_info[0]);
|
||||
IPython.notebook.select(index + 1);
|
||||
}
|
||||
};
|
||||
|
||||
mpl.figure.prototype.handle_save = function (fig, _msg) {
|
||||
fig.ondownload(fig, null);
|
||||
};
|
||||
|
||||
mpl.find_output_cell = function (html_output) {
|
||||
// Return the cell and output element which can be found *uniquely* in the notebook.
|
||||
// Note - this is a bit hacky, but it is done because the "notebook_saving.Notebook"
|
||||
// IPython event is triggered only after the cells have been serialised, which for
|
||||
// our purposes (turning an active figure into a static one), is too late.
|
||||
var cells = IPython.notebook.get_cells();
|
||||
var ncells = cells.length;
|
||||
for (var i = 0; i < ncells; i++) {
|
||||
var cell = cells[i];
|
||||
if (cell.cell_type === 'code') {
|
||||
for (var j = 0; j < cell.output_area.outputs.length; j++) {
|
||||
var data = cell.output_area.outputs[j];
|
||||
if (data.data) {
|
||||
// IPython >= 3 moved mimebundle to data attribute of output
|
||||
data = data.data;
|
||||
}
|
||||
if (data['text/html'] === html_output) {
|
||||
return [cell, data, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Register the function which deals with the matplotlib target/channel.
|
||||
// The kernel may be null if the page has been refreshed.
|
||||
if (IPython.notebook.kernel !== null) {
|
||||
IPython.notebook.kernel.comm_manager.register_target(
|
||||
'matplotlib',
|
||||
mpl.mpl_figure_comm
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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">
|
||||
<script src="{{ prefix }}/_static/js/mpl_tornado.js"></script>
|
||||
<script src="{{ prefix }}/js/mpl.js"></script>
|
||||
<script>
|
||||
function ready(fn) {
|
||||
if (document.readyState != "loading") {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", fn);
|
||||
}
|
||||
}
|
||||
|
||||
ready(
|
||||
function () {
|
||||
var websocket_type = mpl.get_websocket_type();
|
||||
var uri = "{{ ws_uri }}" + {{ str(fig_id) }} + "/ws";
|
||||
if (window.location.protocol === 'https:') uri = uri.replace('ws:', 'wss:')
|
||||
var websocket = new websocket_type(uri);
|
||||
var fig = new mpl.figure(
|
||||
{{ str(fig_id) }}, websocket, mpl_ondownload,
|
||||
document.getElementById("figure"));
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<title>matplotlib</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mpl-warnings" class="mpl-warnings"></div>
|
||||
<div id="figure" style="margin: 10px 10px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user