How to make a web app with bokeh
Here is an example of an animated bokeh application, embedded in this blog page via:
<iframe height="800" width="800" src="https://anim-demo.herokuapp.com"></iframe>
The code for the bokeh app is included below. It illustrates the use of threading to allow updates to the plot while it’s running. It is a modified version of the example in the bokeh server documentation.
The app itself is hosted on the heroku cloud site. I essentially followed Publishing a Bokeh App to Heroku to set this up. The key file is the Procfile that controls execution of the bokeh server on the cloud. I used the following:
web: bokeh serve --port=$PORT --allow-websocket-origin=anim-demo.herokuapp.com --address=0.0.0.0 --use-xheaders main.py
Important: If you want to embed this app in another web page as a <script></script>
tag instead of
as an iframe
, you follow the directions in the bokeh embed documentation, in the section on Bokeh Applications. However, a site
accessing the bokeh server via a <script>
tag needs to be granted permission via an explicit mention in the --allow-websocket-origin
option in the bokeh server. Alternatively, you can run the server with ---allow-websocket-origin='*'
but that is not recommended. Finally,
there’s an option to restrict access to a domain with ---allow-websocket-origin=domain:com
.
All of this is explained in the discussion of the bokeh server in the bokeh documentation, especially the section on Websocket Origin.
Bokeh App code
#!/usr/bin/python
# bokeh server app
from random import random
from functools import partial
from threading import Thread
import time
from bokeh.core.enums import ButtonType
from bokeh.models import ColumnDataSource, Button, Slider
from bokeh.layouts import layout
from bokeh.plotting import curdoc, figure
from tornado import gen
@gen.coroutine
def update(x, y,i):
if i==0:
source.data=dict(x=[x],y=[y])
else:
source.stream(dict(x=[x], y=[y]))
def wait_then_get_next_point():
for i in range(100):
# do some blocking computation
time.sleep(time_interval)
x, y = random(), random()
# but update the document from callback
doc.add_next_tick_callback(partial(update, x=x, y=y,i=i))
def slider_callback(attr, old, new):
global time_interval
time_interval = 1/float(new)
def do_it():
thread = Thread(target=wait_then_get_next_point)
thread.start()
# this must only be modified from a Bokeh session callback
source = ColumnDataSource(data=dict(x=[], y=[]))
# This is important! Save curdoc() to make sure all threads
# use a slider to set the update rate
time_interval = .1
button = Button(label="Go!",button_type="primary")
button.on_click(do_it)
slider = Slider(start=1, end=20, value=1/time_interval, step=1, title='Updates per Second')
slider.on_change('value',slider_callback)
# see the same document.
doc = curdoc()
doc.title = "Bokeh Animation"
p = figure(x_range=[0, 1], y_range=[0,1],tools=[],title='Demo of Coroutines\n in Bokeh Animation')
l = p.circle(x='x', y='y', source=source)
doc.add_root(layout([p],[slider,button]))