mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
### What is this PR for? https://github.com/apache/zeppelin/pull/2106 rewrote python interpreter. But dynamic form feature is not rewritten correctly. ### What type of PR is it? Hot Fix ### Todos * [x] - Bring dynamic form back ### What is the Jira issue? https://github.com/apache/zeppelin/pull/2106 ### How should this be tested? run ``` %python print("Hello "+z.input("name", "sun")) ``` ``` %python print("Hello "+z.select("day", [("1","mon"), ("2","tue"), ("3","wed"), ("4","thurs"), ("5","fri"), ("6","sat"), ("7","sun")])) ``` ``` %python options = [("apple","Apple"), ("banana","Banana"), ("orange","Orange")] print("Hello "+ " and ".join(z.checkbox("fruit", options, ["apple"]))) ``` ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no Author: Lee moon soo <moon@apache.org> Closes #2155 from Leemoonsoo/python_get_interpreter_context and squashes the following commits:c5e584a[Lee moon soo] fix matplotlib display error on python 3.43e6603b[Lee moon soo] correctly handle zeppelin.python property.5be8db4[Lee moon soo] Expose a method to get InterpreterOutput, so user can call InterpreterOutput.clear()a405a93[Lee moon soo] implement dynamic form
317 lines
12 KiB
Python
317 lines
12 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
# (the "License"); you may not use this file except in compliance with
|
|
# the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# This file provides a static (non-interactive) matplotlib plotting backend
|
|
# for zeppelin notebooks for use with the python/pyspark interpreters
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
import uuid
|
|
import warnings
|
|
import base64
|
|
from io import BytesIO
|
|
try:
|
|
from StringIO import StringIO
|
|
except ImportError:
|
|
from io import StringIO
|
|
|
|
import mpl_config
|
|
import matplotlib
|
|
from matplotlib._pylab_helpers import Gcf
|
|
from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg
|
|
from matplotlib.backend_bases import ShowBase, FigureManagerBase
|
|
from matplotlib.figure import Figure
|
|
|
|
########################################################################
|
|
#
|
|
# The following functions and classes are for pylab and implement
|
|
# window/figure managers, etc...
|
|
#
|
|
########################################################################
|
|
|
|
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')
|
|
try:
|
|
managers = Gcf.get_all_fig_managers()
|
|
if not managers:
|
|
return
|
|
|
|
# Tell zeppelin that the output will be html using the %html magic
|
|
# We want to do this only once to avoid seeing "%html" printed
|
|
# directly to the outout when multiple figures are displayed from
|
|
# one paragraph.
|
|
if mpl_config.get('angular'):
|
|
print('%angular')
|
|
else:
|
|
print('%html')
|
|
|
|
# Show all open figures
|
|
for manager in managers:
|
|
manager.show(**kwargs)
|
|
finally:
|
|
# This closes all the figures if close is set to True.
|
|
if close and Gcf.get_all_fig_managers():
|
|
Gcf.destroy_all()
|
|
|
|
|
|
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)
|
|
fmt = fmt.encode()
|
|
if sys.version_info >= (3, 4) and sys.version_info < (3, 5):
|
|
byte_str = bytes("data:image/%s;base64," %fmt, "utf-8")
|
|
else:
|
|
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).
|
|
This is overriden to allow open figures to be reshown after they
|
|
are updated when mpl_config.get('close') is False.
|
|
"""
|
|
if not self._is_idle_drawing:
|
|
with self._idle_draw_cntx():
|
|
self.draw(*args, **kwargs)
|
|
draw_if_interactive()
|
|
|
|
|
|
class FigureManagerZInline(FigureManagerBase):
|
|
"""
|
|
Wrap everything up into a window for the pylab interface
|
|
"""
|
|
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
|
|
|
|
|
|
def draw_if_interactive():
|
|
"""
|
|
If interactive mode is on, this allows for updating properties of
|
|
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. 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
|
|
# this function call implies that it has been updated
|
|
if not mpl_config.get('close'):
|
|
manager._shown = False
|
|
|
|
|
|
def new_figure_manager(num, *args, **kwargs):
|
|
"""
|
|
Create a new figure manager instance
|
|
"""
|
|
# if a main-level app must be created, this (and
|
|
# new_figure_manager_given_figure) is the usual place to
|
|
# do it -- see backend_wx, backend_wxagg and backend_tkagg for
|
|
# examples. Not all GUIs require explicit instantiation of a
|
|
# main-level app (egg backend_gtk, backend_gtkagg) for pylab
|
|
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 = FigureCanvasZInline(figure)
|
|
manager = FigureManagerZInline(canvas, num)
|
|
return manager
|
|
|
|
|
|
########################################################################
|
|
#
|
|
# Backend specific functions
|
|
#
|
|
########################################################################
|
|
|
|
def zdisplay(fig, **kwargs):
|
|
"""
|
|
Publishes a matplotlib figure to the notebook paragraph output.
|
|
"""
|
|
# kwargs can be width or height (in units supported by div tag)
|
|
width = kwargs.pop('width', 'auto')
|
|
height = kwargs.pop('height', 'auto')
|
|
fmt = kwargs.get('format', mpl_config.get('format'))
|
|
|
|
# Check if format is supported
|
|
supported_formats = mpl_config.get('supported_formats')
|
|
if fmt not in supported_formats:
|
|
raise ValueError("Unsupported format %s" %fmt)
|
|
|
|
# For SVG the data string has to be unicode, not bytes
|
|
if fmt == 'svg':
|
|
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...
|
|
width = '{}px'.format(mpl_config.get('width'))
|
|
height = '{}px'.format(mpl_config.get('height'))
|
|
else:
|
|
# Express the image as bytes
|
|
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))
|
|
|
|
def displayhook():
|
|
"""
|
|
Called post paragraph execution if interactive mode is on
|
|
"""
|
|
if matplotlib.is_interactive():
|
|
show()
|
|
|
|
########################################################################
|
|
#
|
|
# Now just provide the standard names that backend.__init__ is expecting
|
|
#
|
|
########################################################################
|
|
|
|
# Create a reference to the show function we are using. This is what actually
|
|
# gets called by matplotlib.pyplot.show().
|
|
show = Show()
|
|
|
|
# Default FigureCanvas and FigureManager classes to use from the backend
|
|
FigureCanvas = FigureCanvasZInline
|
|
FigureManager = FigureManagerZInline
|