Skip to content
This repository has been archived by the owner on Nov 29, 2019. It is now read-only.

Add support for custom widget layouts #66

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
99 changes: 21 additions & 78 deletions parambokeh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@

from bokeh.document import Document
from bokeh.io import curdoc
from bokeh.layouts import row, column, widgetbox
from bokeh.layouts import widgetbox
from bokeh.models.widgets import Div, Button, CheckboxGroup, TextInput
from bokeh.models import CustomJS
from bokeh.protocol import Protocol

from pyviz_panels import Column, Row, View
from pyviz_panels.comms import JS_CALLBACK, JupyterCommManager
try:
from IPython.display import publish_display_data

import bokeh.embed.notebook
from bokeh.util.string import encode_utf8
from pyviz_comms import JupyterCommManager, JS_CALLBACK, bokeh_msg_handler, PYVIZ_PROXY
from ipykernel.comm import Comm as IPyComm
IPYTHON_AVAILABLE = True
except:
IPYTHON_AVAILABLE = False
Expand Down Expand Up @@ -50,50 +48,6 @@ def _err(): raise ValueError(_missing_cmd())
##


def notebook_show(obj, doc, comm):
"""
Displays bokeh output inside a notebook.
"""
target = obj.ref['id']
load_mime = 'application/vnd.holoviews_load.v0+json'
exec_mime = 'application/vnd.holoviews_exec.v0+json'

# Publish plot HTML
bokeh_script, bokeh_div, _ = bokeh.embed.notebook.notebook_content(obj, comm.id)
publish_display_data(data={'text/html': encode_utf8(bokeh_div)})

# Publish comm manager
JS = '\n'.join([PYVIZ_PROXY, JupyterCommManager.js_manager])
publish_display_data(data={load_mime: JS, 'application/javascript': JS})

# Publish bokeh plot JS
msg_handler = bokeh_msg_handler.format(plot_id=target)
comm_js = comm.js_template.format(plot_id=target, comm_id=comm.id, msg_handler=msg_handler)
bokeh_js = '\n'.join([comm_js, bokeh_script])

# Note: extension should be altered so text/html is not required
publish_display_data(data={exec_mime: '', 'text/html': '',
'application/javascript': bokeh_js},
metadata={exec_mime: {'id': target}})


def process_hv_plots(widgets, plots):
"""
Temporary fix to patch HoloViews plot comms
"""
bokeh_plots = []
for plot in plots:
if hasattr(plot, '_update_callbacks'):
for subplot in plot.traverse(lambda x: x):
subplot.comm = widgets.server_comm
for cb in subplot.callbacks:
for c in cb.callbacks:
c.code = c.code.replace(plot.id, widgets.plot_id)
plot = plot.state
bokeh_plots.append(plot)
return bokeh_plots


class default_label_formatter(param.ParameterizedFunction):
"Default formatter to turn parameter names into appropriate widget labels."

Expand Down Expand Up @@ -203,47 +157,45 @@ def __call__(self, parameterized, doc=None, plots=[], **params):
if not IPYTHON_AVAILABLE:
raise ImportError('IPython is not available, cannot use '
'Widgets in notebook mode.')
self.comm = JupyterCommManager.get_client_comm(on_msg=self.on_msg)
self.client_comm = JupyterCommManager.get_client_comm(on_msg=self.on_msg)
self.comm = JupyterCommManager.get_server_comm()

# HACK: Detects HoloViews plots and lets them handle the comms
hv_plots = [plot for plot in plots if hasattr(plot, 'comm')]
self.server_comm = JupyterCommManager.get_server_comm()
if hv_plots:
self.document = [p.document for p in hv_plots][0]
self.p.push = False
else:
self.document = doc or Document()
else:
self.document = doc or curdoc()
self.server_comm = None
self.client_comm = None
self.comm = None

self._queue = []
self._active = False
self._widget_options = {}
self.shown = False

# Initialize root container
widget_box = widgetbox(width=self.p.width)
view_params = any(isinstance(p, _View) for p in parameterized.params().values())
layout = self.p.view_position
container_type = column if layout in ['below', 'above'] else row
container = container_type() if plots or view_params else widget_box
self.plot_id = container.ref['id']
container_type = Column if layout in ['below', 'above'] else Row
self.container = container_type() if plots or view_params else View(widget_box)
self.plot_id = widget_box.ref['id']

# Initialize widgets and populate container
widgets, views = self.widgets()
plots = views + plots
widget_box.children = widgets

plots = process_hv_plots(self, plots)

if plots:
view_box = column(plots)
view_box = Column(*plots)
if layout in ['below', 'right']:
children = [widget_box, view_box]
else:
children = [view_box, widget_box]
container.children = children
self.container.children = children

# Initialize view parameters
for view in views:
Expand All @@ -259,17 +211,8 @@ def __call__(self, parameterized, doc=None, plots=[], **params):
if self.p.on_init:
self.execute()

if self.p.mode == 'raw':
return container
return self.container

self.document.add_root(container)
if self.p.mode == 'notebook':
notebook_show(container, self.document, self.server_comm)
if self.document._hold is None:
self.document.hold()
self.shown = True
return
return self.document


def on_msg(self, msg):
Expand Down Expand Up @@ -345,12 +288,12 @@ def _send_notebook_diff(self):
self.document._held_events = []
if msg is None:
return
self.server_comm.send(msg.header_json)
self.server_comm.send(msg.metadata_json)
self.server_comm.send(msg.content_json)
self.comm.send(msg.header_json)
self.comm.send(msg.metadata_json)
self.comm.send(msg.content_json)
for header, payload in msg.buffers:
self.server_comm.send(json.dumps(header))
self.server_comm.send(buffers=[payload])
self.comm.send(json.dumps(header))
self.comm.send(buffers=[payload])

def _update_trait(self, p_name, p_value, widget=None):
widget = self._widgets[p_name] if widget is None else widget
Expand All @@ -368,7 +311,7 @@ def _make_widget(self, p_name):
p_obj = self.parameterized.params(p_name)

if isinstance(p_obj, _View):
p_obj._comm = self.server_comm
p_obj._comm = self.comm
p_obj._document = self.document
p_obj._notebook = self.p.mode == 'notebook'

Expand Down Expand Up @@ -446,7 +389,7 @@ def _get_customjs(self, change, p_name):
"""
data_template = "data = {{p_name: '{p_name}', value: cb_obj['{change}']}};"
fetch_data = data_template.format(change=change, p_name=p_name)
self_callback = JS_CALLBACK.format(comm_id=self.comm.id,
self_callback = JS_CALLBACK.format(comm_id=self.client_comm.id,
timeout=self.timeout,
debounce=self.debounce,
plot_id=self.plot_id)
Expand Down