diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 15ba2c5a7c..6b36e9abfb 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -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: @@ -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() diff --git a/holoviews/ipython/display_hooks.py b/holoviews/ipython/display_hooks.py index 1da1098241..c4a1c6a85f 100644 --- a/holoviews/ipython/display_hooks.py +++ b/holoviews/ipython/display_hooks.py @@ -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: diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 157d348549..5f4ed53249 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -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() @@ -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: @@ -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): """ @@ -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) @@ -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: diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index 23a6185880..fef14b2ef6 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Public API for all plotting renderers supported by HoloViews, regardless of plotting package or backend. @@ -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 @@ -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 @@ -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': '
'}, {} + return data, {} else: html = self._figure_data(plot, fmt, as_script=True, **kwargs) data['text/html'] = html