From af15fdde6cee6ac9c57d9e18269b87212522a479 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 1 May 2020 16:20:53 +0200 Subject: [PATCH 1/6] Ensure callbacks work in ipywidgets mode --- holoviews/plotting/bokeh/callbacks.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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: From d03a587235f962a8ef1b81dfad2e357e171e9b54 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 1 May 2020 16:21:38 +0200 Subject: [PATCH 2/6] Implement rendering in ipywidgets/vscode modes --- holoviews/ipython/__init__.py | 8 +++++++ holoviews/plotting/renderer.py | 43 +++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 15ba2c5a7c..01334fb7e0 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -83,6 +83,11 @@ class notebook_extension(extension): logo = param.Boolean(default=True, doc="Toggles display of HoloViews logo") + comms = param.ObjectSelector( + default='default', objects=['default', 'ipywidgets', 'vscode'], doc=""" + Whether to render output in Jupyter with the default Jupyter + extension or use the jupyter_bokeh ipywidget model.""") + inline = param.Boolean(default=True, doc=""" Whether to inline JS and CSS resources. If disabled, resources are loaded from CDN if one is available.""") @@ -178,6 +183,9 @@ def __call__(self, *args, **params): resources = list(resources) if len(resources) == 0: return + from panel import config + config.comms = p.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/plotting/renderer.py b/holoviews/plotting/renderer.py index 23a6185880..af62f10ed6 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -22,7 +22,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 +375,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 +399,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) + if hasattr(widget, '_repr_mimebundle_'): + return widget._repr_mimebundle(include, exclude) + 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 From c29228e9865f3d7faa82d9b33ab0691fd8e058f2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 4 May 2020 11:28:06 +0200 Subject: [PATCH 3/6] Minor fixes --- holoviews/ipython/display_hooks.py | 2 +- holoviews/plotting/renderer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/renderer.py b/holoviews/plotting/renderer.py index af62f10ed6..3e9e9eef55 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -401,7 +401,7 @@ def components(self, obj, fmt=None, comm=True, **kwargs): return render_mimebundle(*args) # Handle rendering object as ipywidget - widget = ipywidget(plot) + widget = ipywidget(plot, combine_events=True) if hasattr(widget, '_repr_mimebundle_'): return widget._repr_mimebundle(include, exclude) plaintext = repr(widget) @@ -421,7 +421,7 @@ def components(self, obj, fmt=None, comm=True, **kwargs): from IPython.display import display display(data, raw=True) return {'text/html': '
'}, {} - return data + return data, {} else: html = self._figure_data(plot, fmt, as_script=True, **kwargs) data['text/html'] = html From b64b6c23c86b9612caede3f4ea881fba18368751 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 7 Jun 2020 21:16:27 +0200 Subject: [PATCH 4/6] Remove comms parameter --- holoviews/ipython/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 01334fb7e0..6b36e9abfb 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -83,11 +83,6 @@ class notebook_extension(extension): logo = param.Boolean(default=True, doc="Toggles display of HoloViews logo") - comms = param.ObjectSelector( - default='default', objects=['default', 'ipywidgets', 'vscode'], doc=""" - Whether to render output in Jupyter with the default Jupyter - extension or use the jupyter_bokeh ipywidget model.""") - inline = param.Boolean(default=True, doc=""" Whether to inline JS and CSS resources. If disabled, resources are loaded from CDN if one is available.""") @@ -117,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: @@ -184,7 +180,8 @@ def __call__(self, *args, **params): if len(resources) == 0: return from panel import config - config.comms = p.comms + 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) From 7afd60c05bef9067860946c8627a626381fb7e9d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 7 Jun 2020 21:39:27 +0200 Subject: [PATCH 5/6] Fixed flakes --- holoviews/plotting/renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index 3e9e9eef55..338f9cdde8 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -403,7 +403,7 @@ def components(self, obj, fmt=None, comm=True, **kwargs): # Handle rendering object as ipywidget widget = ipywidget(plot, combine_events=True) if hasattr(widget, '_repr_mimebundle_'): - return widget._repr_mimebundle(include, exclude) + return widget._repr_mimebundle() plaintext = repr(widget) if len(plaintext) > 110: plaintext = plaintext[:110] + '…' From 8a65b8c30cb82c8f4a51f5dc03026b8c66d24c59 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 7 Jun 2020 21:54:12 +0200 Subject: [PATCH 6/6] Fix utf-8 file encoding --- holoviews/plotting/renderer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index 338f9cdde8..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.