mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Added support for Angular Display System
This commit is contained in:
parent
edf750af31
commit
f9c9498308
4 changed files with 131 additions and 27 deletions
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
import uuid
|
||||
import warnings
|
||||
import base64
|
||||
from io import BytesIO
|
||||
try:
|
||||
|
|
@ -40,13 +42,13 @@ from matplotlib.figure import Figure
|
|||
########################################################################
|
||||
|
||||
class Show(ShowBase):
|
||||
'''
|
||||
"""
|
||||
A callable object that displays the figures to the screen. Valid kwargs
|
||||
include figure width and height (in units supported by the div tag), block
|
||||
(allows users to override blocking behavior regardless of whether or not
|
||||
interactive mode is enabled, currently unused) and close (Implicitly call
|
||||
matplotlib.pyplot.close('all') with each call to show()).
|
||||
'''
|
||||
"""
|
||||
def __call__(self, close=None, block=None, **kwargs):
|
||||
if close is None:
|
||||
close = mpl_config.get('close')
|
||||
|
|
@ -59,7 +61,10 @@ class Show(ShowBase):
|
|||
# We want to do this only once to avoid seeing "%html" printed
|
||||
# directly to the outout when multiple figures are displayed from
|
||||
# one paragraph.
|
||||
print("%html")
|
||||
if mpl_config.get('angular'):
|
||||
print('%angular')
|
||||
else:
|
||||
print('%html')
|
||||
|
||||
# Show all open figures
|
||||
for manager in managers:
|
||||
|
|
@ -75,6 +80,46 @@ class FigureCanvasZInline(FigureCanvasAgg):
|
|||
The canvas the figure renders into. Calls the draw and print fig
|
||||
methods, creates the renderers, etc...
|
||||
"""
|
||||
def get_bytes(self, **kwargs):
|
||||
"""
|
||||
Get the byte representation of the figure.
|
||||
Should only be used with jpg/png formats.
|
||||
"""
|
||||
# Make sure format is correct
|
||||
fmt = kwargs.get('format', mpl_config.get('format'))
|
||||
if fmt == 'svg':
|
||||
raise ValueError("get_bytes() does not support svg, use png or jpg")
|
||||
|
||||
# Express the image as bytes
|
||||
buf = BytesIO()
|
||||
self.print_figure(buf, **kwargs)
|
||||
byte_str = b"data:image/%s;base64," %fmt
|
||||
byte_str += base64.b64encode(buf.getvalue())
|
||||
|
||||
# Python3 forces all strings to default to unicode, but for raster image
|
||||
# formats (eg png, jpg), we want to work with bytes. Thus this step is
|
||||
# needed to ensure compatability for all python versions.
|
||||
byte_str = byte_str.decode('ascii')
|
||||
buf.close()
|
||||
return byte_str
|
||||
|
||||
def get_svg(self, **kwargs):
|
||||
"""
|
||||
Get the svg representation of the figure.
|
||||
Should only be used with svg format.
|
||||
"""
|
||||
# Make sure format is correct
|
||||
fmt = kwargs.get('format', mpl_config.get('format'))
|
||||
if fmt != 'svg':
|
||||
raise ValueError("get_svg() does not support png or jpg, use svg")
|
||||
|
||||
# For SVG the data string has to be unicode, not bytes
|
||||
buf = StringIO()
|
||||
self.print_figure(buf, **kwargs)
|
||||
svg_str = buf.getvalue()
|
||||
buf.close()
|
||||
return svg_str
|
||||
|
||||
def draw_idle(self, *args, **kwargs):
|
||||
"""
|
||||
Called when the figure gets updated (eg through a plotting command).
|
||||
|
|
@ -93,13 +138,72 @@ class FigureManagerZInline(FigureManagerBase):
|
|||
"""
|
||||
def __init__(self, canvas, num):
|
||||
FigureManagerBase.__init__(self, canvas, num)
|
||||
self.fig_id = "figure_{0}".format(uuid.uuid4().hex)
|
||||
self._shown = False
|
||||
|
||||
def angular_bind(self, **kwargs):
|
||||
"""
|
||||
Bind figure data to Zeppelin's Angular Object Registry.
|
||||
If mpl_config("angular") is True and PY4J is supported, this allows
|
||||
for the possibility to interactively update a figure from a separate
|
||||
paragraph without having to display it multiple times.
|
||||
"""
|
||||
# This doesn't work for SVG so make sure it's not our format
|
||||
fmt = kwargs.get('format', mpl_config.get('format'))
|
||||
if fmt == 'svg':
|
||||
return
|
||||
|
||||
# Get the figure data as a byte array
|
||||
src = self.canvas.get_bytes(**kwargs)
|
||||
|
||||
# Flag to determine whether or not to use
|
||||
# zeppelin's angular display system
|
||||
angular = mpl_config.get('angular')
|
||||
|
||||
# ZeppelinContext instance (requires PY4J)
|
||||
context = mpl_config.get('context')
|
||||
|
||||
# Finally we must ensure that automatic closing is set to False,
|
||||
# as otherwise using the angular display system is pointless
|
||||
close = mpl_config.get('close')
|
||||
|
||||
# If above conditions are met, bind the figure data to
|
||||
# the Angular Object Registry.
|
||||
if not close and angular:
|
||||
if hasattr(context, 'angularBind'):
|
||||
# Binding is performed through figure ID to ensure this works
|
||||
# if multiple figures are open
|
||||
context.angularBind(self.fig_id, src)
|
||||
|
||||
# Zeppelin will automatically replace this value even if it
|
||||
# is updated from another pargraph thanks to the {{}} notation
|
||||
src = "{{%s}}" %self.fig_id
|
||||
else:
|
||||
warnings.warn("Cannot bind figure to Angular Object Registry. "
|
||||
"Check if PY4J is installed.")
|
||||
return src
|
||||
|
||||
def angular_unbind(self):
|
||||
"""
|
||||
Unbind figure from angular display system.
|
||||
"""
|
||||
context = mpl_config.get('context')
|
||||
if hasattr(context, 'angularUnbind'):
|
||||
context.angularUnbind(self.fig_id)
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
Called when close=True or implicitly by pyplot.close().
|
||||
Overriden to automatically clean up the angular object registry.
|
||||
"""
|
||||
self.angular_unbind()
|
||||
|
||||
def show(self, **kwargs):
|
||||
if not self._shown:
|
||||
zdisplay(self.canvas.figure, **kwargs)
|
||||
else:
|
||||
self.canvas.draw_idle()
|
||||
self.angular_bind(**kwargs)
|
||||
|
||||
self._shown = True
|
||||
|
||||
|
|
@ -110,10 +214,13 @@ def draw_if_interactive():
|
|||
the figure when each new plotting command is called.
|
||||
"""
|
||||
manager = Gcf.get_active()
|
||||
interactive = matplotlib.is_interactive()
|
||||
angular = mpl_config.get('angular')
|
||||
|
||||
# Don't bother continuing if we aren't in interactive mode
|
||||
# or if there are no active figures
|
||||
if not matplotlib.is_interactive() or manager is None:
|
||||
# or if there are no active figures. Also pointless to continue
|
||||
# in angular mode as we don't want to reshow the figure.
|
||||
if not interactive or angular or manager is None:
|
||||
return
|
||||
|
||||
# Allow for figure to be reshown if close is false since
|
||||
|
|
@ -167,9 +274,7 @@ def zdisplay(fig, **kwargs):
|
|||
|
||||
# For SVG the data string has to be unicode, not bytes
|
||||
if fmt == 'svg':
|
||||
buf = StringIO()
|
||||
fig.canvas.print_figure(buf, **kwargs)
|
||||
img_str = buf.getvalue()
|
||||
img = fig.canvas.get_svg(**kwargs)
|
||||
|
||||
# This is needed to ensure the SVG image is the correct size.
|
||||
# We should find a better way to do this...
|
||||
|
|
@ -177,22 +282,14 @@ def zdisplay(fig, **kwargs):
|
|||
height = '{}px'.format(mpl_config.get('height'))
|
||||
else:
|
||||
# Express the image as bytes
|
||||
buf = BytesIO()
|
||||
fig.canvas.print_figure(buf, **kwargs)
|
||||
img_str = b"data:image/%s;base64," %fmt
|
||||
img_str += base64.b64encode(buf.getvalue())
|
||||
img_tag = "<img src={img} style='width={width};height:{height}'>"
|
||||
|
||||
# Python3 forces all strings to default to unicode, but for raster image
|
||||
# formats (eg png, jpg), we want to work with bytes. Thus this step is
|
||||
# needed to ensure compatability for all python versions.
|
||||
img_str = img_str.decode("ascii")
|
||||
img_str = img_tag.format(img=img_str, width=width, height=height)
|
||||
fig_id = fig.canvas.manager.num
|
||||
src = fig.canvas.manager.angular_bind(**kwargs)
|
||||
img = "<img src={src} style='width={width};height:{height}'>"
|
||||
img = img.format(src=src, width=width, height=height)
|
||||
|
||||
# Print the image to the notebook paragraph via the %html magic
|
||||
html = "<div style='width:{width};height:{height}'>{img}<div>"
|
||||
print(html.format(width=width, height=height, img=img_str))
|
||||
buf.close()
|
||||
print(html.format(width=width, height=height, img=img))
|
||||
|
||||
def displayhook():
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -23,15 +23,16 @@ def configure(**kwargs):
|
|||
Generic configure function.
|
||||
Usage: configure(prop1='foo', prop2='bar', ...)
|
||||
Currently supported zeppelin-specific properties are:
|
||||
interactive - If true show all figures without explicit call to show()
|
||||
via a post-execute hook.
|
||||
angular - If true, bind figures to angular display system.
|
||||
close - If true, close all figures once shown.
|
||||
width, height - Default width / height of the figure in pixels.
|
||||
fontsize - Font size.
|
||||
dpi - dpi of the figure.
|
||||
fmt - Figure format
|
||||
supported_formats - Supported Figure formats ()
|
||||
interactive - If true show all figures without explicit call to show()
|
||||
via a post-execute hook.
|
||||
|
||||
context - ZeppelinContext instance (requires PY4J)
|
||||
"""
|
||||
_config.update(**kwargs)
|
||||
|
||||
|
|
@ -85,7 +86,9 @@ def _init_config():
|
|||
_config['fontsize'] = fontsize
|
||||
_config['close'] = True
|
||||
_config['interactive'] = matplotlib.is_interactive()
|
||||
_config['angular'] = False
|
||||
_config['supported_formats'] = ['png', 'jpg', 'svg']
|
||||
_config['context'] = None
|
||||
|
||||
|
||||
_config = {}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import os
|
|||
import sys
|
||||
import signal
|
||||
import base64
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
|
|
@ -218,6 +219,8 @@ class PyZeppelinContext(object):
|
|||
except ImportError:
|
||||
# Fall back to Agg if no custom backend installed
|
||||
matplotlib.use('Agg')
|
||||
warnings.warn("Unable to load inline matplotlib backend, "
|
||||
"falling back to Agg")
|
||||
|
||||
|
||||
z = PyZeppelinContext()
|
||||
|
|
|
|||
|
|
@ -138,12 +138,13 @@ class PyZeppelinContext(dict):
|
|||
# Everything looks good so make config assuming that we are using
|
||||
# an inline backend
|
||||
self._displayhook = backend_zinline.displayhook
|
||||
self.configure_mpl(width=600, height=400, dpi=72,
|
||||
fontsize=10, interactive=True, format='png')
|
||||
self.configure_mpl(width=600, height=400, dpi=72, fontsize=10,
|
||||
interactive=True, format='png', context=self.z)
|
||||
except ImportError:
|
||||
# Fall back to Agg if no custom backend installed
|
||||
matplotlib.use('Agg')
|
||||
return
|
||||
warnings.warn("Unable to load inline matplotlib backend, "
|
||||
"falling back to Agg")
|
||||
|
||||
def configure_mpl(self, **kwargs):
|
||||
import mpl_config
|
||||
|
|
|
|||
Loading…
Reference in a new issue