Search code examples
htmlexportjupyter-notebookbokeh

Export interactive Bokeh plots with widgets to standalone HTML


I'm looking for a way to export my Jupyter Notebook containing interactive Bokeh plots with widgets to standalone HTML. When using the Jupyter NB "download to" HTML function located in the toolbar, everything but the interactive Bokeh plots export well, also the static Bokeh plots (static plots are 'interactive' as well, but the underlying data does not change)

How do I get the interactive plots with widgets working in a standalone HTML?

Below you can find a working example in Jupyter Notebook with Bokeh 13.0 installed.

import numpy as np
import pandas as pd

from bokeh.io import save, curdoc,output_file ,show, output_notebook, push_notebook
from bokeh.plotting import figure, gridplot

from bokeh.models import ColumnDataSource, Panel
from bokeh.models.widgets import Slider, Tabs, DataTable, TableColumn
from bokeh.layouts import layout, WidgetBox
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
# output_file('tryout.html')
output_notebook()

# Static Bokeh plot:
data = pd.DataFrame(np.random.random([10,2]),columns=['x','y'])
dataMean = pd.DataFrame([],columns=['mean','std'])

dataMean.loc[:,'mean'] =data.mean()
dataMean.loc[:,'std'] =data.std()        
src1 = ColumnDataSource(data)
src2 = ColumnDataSource(dataMean)

p = figure(plot_width = 700, plot_height = 400, 
title = 'Test case',x_axis_label = 'x', y_axis_label = 'y')

p.line(source=src1,y='y',x='x',color='blue',line_width=2)
p.circle(source=src1,y='y',x='x',color='green')

columnsT = [TableColumn(field="mean", title="mean"),
        TableColumn(field="std", title='std')]
data_table = DataTable(source=src2, columns=columnsT, width=400, height=400)    
data_table.index_position = None
controls = WidgetBox(data_table)    
layO = layout([[p],[controls]])
# Make a tab with the layout 
tab1 = Panel(child=layO, title = 'test')
tabs = Tabs(tabs=[tab1])
show(tabs)

# Now the same plot, but fitted with a slider widget
def modify_doc(doc):

    def make_dataset(N = 2):

        data = pd.DataFrame(np.random.random([N,2]),columns=['x','y'])
        dataMean = pd.DataFrame([],columns=['mean','std'])

        dataMean.loc[:,'mean'] =data.mean()
        dataMean.loc[:,'std'] =data.std()        
        return ColumnDataSource(data),ColumnDataSource(dataMean)

    def make_plot(src):
        # Blank plot with correct labels
        p = figure(plot_width = 700, plot_height = 400, 
                  title = 'Test case',x_axis_label = 'x', y_axis_label = 'y')

        p.line(source=src,y='y',x='x',color='blue',line_width=2)
        p.circle(source=src,y='y',x='x',color='green')
        return p
    def update(attr, old, new):
        new_src, new_src2 = make_dataset(N_select.value)

        src.data.update(new_src.data)
        src2.data.update(new_src2.data)

    N_select = Slider(start = 2, end = 20, step = 1, value = 2, title = 'number of points',width=700)
    N_select.on_change('value', update)    

    columnsT = [
        TableColumn(field="mean", title="mean"),
        TableColumn(field="std", title='std')]

    src, src2 = make_dataset(N_select.value)
    data_table = DataTable(source=src2, columns=columnsT, width=400, height=400)    
    data_table.index_position = None

    p = make_plot(src)
    # Put controls in a single element
    controls = WidgetBox(N_select,data_table)
    layO = layout([[p],[controls]])
    # Make a tab with the layout 
    tab1 = Panel(child=layO, title = 'test')  
    tabs = Tabs(tabs=[tab1])    
    doc.add_root(tabs)
handler= FunctionHandler(modify_doc)
app = Application(handler)
show(app)

If I change the output_notebook() to output_file('tryout.html'), it givesme the following error, which I dont understand and could find a solution for as well:

RuntimeError: no display hook installed for notebook type None

Hope anybody is able to help me out with this.

thanks in advance!


Solution

  • EDIT: The earlier answer is still generally correct, however there have been recent developments such as PyScript that can afford the option to "run Python code" (including Bokeh server app code) in the browser in static HTML pages. The Holoviz/Panel team is on the forefront of this.


    What you are asking for is not possible, at least not as I understand your question. You have created a Bokeh server application, with real Python code callbacks. It is not possible for a standalone HTML document to run real Python code, because browsers have no ability whatsoever to run Python code. Real Python code callbacks require a live, running Python interpreter process. When you embed a bokeh server app in the notebook, as you have done above, that process is the IPython kernel.

    If you simply want a Bokeh sever app (which requires running on a Bokeh server, because that is the Python process that runs your callbacks) that can run outside the notebook, the easiest way is to put all the code in a script that you run with

    bokeh serve --show myapp.py
    

    The very rough outline of such apps is:

    from bokeh.io import curdoc
    from bokeh.layouts import column
    from bokeh.models import Slider
    from bokeh.plotting import figure
    
    # create plots
    plot = figure(...)
    
    # create widgets
    slider = Slider(...)
    
    # add callbacks to widgets
    def update(attr, old, new):
        pass
    slider.on_change('value', update)
    
    # put things in a layout
    layout = column(slider, plot)
    
    # add to curdoc
    curdoc().add_root(layout)
    

    Alternatively it's also possible to embed Bokeh server apps in "regular" python scripts. For that, see Embedding Bokeh Server as Library.