Skip to content

Nbagg backend #3008

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 28, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2514,7 +2514,7 @@ class NonGuiException(Exception):
pass


class FigureManagerBase:
class FigureManagerBase(object):
"""
Helper class for pyplot mode, wraps everything up into a neat bundle

Expand Down
206 changes: 206 additions & 0 deletions lib/matplotlib/backends/backend_nbagg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
"""Interactive figures in the IPython notebook"""
from base64 import b64encode
import json
import io
import os
from uuid import uuid4 as uuid

from IPython.display import display,Javascript,HTML
from IPython.kernel.comm import Comm

from matplotlib.figure import Figure
from matplotlib.backends.backend_webagg_core import (FigureManagerWebAgg,
FigureCanvasWebAggCore,
NavigationToolbar2WebAgg)
from matplotlib.backend_bases import ShowBase, NavigationToolbar2


class Show(ShowBase):
def display_js(self):
# XXX How to do this just once? It has to deal with multiple
# browser instances using the same kernel.
display(Javascript(FigureManagerNbAgg.get_javascript()))

def __call__(self, block=None):
from matplotlib import is_interactive
import matplotlib._pylab_helpers as pylab_helpers

queue = pylab_helpers.Gcf._activeQue
for manager in queue[:]:
if not manager.shown:
self.display_js()

manager.show()
# If we are not interactive, disable the figure from
# the active queue, but don't destroy it.
if not is_interactive():
queue.remove(manager)
manager.canvas.draw_idle()


show = Show()


def draw_if_interactive():
from matplotlib import is_interactive
import matplotlib._pylab_helpers as pylab_helpers

if is_interactive():
manager = pylab_helpers.Gcf.get_active()
if manager is not None:
if not manager.shown:
manager.show()
manager.canvas.draw_idle()


def connection_info():
"""
Return a string showing the figure and connection status for
the backend.

"""
# TODO: Make this useful!
import matplotlib._pylab_helpers as pylab_helpers
result = []
for manager in pylab_helpers.Gcf.get_all_fig_managers():
fig = manager.canvas.figure
result.append('{} - {}'.format(fig.get_label() or "Figure {0}".format(manager.num),
manager.web_sockets))
result.append('Figures pending show: ' + str(len(pylab_helpers.Gcf._activeQue)))
return '\n'.join(result)


class NavigationIPy(NavigationToolbar2WebAgg):
# Note: Version 3.2 icons, not the later 4.0 ones.
# http://fontawesome.io/3.2.1/icons/
_font_awesome_classes = {
'home': 'icon-home',
'back': 'icon-arrow-left',
'forward': 'icon-arrow-right',
'zoom_to_rect': 'icon-check-empty',
'move': 'icon-move',
None: None
}

# Use the standard toolbar items + download button
toolitems = [(text, tooltip_text, _font_awesome_classes[image_file], name_of_method)
for text, tooltip_text, image_file, name_of_method
in NavigationToolbar2.toolitems
if image_file in _font_awesome_classes]


class FigureManagerNbAgg(FigureManagerWebAgg):
ToolbarCls = NavigationIPy

def __init__(self, canvas, num):
self.shown = False
FigureManagerWebAgg.__init__(self, canvas, num)

def show(self):
if not self.shown:
self._create_comm()
self.shown = True

def reshow(self):
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()
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')
for comm in self.web_sockets.copy():
comm.on_close()


def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasWebAggCore(figure)
manager = FigureManagerNbAgg(canvas, num)
return manager


class CommSocket(object):
"""
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.

"""
def __init__(self, manager):
self.supports_binary = None
self.manager = manager
self.uuid = str(uuid())
display(HTML("<div id=%r></div>" % 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)

def on_close(self):
# When the socket is closed, deregister the websocket with
# the FigureManager.
if self.comm in self.manager.web_sockets:
self.manager.remove_web_socket(self)
self.comm.close()

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_uri = "data:image/png;base64,{0}".format(b64encode(blob))
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()
elif message['type'] == 'supports_binary':
self.supports_binary = message['value']
else:
self.manager.handle_json(message)
8 changes: 3 additions & 5 deletions lib/matplotlib/backends/backend_webagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,13 @@ class WebSocket(tornado.websocket.WebSocketHandler):

def open(self, fignum):
self.fignum = int(fignum)
manager = Gcf.get_fig_manager(self.fignum)
manager.add_web_socket(self)
self.manager = Gcf.get_fig_manager(self.fignum)
self.manager.add_web_socket(self)
if hasattr(self, 'set_nodelay'):
self.set_nodelay(True)

def on_close(self):
manager = Gcf.get_fig_manager(self.fignum)
if manager is not None:
manager.remove_web_socket(self)
self.manager.remove_web_socket(self)

def on_message(self, message):
message = json.loads(message)
Expand Down
Loading