diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index d306b950f1ee..63698180788a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1454,6 +1454,17 @@ def tk_window_focus(): except (KeyError, ValueError): pass + +# Jupyter extension paths +def _jupyter_nbextension_paths(): + return [{ + 'section': 'notebook', + 'src': 'backends/web_backend/js', + 'dest': 'matplotlib', + 'require': 'matplotlib/extension' + }] + + default_test_modules = [ 'matplotlib.tests.test_agg', 'matplotlib.tests.test_animation', diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 6c72f16020ae..fe7310155336 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2161,7 +2161,8 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, origfacecolor = self.figure.get_facecolor() origedgecolor = self.figure.get_edgecolor() - self.figure.dpi = dpi + if dpi != 'figure': + self.figure.dpi = dpi self.figure.set_facecolor(facecolor) self.figure.set_edgecolor(edgecolor) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 3fcca314124d..972bc8929975 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -3,23 +3,27 @@ # lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify # that changes made maintain expected behaviour. -import datetime from base64 import b64encode import json import io +from tempfile import mkdtemp +import shutil import os from matplotlib.externals import six from uuid import uuid4 as uuid -import tornado.ioloop - -from IPython.display import display, Javascript, HTML +from IPython.display import display, HTML +from IPython import version_info try: # Jupyter/IPython 4.x or later - from ipykernel.comm import Comm + from ipywidgets import DOMWidget + from traitlets import Unicode, Bool, Float, List, Any + from notebook.nbextensions import install_nbextension, check_nbextension except ImportError: # Jupyter/IPython 3.x or earlier - from IPython.kernel.comm import Comm + from IPython.html.widgets import DOMWidget + from IPython.utils.traitlets import Unicode, Bool, Float, List, Any + from IPython.html.nbextensions import install_nbextension from matplotlib import rcParams from matplotlib.figure import Figure @@ -33,6 +37,7 @@ class Show(ShowBase): + def __call__(self, block=None): from matplotlib._pylab_helpers import Gcf @@ -98,6 +103,7 @@ def connection_info(): 'zoom_to_rect': 'fa fa-square-o icon-check-empty', 'move': 'fa fa-arrows icon-move', 'download': 'fa fa-floppy-o icon-save', + 'export': 'fa fa-file-picture-o icon-picture', None: None } @@ -109,84 +115,74 @@ class NavigationIPy(NavigationToolbar2WebAgg): _FONT_AWESOME_CLASSES[image_file], name_of_method) for text, tooltip_text, image_file, name_of_method in (NavigationToolbar2.toolitems + - (('Download', 'Download plot', 'download', 'download'),)) + (('Download', 'Download plot', 'download', 'download'), + ('Export', 'Export plot', 'export', 'export'))) if image_file in _FONT_AWESOME_CLASSES] + def export(self): + buf = io.BytesIO() + self.canvas.figure.savefig(buf, format='png', dpi='figure') + data = "" + data = data.format(b64encode(buf.getvalue()).decode('utf-8')) + display(HTML(data)) + + +class FigureCanvasNbAgg(DOMWidget, FigureCanvasWebAggCore): + _view_module = Unicode("matplotlib", sync=True) + _view_name = Unicode('MPLCanvasView', sync=True) + _toolbar_items = List(sync=True) + _closed = Bool(True) + _id = Unicode('', sync=True) + + # Must declare the superclass private members. + _png_is_old = Bool() + _force_full = Bool() + _current_image_mode = Unicode() + _dpi_ratio = Float(1.0) + _is_idle_drawing = Bool() + _is_saving = Bool() + _button = Any() + _key = Any() + _lastx = Any() + _lasty = Any() + _is_idle_drawing = Bool() + + def __init__(self, figure, *args, **kwargs): + super(FigureCanvasWebAggCore, self).__init__(figure, *args, **kwargs) + super(DOMWidget, self).__init__(*args, **kwargs) + self._uid = uuid().hex + self.on_msg(self._handle_message) + + def _handle_message(self, object, message, buffers): + # The 'supports_binary' message is relevant to the + # websocket itself. The other messages get passed along + # to matplotlib as-is. -class FigureManagerNbAgg(FigureManagerWebAgg): - ToolbarCls = NavigationIPy - - def __init__(self, canvas, num): - self._shown = False - FigureManagerWebAgg.__init__(self, canvas, num) - - def display_js(self): - # XXX How to do this just once? It has to deal with multiple - # browser instances using the same kernel (require.js - but the - # file isn't static?). - display(Javascript(FigureManagerNbAgg.get_javascript())) - - def show(self): - if not self._shown: - self.display_js() - self._create_comm() - else: - self.canvas.draw_idle() - self._shown = True - - def reshow(self): - """ - A special method to re-show the figure in the notebook. - - """ - self._shown = False - self.show() - - @property - def connected(self): - return bool(self.web_sockets) - - @classmethod - def get_javascript(cls, stream=None): - if stream is None: - output = io.StringIO() + # Every message has a "type" and a "figure_id". + message = json.loads(message) + if message['type'] == 'closing': + self._closed = True + elif message['type'] == 'supports_binary': + self.supports_binary = message['value'] + elif message['type'] == 'initialized': + _, _, w, h = self.figure.bbox.bounds + self.manager.resize(w, h) + self.send_json('refresh') else: - output = stream - super(FigureManagerNbAgg, cls).get_javascript(stream=output) - with io.open(os.path.join( - os.path.dirname(__file__), - "web_backend", - "nbagg_mpl.js"), encoding='utf8') as fd: - output.write(fd.read()) - if stream is None: - return output.getvalue() - - def _create_comm(self): - comm = CommSocket(self) - self.add_web_socket(comm) - return comm - - def destroy(self): - self._send_event('close') - # need to copy comms as callbacks will modify this list - for comm in list(self.web_sockets): - comm.on_close() - self.clearup_closed() - - def clearup_closed(self): - """Clear up any closed Comms.""" - self.web_sockets = set([socket for socket in self.web_sockets - if socket.is_open()]) - - if len(self.web_sockets) == 0: - self.canvas.close_event() + self.manager.handle_json(message) - def remove_comm(self, comm_id): - self.web_sockets = set([socket for socket in self.web_sockets - if not socket.comm.comm_id == comm_id]) + def send_json(self, content): + self.send({'data': json.dumps(content)}) + def send_binary(self, blob): + # The comm is ascii, so we always send the image in base64 + # encoded data URL form. + data = b64encode(blob) + if six.PY3: + data = data.decode('ascii') + data_uri = "data:image/png;base64,{0}".format(data) + self.send({'data': data_uri}) -class FigureCanvasNbAgg(FigureCanvasWebAggCore): def new_timer(self, *args, **kwargs): return TimerTornado(*args, **kwargs) @@ -197,6 +193,31 @@ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) +class FigureManagerNbAgg(FigureManagerWebAgg): + ToolbarCls = NavigationIPy + + def __init__(self, canvas, num): + FigureManagerWebAgg.__init__(self, canvas, num) + toolitems = [] + for name, tooltip, image, method in self.ToolbarCls.toolitems: + if name is None: + toolitems.append(['', '', '', '']) + else: + toolitems.append([name, tooltip, image, method]) + canvas._toolbar_items = toolitems + self.web_sockets = [self.canvas] + + def show(self): + if self.canvas._closed: + self.canvas._closed = False + display(self.canvas) + else: + self.canvas.draw_idle() + + def destroy(self): + self._send_event('close') + + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance @@ -229,76 +250,46 @@ def closer(event): return manager -class CommSocket(object): +def nbinstall(overwrite=False, user=True): """ - Manages the Comm connection between IPython and the browser (client). - - Comms are 2 way, with the CommSocket being able to publish a message - via the send_json method, and handle a message with on_message. On the - JS side figure.send_message and figure.ws.onmessage do the sending and - receiving respectively. - + Copies javascript dependencies to the '/nbextensions' folder in + your IPython directory. + + Parameters + ---------- + + overwrite : bool + If True, always install the files, regardless of what mayƓ already be + installed. Defaults to False. + user : bool + Whether to install to the user's .ipython/nbextensions directory. + Otherwise do a system-wide install + (e.g. /usr/local/share/jupyter/nbextensions). Defaults to False. """ - def __init__(self, manager): - self.supports_binary = None - self.manager = manager - self.uuid = str(uuid()) - # Publish an output area with a unique ID. The javascript can then - # hook into this area. - display(HTML("
" % self.uuid)) - try: - self.comm = Comm('matplotlib', data={'id': self.uuid}) - except AttributeError: - raise RuntimeError('Unable to create an IPython notebook Comm ' - 'instance. Are you in the IPython notebook?') - self.comm.on_msg(self.on_message) - - manager = self.manager - self._ext_close = False - - def _on_close(close_message): - self._ext_close = True - manager.remove_comm(close_message['content']['comm_id']) - manager.clearup_closed() - - self.comm.on_close(_on_close) - - def is_open(self): - return not (self._ext_close or self.comm._closed) - - def on_close(self): - # When the socket is closed, deregister the websocket with - # the FigureManager. - if self.is_open(): - try: - self.comm.close() - except KeyError: - # apparently already cleaned it up? - pass - - def send_json(self, content): - self.comm.send({'data': json.dumps(content)}) - - def send_binary(self, blob): - # The comm is ascii, so we always send the image in base64 - # encoded data URL form. - data = b64encode(blob) - if six.PY3: - data = data.decode('ascii') - data_uri = "data:image/png;base64,{0}".format(data) - self.comm.send({'data': data_uri}) - - def on_message(self, message): - # The 'supports_binary' message is relevant to the - # websocket itself. The other messages get passed along - # to matplotlib as-is. - - # Every message has a "type" and a "figure_id". - message = json.loads(message['content']['data']) - if message['type'] == 'closing': - self.on_close() - self.manager.clearup_closed() - elif message['type'] == 'supports_binary': - self.supports_binary = message['value'] - else: - self.manager.handle_json(message) + if (check_nbextension('matplotlib') or + check_nbextension('matplotlib', True)): + return + + # Make a temporary directory so we can wrap mpl.js in a requirejs define(). + tempdir = mkdtemp() + path = os.path.join(os.path.dirname(__file__), "web_backend") + shutil.copy2(os.path.join(path, "nbagg_mpl.js"), tempdir) + + with open(os.path.join(path, 'mpl.js')) as fid: + contents = fid.read() + + with open(os.path.join(tempdir, 'mpl.js'), 'w') as fid: + fid.write('define(["jquery"], function($) {\n') + fid.write(contents) + fid.write('\nreturn mpl;\n});') + + install_nbextension( + tempdir, + overwrite=overwrite, + symlink=False, + destination='matplotlib', + verbose=0, + **({'user': user} if version_info >= (3, 0, 0, '') else {}) + ) + +#nbinstall() diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 8dba7c3e8647..9f93b2bb1bc8 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -18,7 +18,7 @@ import io import json import os -import time +import datetime import warnings import numpy as np @@ -501,6 +501,7 @@ def get_javascript(cls, stream=None): with io.open(os.path.join( os.path.dirname(__file__), "web_backend", + "js", "mpl.js"), encoding='utf8') as fd: output.write(fd.read()) @@ -530,7 +531,7 @@ def get_javascript(cls, stream=None): @classmethod def get_static_file_path(cls): - return os.path.join(os.path.dirname(__file__), 'web_backend') + return os.path.join(os.path.dirname(__file__), 'web_backend', 'js') def _send_event(self, event_type, **kwargs): payload = {'type': event_type} diff --git a/lib/matplotlib/backends/web_backend/js/extension.js b/lib/matplotlib/backends/web_backend/js/extension.js new file mode 100644 index 000000000000..be7ea701550c --- /dev/null +++ b/lib/matplotlib/backends/web_backend/js/extension.js @@ -0,0 +1,18 @@ + +define([], function() { + if (window.require) { + window.require.config({ + map: { + "*" : { + "matplotlib": "nbextensions/matplotlib/nbagg_mpl", + "jupyter-js-widgets": "nbextensions/jupyter-js-widgets/extension" + } + } + }); + } + + // Export the required load_ipython_extention + return { + load_ipython_extension: function() {} + }; +}); diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js similarity index 96% rename from lib/matplotlib/backends/web_backend/mpl.js rename to lib/matplotlib/backends/web_backend/js/mpl.js index cecebd8e0201..d9949453ea5c 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -1,4 +1,16 @@ /* Put everything inside the global mpl namespace */ + +// Universal Module Definition +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD + define(['jquery'], factory); + } else { + // Browser globals (root is window) + root.returnExports = factory(root.jQuery); + } +}(this, function ($) { + window.mpl = {}; @@ -133,11 +145,11 @@ mpl.figure.prototype._init_canvas = function() { this.context = canvas[0].getContext("2d"); var backingStore = this.context.backingStorePixelRatio || - this.context.webkitBackingStorePixelRatio || - this.context.mozBackingStorePixelRatio || - this.context.msBackingStorePixelRatio || - this.context.oBackingStorePixelRatio || - this.context.backingStorePixelRatio || 1; + this.context.webkitBackingStorePixelRatio || + this.context.mozBackingStorePixelRatio || + this.context.msBackingStorePixelRatio || + this.context.oBackingStorePixelRatio || + this.context.backingStorePixelRatio || 1; mpl.ratio = (window.devicePixelRatio || 1) / backingStore; @@ -552,3 +564,7 @@ mpl.figure.prototype.toolbar_button_onclick = function(name) { mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) { this.message.textContent = tooltip; }; + +return mpl; + +})); diff --git a/lib/matplotlib/backends/web_backend/mpl_tornado.js b/lib/matplotlib/backends/web_backend/js/mpl_tornado.js similarity index 100% rename from lib/matplotlib/backends/web_backend/mpl_tornado.js rename to lib/matplotlib/backends/web_backend/js/mpl_tornado.js diff --git a/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js new file mode 100644 index 000000000000..0718434c75e8 --- /dev/null +++ b/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js @@ -0,0 +1,167 @@ + + +define(['jupyter-js-widgets', '/nbextensions/matplotlib/mpl.js'], function(widgets, mpl) { + + var MPLCanvasView = widgets.WidgetView.extend({ + + render: function() { + var that = this; + + var id = this.model.get('_id'); + + var element = this.$el; + + this.ws_proxy = this.comm_websocket_adapter(this.model.comm); + + function ondownload(figure, format) { + window.open(figure.imageObj.src); + } + + mpl.toolbar_items = this.model.get('_toolbar_items') + + var fig = new mpl.figure(id, this.ws_proxy, + ondownload, + element.get(0)); + + // 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. + this.ws_proxy.onopen(); + + fig.parent_element = element.get(0); + + // subscribe to incoming messages from the MPLCanvasWidget + this.model.on('msg:custom', this.ws_proxy.onmessage, this); + + this.send(JSON.stringify({ type: 'initialized' })); + }, + + 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 = {}; + var that = this; + + ws.close = function() { + comm.close() + }; + ws.send = function(m) { + that.send(m); + }; + return ws; + } + + }); + + mpl.figure.prototype.handle_close = function(fig, msg) { + var width = fig.canvas.width/mpl.ratio + fig.root.unbind('remove') + + // Re-enable the keyboard manager in IPython - without this line, in FF, + // the notebook keyboard shortcuts fail. + IPython.keyboard_manager.enable() + fig.close_ws(fig, msg); + } + + mpl.figure.prototype.close_ws = function(fig, msg){ + fig.send_message('closing', msg); + // fig.ws.close() + } + + mpl.figure.prototype.updated_canvas_event = function() { + // Tell IPython that the notebook contents must change. + IPython.notebook.set_dirty(true); + this.send_message("ack", {}); + } + + mpl.figure.prototype._init_toolbar = function() { + var fig = this; + + var nav_element = $('
') + nav_element.attr('style', 'width: 100%'); + this.root.append(nav_element); + + // 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']); + } + + 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) { continue; }; + + var button = $(''); + button.click(method_name, toolbar_event); + button.mouseover(tooltip, toolbar_mouse_event); + nav_element.append(button); + } + + // Add the status bar. + var status_bar = $(''); + nav_element.append(status_bar); + this.message = status_bar[0]; + + // Add the close button to the window. + var buttongrp = $('
'); + var button = $(''); + button.click(function (evt) { fig.handle_close(fig, {}); } ); + button.mouseover('Stop Interaction', toolbar_mouse_event); + buttongrp.append(button); + var titlebar = this.root.find($('.ui-dialog-titlebar')); + titlebar.prepend(buttongrp); + } + + mpl.figure.prototype._root_extra_style = function(el){ + var fig = this + el.on("remove", function(){ + fig.close_ws(fig, {}); + }); + } + + mpl.figure.prototype._canvas_extra_style = function(el){ + // this is important to make the div 'focusable + el.attr('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) { + var manager = IPython.notebook.keyboard_manager; + if (!manager) + manager = IPython.keyboard_manager; + + // Check for shift+enter + if (event.shiftKey && event.which == 13) { + this.canvas_div.blur(); + event.shiftKey = false; + // Send a "J" for go to next cell + event.which = 74; + event.keyCode = 74; + manager.command_mode(); + manager.handle_keydown(event); + } + } + + mpl.figure.prototype.handle_save = function(fig, msg) { + fig.ondownload(fig, null); + } + + return {MPLCanvasView: MPLCanvasView} +}); diff --git a/lib/matplotlib/backends/web_backend/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/nbagg_mpl.js deleted file mode 100644 index 9471f5340d51..000000000000 --- a/lib/matplotlib/backends/web_backend/nbagg_mpl.js +++ /dev/null @@ -1,211 +0,0 @@ -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.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); - // Pass the mpl event to the overriden (by mpl) onmessage function. - ws.onmessage(msg['content']['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 = $("#" + id); - var ws_proxy = comm_websocket_adapter(comm) - - function ondownload(figure, format) { - window.open(figure.imageObj.src); - } - - var fig = new mpl.figure(id, ws_proxy, - ondownload, - element.get(0)); - - // 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.get(0); - fig.cell_info = mpl.find_output_cell("
"); - if (!fig.cell_info) { - console.error("Failed to find cell for figure", id, fig); - return; - } - - var output_index = fig.cell_info[2] - var cell = fig.cell_info[0]; - -}; - -mpl.figure.prototype.handle_close = function(fig, msg) { - var width = fig.canvas.width/mpl.ratio - fig.root.unbind('remove') - - // 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).html(''); - 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/mpl.ratio - var dataURL = this.canvas.toDataURL(); - this.cell_info[1]['text/html'] = ''; -} - -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 nav_element = $('
') - nav_element.attr('style', 'width: 100%'); - this.root.append(nav_element); - - // 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']); - } - - 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) { continue; }; - - var button = $(''); - button.click(method_name, toolbar_event); - button.mouseover(tooltip, toolbar_mouse_event); - nav_element.append(button); - } - - // Add the status bar. - var status_bar = $(''); - nav_element.append(status_bar); - this.message = status_bar[0]; - - // Add the close button to the window. - var buttongrp = $('
'); - var button = $(''); - button.click(function (evt) { fig.handle_close(fig, {}); } ); - button.mouseover('Stop Interaction', toolbar_mouse_event); - buttongrp.append(button); - var titlebar = this.root.find($('.ui-dialog-titlebar')); - titlebar.prepend(buttongrp); -} - -mpl.figure.prototype._root_extra_style = function(el){ - var fig = this - el.on("remove", function(){ - fig.close_ws(fig, {}); - }); -} - -mpl.figure.prototype._canvas_extra_style = function(el){ - // this is important to make the div 'focusable - el.attr('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) { - var manager = IPython.notebook.keyboard_manager; - if (!manager) - manager = IPython.keyboard_manager; - - // Check for shift+enter - if (event.shiftKey && event.which == 13) { - this.canvas_div.blur(); - event.shiftKey = false; - // Send a "J" for go to next cell - event.which = 74; - event.keyCode = 74; - manager.command_mode(); - manager.handle_keydown(event); - } -} - -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= 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); -} diff --git a/setup.py b/setup.py index 2570f157152b..754639ad5ec5 100644 --- a/setup.py +++ b/setup.py @@ -290,6 +290,14 @@ def run(self): ext_modules=ext_modules, package_dir=package_dir, package_data=package_data, + include_package_data=True, + data_files=[ + ('share/jupyter/nbextensions/matplotlib', [ + 'lib/matplotlib/backends/web_backend/js/extension.js', + 'lib/matplotlib/backends/web_backend/js/nbagg_mpl.js', + 'lib/matplotlib/backends/web_backend/js/mpl.js', + ]), + ], classifiers=classifiers, download_url="http://matplotlib.org/users/installing.html", diff --git a/setupext.py b/setupext.py index 6e6705e8bd33..95644c79bb28 100755 --- a/setupext.py +++ b/setupext.py @@ -676,6 +676,7 @@ def get_package_data(self): 'backends/web_backend/jquery/css/themes/base/*.min.css', 'backends/web_backend/jquery/css/themes/base/images/*', 'backends/web_backend/css/*.*', + 'backends/web_backend/js/*.js', 'backends/Matplotlib.nib/*', 'mpl-data/stylelib/*.mplstyle', ]}