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

Commit

Permalink
Refactored comms to add JupyterLab support (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens authored and philippjfr committed May 21, 2018
1 parent 8d94548 commit d1d1a47
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 281 deletions.
2 changes: 1 addition & 1 deletion dodo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# dependencies across projects.

def task_install_required_dependencies():
return {'actions': ['conda install -y -q -c conda-forge param "bokeh>=0.12.10"']}
return {'actions': ['conda install -y -q -c conda-forge -c pyviz param "bokeh>=0.12.10" pyviz_comms']}

def task_install_test_dependencies():
return {
Expand Down
5 changes: 2 additions & 3 deletions examples/user_guide/View_Parameters.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,12 @@
" \n",
" def event(self, **kwargs):\n",
" if not self.output or any(k in kwargs for k in ['color', 'element']):\n",
" self.output = hv.DynamicMap(self.view, streams=[self])\n",
" self.output = hv.DynamicMap(self.view, streams=[self], cache_size=0)\n",
" else:\n",
" super(CurveExample, self).event(**kwargs)\n",
"\n",
"example = CurveExample(name='HoloViews Example')\n",
"parambokeh.Widgets(example, callback=example.event, push=False,\n",
" on_init=True, view_position='right')"
"parambokeh.Widgets(example, callback=example.event, on_init=True, view_position='right')"
]
}
],
Expand Down
96 changes: 73 additions & 23 deletions parambokeh/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
from __future__ import absolute_import

import ast
import uuid
import itertools
import functools
import json

import param

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

try:
from .comms import JupyterCommJS, JS_CALLBACK, notebook_show
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
IPYTHON_AVAILABLE = True
except:
IPYTHON_AVAILABLE = False

from .widgets import wtype, literal_params
from .util import named_objs, get_method_owner
from .view import _View
Expand All @@ -29,6 +35,33 @@
__version__ = '0.2.1-unknown'


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}})


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

Expand All @@ -44,7 +77,6 @@ class default_label_formatter(param.ParameterizedFunction):
value is the desired label.""")

def __call__(self, pname):

if pname in self.overrides:
return self.overrides[pname]
if self.replace_underscores:
Expand Down Expand Up @@ -135,43 +167,49 @@ def __call__(self, parameterized, doc=None, plots=[], **params):
self._widgets = {}
self.parameterized = parameterized
self.document = None
self.comm_target = None
if self.p.mode == 'notebook':
if not IPYTHON_AVAILABLE:
raise ImportError('IPython is not available, cannot use '
'Widgets in notebook mode.')
self.comm = JupyterCommJS(on_msg=self.on_msg)
self.comm = JupyterCommManager.get_client_comm(on_msg=self.on_msg)
# 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.comm_target = [p.comm.id for p in hv_plots][0]
self.document = [p.document for p in hv_plots][0]
plots = [p.state for p in plots]
self.p.push = False
else:
self.comm_target = uuid.uuid4().hex
self.document = doc or Document()
else:
self.document = doc or curdoc()

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']

# Initialize widgets and populate container
widgets, views = self.widgets()
plots = views + plots
container = widgetbox(widgets, width=self.p.width)
widget_box.children = widgets
if plots:
view_box = column(plots)
layout = self.p.view_position
if layout in ['below', 'right']:
children = [container, view_box]
children = [widget_box, view_box]
else:
children = [view_box, container]
container_type = column if layout in ['below', 'above'] else row
container = container_type(children=children)
children = [view_box, widget_box]
container.children = children

# Initialize view parameters
for view in views:
p_obj = self.parameterized.params(view.name)
value = getattr(self.parameterized, view.name)
Expand All @@ -190,8 +228,7 @@ def __call__(self, parameterized, doc=None, plots=[], **params):

self.document.add_root(container)
if self.p.mode == 'notebook':
self.notebook_handle = notebook_show(container, self.document,
self.comm_target)
notebook_show(container, self.document, self.server_comm)
if self.document._hold is None:
self.document.hold()
self.shown = True
Expand Down Expand Up @@ -262,18 +299,29 @@ def change_event(self):

# document.hold() must have been done already? because this seems to work
if self.p.mode == 'notebook' and self.p.push and self.document._held_events:
push_notebook(handle=self.notebook_handle, document=self.document)
self._send_notebook_diff()
self._active = False


def _send_notebook_diff(self):
events = list(self.document._held_events)
msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=True)
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)
for header, payload in msg.buffers:
self.server_comm.send(json.dumps(header))
self.server_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
if isinstance(p_value, tuple):
p_value, size = p_value
if isinstance(widget, Div):
widget.text = p_value
elif self.p.mode == 'notebook' and self.shown:
return
else:
if widget.children:
widget.children.remove(widget.children[0])
Expand All @@ -284,7 +332,7 @@ def _make_widget(self, p_name):
p_obj = self.parameterized.params(p_name)

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

Expand Down Expand Up @@ -362,8 +410,10 @@ def _get_customjs(self, change, p_name):
fetch_data = data_template.format(change=change, p_name=p_name)
self_callback = JS_CALLBACK.format(comm_id=self.comm.id,
timeout=self.timeout,
debounce=self.debounce)
js_callback = CustomJS(code=fetch_data+self_callback)
debounce=self.debounce,
plot_id=self.plot_id)
js_callback = CustomJS(code='\n'.join([fetch_data,
self_callback]))
return js_callback


Expand Down
Loading

0 comments on commit d1d1a47

Please sign in to comment.