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:
*This capability to synchronize between python and the browser is the main purpose of the Bokeh Server.*
from bokeh.io import output_notebook, show
output_notebook()
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
from bokeh.layouts import column
from bokeh.models 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)
# EXERCISE: add a Select widget to this example that offers several different greetings
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 content, 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
# 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.
Lets take a look at a more involved example that links several widgets to a plot.
from numpy.random import random
from bokeh.layouts import column, row
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, 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)
# EXERCISE: add more widgets to change more aspects of this plot
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 sourcerollover
— 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.
from math import cos, sin
from bokeh.models import ColumnDataSource
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)
### 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.
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 may 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
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 will trigger extra updates and may result in bad visual effects. If the new columns are 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).
This is the last section of the main tutorial. To explore some extra topics, see the appendices:
A1 - Models and Primitives
A2 - Visualizing Big Data with Datashader
A3 - High-Level Charting with Holoviews
A4 - Additional Resources
To go back to the overview, click here.