Skip to content
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

Add ipywidgets and VSCode rendering support #4404

Merged
merged 6 commits into from
Jun 7, 2020
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
5 changes: 5 additions & 0 deletions holoviews/ipython/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class notebook_extension(extension):
_loaded = False

def __call__(self, *args, **params):
comms = params.pop('comms', None)
super(notebook_extension, self).__call__(*args, **params)
# Abort if IPython not found
try:
Expand Down Expand Up @@ -178,6 +179,10 @@ def __call__(self, *args, **params):
resources = list(resources)
if len(resources) == 0: return

from panel import config
if hasattr(config, 'comms') and comms:
config.comms = comms

for r in [r for r in resources if r != 'holoviews']:
Store.renderers[r].load_nb(inline=p.inline)
Renderer.load_nb()
Expand Down
2 changes: 1 addition & 1 deletion holoviews/ipython/display_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def wrapped(element):
# Only want to add to the archive for one display hook...
disabled_suffixes = ['png_display', 'svg_display']
if not any(fn.__name__.endswith(suffix) for suffix in disabled_suffixes):
if type(holoviews.archive) is not FileArchive:
if type(holoviews.archive) is not FileArchive and 'text/html' in mime_data:
holoviews.archive.add(element, html=mime_data['text/html'])
filename = OutputSettings.options['filename']
if filename:
Expand Down
20 changes: 11 additions & 9 deletions holoviews/plotting/bokeh/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ def _process_msg(self, msg):
def __init__(self, plot, streams, source, **params):
self.plot = plot
self.streams = streams
if plot.renderer.mode != 'server':
if plot.renderer.mode == 'server' or pn.config.comms != 'default':
self.comm = None
else:
if plot.pane:
on_error = partial(plot.pane._on_error, plot.root)
else:
on_error = None
self.comm = plot.renderer.comm_manager.get_client_comm(on_msg=self.on_msg)
self.comm._on_error = on_error
else:
self.comm = None
self.source = source
self.handle_ids = defaultdict(dict)
self.reset()
Expand Down Expand Up @@ -361,9 +361,7 @@ def resolve_attr_spec(cls, spec, cb_obj, model=None):
return {'id': model.ref['id'], 'value': resolved}

def _schedule_callback(self, cb, timeout=None, offset=True):
if timeout is not None:
pass
else:
if timeout is None:
if self._history and self.throttling_scheme == 'adaptive':
timeout = int(np.array(self._history).mean()*1000)
else:
Expand All @@ -372,7 +370,11 @@ def _schedule_callback(self, cb, timeout=None, offset=True):
# Subtract the time taken since event started
diff = time.time()-self._last_event
timeout = max(timeout-(diff*1000), 50)
pn.state.curdoc.add_timeout_callback(cb, int(timeout))
if not pn.state.curdoc:
from tornado.ioloop import IOLoop
IOLoop.current().call_later(int(timeout)/1000., cb)
else:
cb()

def on_change(self, attr, old, new):
"""
Expand Down Expand Up @@ -571,7 +573,7 @@ def initialize(self, plot_id=None):
cb.handle_ids[k].update(v)
continue

if self.plot.renderer.mode == 'server':
if self.comm is None:
self.set_server_callback(handle)
else:
js_callback = self.get_customjs(handles, plot_id=plot_id)
Expand Down Expand Up @@ -654,7 +656,7 @@ def _process_msg(self, msg):
if 'y' in msg and isinstance(yaxis, DatetimeAxis):
msg['y'] = convert_timestamp(msg['y'])

server_mode = self.plot.renderer.mode == 'server'
server_mode = self.comm is None
if isinstance(x_range, FactorRange) and isinstance(msg.get('x'), (int, float)):
msg['x'] = x_range.factors[int(msg['x'])]
elif 'x' in msg and isinstance(x_range, (Range1d, DataRange1d)) and server_mode:
Expand Down
44 changes: 36 additions & 8 deletions holoviews/plotting/renderer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
Public API for all plotting renderers supported by HoloViews,
regardless of plotting package or backend.
Expand All @@ -22,7 +23,7 @@
from bokeh.embed import file_html
from bokeh.resources import CDN, INLINE
from panel import config
from panel.io.notebook import load_notebook, render_model, render_mimebundle
from panel.io.notebook import ipywidget, load_notebook, render_model, render_mimebundle
from panel.io.state import state
from panel.pane import HoloViews as HoloViewsPane
from panel.widgets.player import PlayerBase
Expand Down Expand Up @@ -375,13 +376,17 @@ def components(self, obj, fmt=None, comm=True, **kwargs):
for src, streams in registry for s in streams
)
embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
comm = self.comm_manager.get_server_comm() if comm else None
doc = Document()
with config.set(embed=embed):
model = plot.layout._render_model(doc, comm)
if embed:
return render_model(model, comm)
else:

# This part should be factored out in Panel and then imported
# here for HoloViews 2.0, which will be able to require a
# recent Panel version.
if embed or config.comms == 'default':
comm = self.comm_manager.get_server_comm() if comm else None
doc = Document()
with config.set(embed=embed):
model = plot.layout._render_model(doc, comm)
if embed:
return render_model(model, comm)
args = (model, doc, comm)
if panel_version > '0.9.3':
from panel.models.comm_manager import CommManager
Expand All @@ -395,6 +400,29 @@ def components(self, obj, fmt=None, comm=True, **kwargs):
manager.client_comm_id = client_comm.id
args = args + (manager,)
return render_mimebundle(*args)

# Handle rendering object as ipywidget
widget = ipywidget(plot, combine_events=True)
if hasattr(widget, '_repr_mimebundle_'):
return widget._repr_mimebundle()
plaintext = repr(widget)
if len(plaintext) > 110:
plaintext = plaintext[:110] + '…'
data = {
'text/plain': plaintext,
}
if widget._view_name is not None:
data['application/vnd.jupyter.widget-view+json'] = {
'version_major': 2,
'version_minor': 0,
'model_id': widget._model_id
}
if config.comms == 'vscode':
# Unfortunately VSCode does not yet handle _repr_mimebundle_
from IPython.display import display
display(data, raw=True)
return {'text/html': '<div style="display: none"></div>'}, {}
return data, {}
else:
html = self._figure_data(plot, fmt, as_script=True, **kwargs)
data['text/html'] = html
Expand Down