From 78cac75935a4900680495974c45d3fe82cadb7c2 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 22 May 2013 21:13:54 -0400 Subject: [PATCH 1/7] mpl_interface.js template does not need to be parameterized on the figure number --- lib/matplotlib/backends/backend_webagg.py | 11 +++---- .../backends/web_backend/all_figures.html | 30 +++++++++---------- .../backends/web_backend/single_figure.html | 22 +++++++------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index d0b7c22d0cc9..3d121ac3d18b 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -428,18 +428,19 @@ def get(self): class MPLInterfaceJS(tornado.web.RequestHandler): - def get(self, fignum): + def get(self): with open(os.path.join(WebAggApplication._mpl_dirs['web_backend'], 'mpl_interface.js')) as fd: tpl = fd.read() - fignum = int(fignum) - manager = Gcf.get_fig_manager(fignum) + manager = Gcf.get_fig_manager(1) + canvas = manager.canvas t = tornado.template.Template(tpl) self.write(t.generate( toolitems=NavigationToolbar2WebAgg.toolitems, - canvas=manager.canvas)) + canvas=canvas)) + class Download(tornado.web.RequestHandler): def get(self, fignum, fmt): @@ -553,7 +554,7 @@ def __init__(self, url_prefix=''): (url_prefix + r'/([0-9]+)', self.SingleFigurePage, {'url_prefix': url_prefix}), - (url_prefix + r'/([0-9]+)/mpl_interface.js', self.MPLInterfaceJS), + (url_prefix + r'/mpl_interface.js', self.MPLInterfaceJS), # Sends images and events to the browser, and receives # events from the browser diff --git a/lib/matplotlib/backends/web_backend/all_figures.html b/lib/matplotlib/backends/web_backend/all_figures.html index a501d9379ce5..c3566b7963c0 100644 --- a/lib/matplotlib/backends/web_backend/all_figures.html +++ b/lib/matplotlib/backends/web_backend/all_figures.html @@ -7,61 +7,61 @@ - - + + - - MPL | WebAgg current figures + + MPL | WebAgg current figures
{% for (fig_id, fig_manager) in figures %} {% set fig_label='Figure: {}'.format(fig_manager.canvas.figure.get_label()) %} - + {% if fig_label == 'Figure: ' %} {% set fig_label="Figure {}".format(fig_id) %} {% end %} - +

{{ fig_label }} - +

{% end %} - + diff --git a/lib/matplotlib/backends/web_backend/single_figure.html b/lib/matplotlib/backends/web_backend/single_figure.html index 3cbe5a7f1f99..a01f639c9bb1 100644 --- a/lib/matplotlib/backends/web_backend/single_figure.html +++ b/lib/matplotlib/backends/web_backend/single_figure.html @@ -7,23 +7,23 @@ - - + + - + {% set fig_label='Figure: {}'.format(canvas.figure.get_label()) %} - + {% if fig_label == 'Figure: ' %} {% set fig_label="Figure {}".format(fig_id) %} -{% end %} +{% end %} - MPL | {{ fig_label }} + MPL | {{ fig_label }} @@ -52,6 +52,6 @@

