Bokeh Tutorial

11. Running Bokeh Applications

The architecture of Bokeh is such that high-level “model objects” (representing things like plots, ranges, axes, glyphs, etc.) are created in Python, and then converted to a JSON format that is consumed by the client library, BokehJS. Using the Bokeh Server, it is possible to keep the “model objects” in python and in the browser in sync with one another, creating powerful capabilities:

  • respond to UI and tool events generated in a browser with computations or queries using the full power of python
  • automatically push updates the UI (i.e. widgets or plots), in a browser
  • use periodic, timeout, and asychronous callbacks drive streaming updates

This capability to synchronize between python and the browser is the main purpose of the Bokeh Server.

In [ ]:
from bokeh.io import output_notebook, show
output_notebook()

Bokeh Apps in Notebooks

The easiest way to embed a Bokeh application in a notebook is to make a function modify_doc(doc) that creates Bokeh content, and adds it to the document. This function can be passed to show, and the app defined by the function will be displayed inline. A short complete example is below

In [ ]:
from bokeh.layouts import column
from bokeh.models.widgets import TextInput, Button, Paragraph

def modify_doc(doc):
    
    # create some widgets
    button = Button(label="Say HI")
    input = TextInput(value="Bokeh")
    output = Paragraph()

    # add a callback to a widget
    def update():
        output.text = "Hello, " + input.value
    button.on_click(update)

    # create a layout for everything
    layout = column(button, input, output)

    # add the layout to curdoc
    doc.add_root(layout)
    
# In the notebook, just pass the function that defines the app to show
# You may need to supply notebook_url, e.g notebook_url="http://localhost:8889" 
show(modify_doc) 
In [ ]:
# EXERCISE: add a Select widget to this example that offers several different greetings

Bokeh Apps with bokeh serve

It's also possible to define Bokeh applications by creating a standard Python script. In this case, there is no need to make a function like modify_doc. Typically, the script should simply create all the bokeh cotent, then add it to the doc with a line like

curdoc().add_root(layout)

To try out the example below, copy the code into a file hello.py and then execute:

bokeh serve --show hello.py
NOTE: The exercise below require work outside the notebook
# hello.py 

from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models.widgets import TextInput, Button, Paragraph

# create some widgets
button = Button(label="Say HI")
input = TextInput(value="Bokeh")
output = Paragraph()

# add a callback to a widget
def update():
    output.text = "Hello, " + input.value
button.on_click(update)

# create a layout for everything
layout = column(button, input, output)

# add the layout to curdoc
curdoc().add_root(layout)

Copy this code to a script hello.py and run it with the Bokeh server.

Linking Plots and Widgets

Lets take a look at a more involved example that links several widgets to a plot.

In [ ]:
from numpy.random import random

from bokeh.layouts import column, row
from bokeh.plotting import figure
from bokeh.models.widgets import Select, TextInput

def get_data(N):
    return dict(x=random(size=N), y=random(size=N), r=random(size=N) * 0.03)

COLORS = ["black", "firebrick", "navy", "olive", "goldenrod"]

def modify_doc(doc):
    source = ColumnDataSource(data=get_data(200))

    p = figure(tools="", toolbar_location=None)
    r = p.circle(x='x', y='y', radius='r', source=source,
                 color="navy", alpha=0.6, line_color="white")

    
    select = Select(title="Color", value="navy", options=COLORS)
    input = TextInput(title="Number of points", value="200")

    def update_color(attrname, old, new):
        r.glyph.fill_color = select.value
    select.on_change('value', update_color)

    def update_points(attrname, old, new):
        N = int(input.value)
        source.data = get_data(N)
    input.on_change('value', update_points)

    layout = column(row(select, input, width=400), row(p))

    doc.add_root(layout)

show(modify_doc)
In [ ]:
# EXERCISE: add more widgets to change more aspects of this plot

Streaming Data

It is possible to efficiently stream new data to column data sources by using the stream method. This method accepts two argmuments:

  • new_data — a dictionary with the same structure as the column data source
  • rollover — a maximum column length on the client (earlier data is dropped) [optional]

If no rollover is specified, data is never dropped on the client and columns grow without bound.

It is often useful to use periodic callbacks in conjuction with streaming data The add_periodic_callback method of curdoc() accepts a callback function, and a time interval (in ms) to repeatedly execute the callback.

In [ ]:
from math import cos, sin

def modify_doc(doc):
    p = figure(match_aspect=True)
    p.circle(x=0, y=0, radius=1, fill_color=None, line_width=2)
    
    # this is just to help the auto-datarange
    p.rect(0, 0, 2, 2, alpha=0)

    # this is the data source we will stream to
    source = ColumnDataSource(data=dict(x=[1], y=[0]))
    p.circle(x='x', y='y', size=12, fill_color='white', source=source)

    def update():
        x, y = source.data['x'][-1], source.data['y'][-1]

        # construct the new values for all columns, and pass to stream
        new_data = dict(x=[x*cos(0.1) - y*sin(0.1)], y=[x*sin(0.1) + y*cos(0.1)])
        source.stream(new_data, rollover=8)

    doc.add_periodic_callback(update, 150)
    doc.add_root(p)
    
show(modify_doc)
In [ ]:
### EXERCISE: starting with the above example, create your own streaming plot

Bokeh column data sources also support a patch method that can be used to efficiently update subsets of data.

Directory Format Apps and Templates

Bokeh apps can also be defined with a directory format. This format affords the use of extra modules, data files, templates, theme files, and other features. The directory should contain a main.py which is the "entry point" for the app, but ay contain extra parts:

myapp
   |
   +---main.py
   +---server_lifecycle.py
   +---static
   +---theme.yaml
   +---templates
        +---index.html

The Directory Format section of the User's Guide has more information.

See a complete sophisticated example at: https://github.com/bokeh/bokeh/tree/master/examples/app/dash

Tips and Tricks

  • Real Python callbacks require a Bokeh server application. They cannot work with output_file, components or other functions that generate standalone output. Standalone content can only use CustomJS callbacks.
  • Try to update data sources "all at once" whenever possible, i.e. prefer this:
    source.data = new_data_dict  # GOOD
    
    rather then updating individual columns sequentially:
    # LESS GOOD
      source.data['foo'] = new_foo_column
      source.data['bar'] = new_bar_column
    
    If the new columns are exactly the same length as the old ones, then updating sequentially, but will trigger extra updates, and may result in bad visual effects. If the new columns a different length than the old ones, then updating "all at once" is mandatory.
  • Each time a session is started, the Bokeh server runs the script (or modify_doc) function, and the code that is run must return completely new Bokeh objects every time. It is not possible to share Bokeh objects between sessions. As a concrete example, this is what NOT to do:

    source = ColumnDataSource(data)  # VERY BAD - global outside modify_doc
    
      def modify_doc(doc):
          p = figure()
          p.circle('x', 'y', source=source)
          doc.add_root(p)
    

    The analogous situation would occur with a script if the script imports a global Bokeh object from a separate module (due to the way Python caches imports).