diff --git a/holoviews/plotting/bokeh/renderer.py b/holoviews/plotting/bokeh/renderer.py index 4a443ffa7f..12f6439dcf 100644 --- a/holoviews/plotting/bokeh/renderer.py +++ b/holoviews/plotting/bokeh/renderer.py @@ -7,19 +7,20 @@ from bokeh.application import Application from bokeh.charts import Chart from bokeh.document import Document -from bokeh.embed import notebook_div -from bokeh.io import load_notebook, curdoc, show +from bokeh.embed import notebook_div, autoload_server +from bokeh.io import load_notebook, curdoc, show as bkshow from bokeh.models import (Row, Column, Plot, Model, ToolbarBox, WidgetBox, Div, DataTable, Tabs) from bokeh.plotting import Figure from bokeh.resources import CDN, INLINE +from bokeh.server.server import Server from ...core import Store, HoloMap from ..comms import JupyterComm, Comm from ..plot import GenericElementPlot from ..renderer import Renderer, MIME_TYPES from .widgets import BokehScrubberWidget, BokehSelectionWidget, BokehServerWidgets -from .util import compute_static_patch, serialize_json, attach_periodic +from .util import compute_static_patch, serialize_json, attach_periodic, bokeh_version @@ -84,10 +85,24 @@ def __call__(self, obj, fmt=None, doc=None): elif fmt == 'json': return self.diff(plot), info + @bothmethod - def get_widget(self_or_cls, plot, widget_type, **kwargs): + def get_plot(self_or_cls, obj, doc=None, renderer=None): + """ + Given a HoloViews Viewable return a corresponding plot instance. + Allows supplying a document attach the plot to, useful when + combining the bokeh model with another plot. + """ + plot = super(BokehRenderer, self_or_cls).get_plot(obj, renderer) + if doc is not None: + plot.document = doc + return plot + + + @bothmethod + def get_widget(self_or_cls, plot, widget_type, doc=None, **kwargs): if not isinstance(plot, Plot): - plot = self_or_cls.get_plot(plot) + plot = self_or_cls.get_plot(plot, doc) if self_or_cls.mode == 'server': return BokehServerWidgets(plot, renderer=self_or_cls.instance(), **kwargs) else: @@ -95,25 +110,49 @@ def get_widget(self_or_cls, plot, widget_type, **kwargs): @bothmethod - def app(self_or_cls, plot, notebook=False): + def app(self_or_cls, plot, show=False, new_window=False): """ Creates a bokeh app from a HoloViews object or plot. By default simply uses attaches plot to bokeh's curdoc and - returns the Document, if notebook option is supplied creates - an Application instance, displays it and returns it. + returns the Document, if show option is supplied creates + an Application instance and displays it either in a browser + window or inline if notebook extension has been loaded. + Using the new_window option the app may be displayed in a + new browser tab once the notebook extension has been loaded. """ renderer = self_or_cls.instance(mode='server') - if not notebook: + # If show=False and not in noteboook context return document + if not show and not self_or_cls.notebook_context: doc, _ = renderer(plot) return doc def modify_doc(doc): renderer(plot, doc=doc) - handler = FunctionHandler(modify_doc) app = Application(handler) - show(app) - return app + + if not show: + # If not showing and in notebook context return app + return app + elif self_or_cls.notebook_context and not new_window: + # If in notebook, show=True and no new window requested + # display app inline + return bkshow(app) + + # If app shown outside notebook or new_window requested + # start server and open in new browser tab + from tornado.ioloop import IOLoop + loop = IOLoop.current() + server = Server({'/': app}, port=0, loop=loop) + def show_callback(): + server.show('/') + server.io_loop.add_callback(show_callback) + server.start() + try: + loop.start() + except RuntimeError: + pass + return server def server_doc(self, plot, doc=None): @@ -156,6 +195,7 @@ def diff(self, plot, serialize=True): processed = self._apply_post_render_hooks(patch, plot, 'json') return serialize_json(processed) if serialize else processed + @classmethod def plot_options(cls, obj, percent_size): """ @@ -217,4 +257,5 @@ def load_nb(cls, inline=True): """ Loads the bokeh notebook resources. """ - load_notebook(hide_banner=True, resources=INLINE if inline else CDN) + kwargs = {'notebook_type': 'jupyter'} if bokeh_version > '0.12.5' else {} + load_notebook(hide_banner=True, resources=INLINE if inline else CDN, **kwargs)