- + From 27482be73b5ad69d2d52cc77cdf698d3a0a6d3e1 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 23 May 2013 10:44:38 -0400 Subject: [PATCH 2/7] Make the WebAgg backend work with the IPython notebook --- lib/matplotlib/backends/backend_webagg.py | 74 +++++++- .../backends/web_backend/all_figures.html | 80 ++++---- lib/matplotlib/backends/web_backend/mpl.js | 119 ++++++------ .../backends/web_backend/mpl_interface.js | 174 +++++++++--------- .../backends/web_backend/single_figure.html | 75 +++----- lib/matplotlib/figure.py | 8 + lib/matplotlib/pyplot.py | 2 +- 7 files changed, 285 insertions(+), 247 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index 3d121ac3d18b..ba3bab84d4ed 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -10,6 +10,8 @@ import os import random import socket +import threading +import types import numpy as np @@ -30,6 +32,15 @@ from matplotlib._pylab_helpers import Gcf from matplotlib import _png +# TODO: This should really only be set for the IPython notebook, but +# I'm not sure how to detect that. +try: + __IPYTHON__ +except: + _in_ipython = False +else: + _in_ipython = True + def draw_if_interactive(): """ @@ -57,7 +68,30 @@ def mainloop(self): WebAggApplication.start() -show = Show() + +if not _in_ipython: + show = Show() +else: + def show(): + class RawHTML(object): + def __init__(self, content): + self._content = content + + def _repr_html_(self): + return self._content + + result = [] + import matplotlib._pylab_helpers as pylab_helpers + for manager in pylab_helpers.Gcf().get_all_fig_managers(): + result.append(ipython_inline_display(manager.canvas.figure)) + return RawHTML('\n'.join(result)) + + +class ServerThread(threading.Thread): + def run(self): + tornado.ioloop.IOLoop.instance().start() + +server_thread = ServerThread() def new_figure_manager(num, *args, **kwargs): @@ -127,6 +161,16 @@ def __init__(self, *args, **kwargs): # messages from piling up. self._pending_draw = None + # TODO: I'd like to dynamically add the _repr_html_ method + # to the figure in the right context, but then IPython doesn't + # use it, for some reason. + + # Add the _repr_html_ member to the figure for IPython inline + # support + # if _in_ipython: + # self.figure._repr_html_ = types.MethodType( + # ipython_inline_display, self.figure, self.figure.__class__) + def show(self): # show the figure window show() @@ -199,7 +243,7 @@ def get_diff_image(self): self._png_is_old = False return self._png_buffer.getvalue() - def get_renderer(self, cleared=False): + def get_renderer(self, cleared=None): # Mirrors super.get_renderer, but caches the old one # so that we can do things such as prodce a diff image # in get_diff_image @@ -436,6 +480,8 @@ def get(self): manager = Gcf.get_fig_manager(1) canvas = manager.canvas + self.set_header('Content-Type', 'application/javascript') + t = tornado.template.Template(tpl) self.write(t.generate( toolitems=NavigationToolbar2WebAgg.toolitems, @@ -624,3 +670,27 @@ def start(cls): print("Server stopped") cls.started = True + + +def ipython_inline_display(figure): + import matplotlib._pylab_helpers as pylab_helpers + + WebAggApplication.initialize() + if not server_thread.is_alive(): + server_thread.start() + + with open(os.path.join( + WebAggApplication._mpl_dirs['web_backend'], + 'ipython_inline_figure.html')) as fd: + tpl = fd.read() + + fignum = figure.number + manager = pylab_helpers.Gcf.get_fig_manager(fignum) + + t = tornado.template.Template(tpl) + return t.generate( + prefix=WebAggApplication.url_prefix, + fig_id=fignum, + toolitems=NavigationToolbar2WebAgg.toolitems, + canvas=figure.canvas, + port=WebAggApplication.port) diff --git a/lib/matplotlib/backends/web_backend/all_figures.html b/lib/matplotlib/backends/web_backend/all_figures.html index c3566b7963c0..62ab46adfd58 100644 --- a/lib/matplotlib/backends/web_backend/all_figures.html +++ b/lib/matplotlib/backends/web_backend/all_figures.html @@ -3,65 +3,49 @@ - - - - - + + + + + - + - MPL | WebAgg current figures + MPL | WebAgg current figures - +
- {% for (fig_id, fig_manager) in figures %} - {% set fig_label='Figure: {}'.format(fig_manager.canvas.figure.get_label()) %} - - {% if fig_label == 'Figure: ' %} - {% set fig_label="Figure {}".format(fig_id) %} - {% end %} - -
-

- - {{ fig_label }} - - -

-
-
-
- {% end %} - +
diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index f9d9761801cc..e7a5c63f350c 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -1,6 +1,6 @@ -function figure(fig_id, websocket_url_prefix) { +function figure(fig_id, websocket_url_prefix, parent_element) { this.id = fig_id; - + if (typeof(WebSocket) !== 'undefined') { this.WebSocket = WebSocket; } else if (typeof(MozWebSocket) !== 'undefined') { @@ -11,10 +11,10 @@ function figure(fig_id, websocket_url_prefix) { 'Firefox 4 and 5 are also supported but you ' + 'have to enable WebSockets in about:config.'); }; - - + + this.ws = new this.WebSocket(websocket_url_prefix + fig_id + '/ws'); - + this.supports_binary = (this.ws.binaryType != undefined); if (!this.supports_binary) { @@ -24,55 +24,44 @@ function figure(fig_id, websocket_url_prefix) { "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.focus_on_mousover = false; - -} -figure.prototype.finalize = function (canvas_id_prefix, toolbar_id_prefix, message_id_prefix) { - // resizing_div_id might be the canvas or a containing div for more control of display - - var canvas_id = canvas_id_prefix + '-canvas'; - var rubberband_id = canvas_id_prefix + '-rubberband-canvas'; - var message_id = message_id_prefix + '-message'; - - this.message = document.getElementById(message_id); - this.canvas = document.getElementById(canvas_id); - this.context = this.canvas.getContext("2d"); - this.rubberband_canvas = document.getElementById(rubberband_id); - this.rubberband_context = this.rubberband_canvas.getContext("2d"); - this.rubberband_context.strokeStyle = "#000000"; - - this.format_dropdown = document.getElementById(toolbar_id_prefix + '-format_picker'); - + this.root = $('
'); + $(parent_element).append(this.root); + + init_mpl_canvas(this); + init_mpl_toolbar(this); + this.ws.onopen = function () { this.ws.send(JSON.stringify( {type: 'supports_binary', value: this.supports_binary})); } - - // attach the onload function to the image object when an - // image has been recieved via onmessage + fig = this - onload_creator = function(fig) {return function() {fig.context.drawImage(fig.imageObj, 0, 0);};}; + onload_creator = function(fig) { + return function() { + fig.context.drawImage(fig.imageObj, 0, 0); + }; + }; this.imageObj.onload = onload_creator(fig); - this.imageObj.onunload = function() { this.ws.close(); } this.ws.onmessage = gen_on_msg_fn(this); -}; +} function gen_on_msg_fn(fig) @@ -85,7 +74,7 @@ function gen_on_msg_fn(fig) * Chrome. But how to set the MIME type? It doesn't seem * to be part of the websocket stream */ evt.data.type = "image/png"; - + /* Free the memory for the previous frames */ if (fig.imageObj.src) { (window.URL || window.webkitURL).revokeObjectURL( @@ -103,14 +92,14 @@ function gen_on_msg_fn(fig) return; } } - + var msg = JSON.parse(evt.data); - + switch(msg['type']) { case 'message': fig.message.textContent = msg['message']; break; - + case 'cursor': var cursor = msg['cursor']; switch(cursor) @@ -130,7 +119,7 @@ function gen_on_msg_fn(fig) } fig.canvas.style.cursor = cursor; break; - + case 'resize': var size = msg['size']; if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) { @@ -144,7 +133,7 @@ function gen_on_msg_fn(fig) value: fig.supports_binary})); } break; - + case 'rubberband': var x0 = msg['x0']; var y0 = fig.canvas.height - msg['y0']; @@ -158,7 +147,7 @@ function gen_on_msg_fn(fig) 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.canvas.height); fig.rubberband_context.strokeRect(min_x, min_y, width, height); @@ -167,33 +156,40 @@ function gen_on_msg_fn(fig) }; }; +// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas + +function findPos(e) { + //this section is from http://www.quirksmode.org/js/events_properties.html + var targ; + if (!e) + e = window.event; + if (e.target) + targ = e.target; + else if (e.srcElement) + targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + // jQuery normalizes the pageX and pageY + // pageX,Y are the mouse positions relative to the document + // offset() returns the position of the element relative to the document + var x = e.pageX - $(targ).offset().left; + var y = e.pageY - $(targ).offset().top; + + return {"x": x, "y": y}; +}; - -function findPos(obj) { - // Find the position of the given HTML node. - - var curleft = 0, curtop = 0; - if (obj.offsetParent) { - do { - curleft += obj.offsetLeft; - curtop += obj.offsetTop; - } while (obj = obj.offsetParent); - return { x: curleft, y: curtop }; - } - return undefined; -} - figure.prototype.mouse_event = function(event, name) { - var canvas_pos = findPos(this.canvas) - + var canvas_pos = findPos(event) + if (this.focus_on_mouseover && name === 'motion_notify') { this.canvas.focus(); } - - var x = event.pageX - canvas_pos.x; - var y = event.pageY - canvas_pos.y; + + var x = canvas_pos.x; + var y = canvas_pos.y; this.ws.send(JSON.stringify( {type: name, @@ -205,7 +201,7 @@ figure.prototype.mouse_event = function(event, name) { * to control all of the cursor setting manually through the * 'cursor' event from matplotlib */ event.preventDefault(); - return false; + return false; } figure.prototype.key_event = function(event, name) { @@ -241,9 +237,6 @@ figure.prototype.toolbar_button_onclick = function(name) { } }; - figure.prototype.toolbar_button_onmouseover = function(tooltip) { this.message.textContent = tooltip; }; - - diff --git a/lib/matplotlib/backends/web_backend/mpl_interface.js b/lib/matplotlib/backends/web_backend/mpl_interface.js index d6f2c046b89b..06b782aaede0 100644 --- a/lib/matplotlib/backends/web_backend/mpl_interface.js +++ b/lib/matplotlib/backends/web_backend/mpl_interface.js @@ -6,111 +6,113 @@ var extensions = [{% for filetype, extensions in sorted(canvas.get_supported_fil var default_extension = '{{ canvas.get_default_filetype() }}'; -function init_mpl_canvas(fig, canvas_div_id, id_prefix) { - - var canvas_div = $(document.getElementById(canvas_div_id)); +function init_mpl_canvas(fig) { + var canvas_div = $('
'); canvas_div.attr('style', 'position: relative; clear: both;'); - - var canvas = $('', {id: id_prefix + '-canvas'}); - canvas.attr('id', id_prefix + '-canvas'); + fig.root.append(canvas_div); + + var canvas = $(''); canvas.addClass('mpl-canvas'); canvas.attr('style', "left: 0; top: 0; z-index: 0;") canvas.attr('width', '800'); canvas.attr('height', '800'); - + function canvas_keyboard_event(event) { return fig.key_event(event, event['data']); } - canvas.keydown('key_press', canvas_keyboard_event); - canvas.keyup('key_release', canvas_keyboard_event); - + canvas.keydown('key_press', canvas_keyboard_event); + canvas.keyup('key_release', canvas_keyboard_event); + canvas_div.append(canvas); - + + fig.canvas = canvas[0]; + fig.context = canvas[0].getContext("2d"); + // Let the top level document handle key events. + canvas.unbind('keydown'); + canvas.unbind('keyup'); + // create a second canvas which floats on top of the first. - var rubberband = $('', {id: id_prefix + '-rubberband-canvas'}); + var rubberband = $(''); rubberband.attr('style', "position: absolute; left: 0; top: 0; z-index: 1;") rubberband.attr('width', '800'); rubberband.attr('height', '800'); function mouse_event_fn(event) { return fig.mouse_event(event, event['data']); } - rubberband.mousedown('button_press', mouse_event_fn); + rubberband.mousedown('button_press', mouse_event_fn); rubberband.mouseup('button_release', mouse_event_fn); rubberband.mousemove('motion_notify', mouse_event_fn); canvas_div.append(rubberband); + + fig.rubberband_canvas = rubberband[0]; + fig.rubberband_context = rubberband[0].getContext("2d"); + fig.rubberband_context.strokeStyle = "#000000"; }; -function init_mpl_statusbar(container_id, id_prefix) { - var status_bar = $(''); - var status_id = id_prefix + '-message'; - status_bar.attr('id', status_id); - $(document.getElementById(container_id)).append(status_bar); - return status_id -}; +function init_mpl_toolbar(fig) { + var nav_element = $('
') + nav_element.attr('style', 'width: 600px'); + fig.root.append(nav_element); -function init_mpl_toolbar(fig, nav_container_id, nav_elem_id_prefix) { - // Adds a navigation toolbar to the object found with the given jquery query string - - if (nav_elem_id_prefix === undefined) { - nav_elem_id_prefix = nav_container_id; - } - - // Define a callback function for later on. - function toolbar_event(event) { return fig.toolbar_button_onclick(event['data']); } - function toolbar_mouse_event(event) { return fig.toolbar_button_onmouseover(event['data']); } - - var nav_element = $(document.getElementById(nav_container_id)); - - for(var toolbar_ind in toolbar_items){ - var name = toolbar_items[toolbar_ind][0]; - var tooltip = toolbar_items[toolbar_ind][1]; - var image = toolbar_items[toolbar_ind][2]; - var method_name = toolbar_items[toolbar_ind][3]; - - if (!name) { - // put a spacer in here. - continue; - } - - var button = $('