My question is quite similar to another thread using bokeh 0.7.1, but the API for bokeh servers has changed enough in 0.12.0, that I am struggling to adapt that answer to the new version.
To summarize, I have a page with a grid of timestream plots pulling data from a file that is continuously updated. The page has a MultiSelect menu that lists all the variables in my file. I want to be able to select different variables in the menu, press a button, and then have the plots of the existing variable disappear and be replaced by the new timestreams, where the number of plots may be different. I am running my script with the bokeh serve --show script.py
wrapper.
In my initial attempt at this, I assigned an event handler to a button, which clears 'curdoc' and then adds plots for the newly chosen variables from the MultiSelect. This runs, but the number of plots doesn't update. Clearly I am missing the call that tells the server to somehow refresh the page layout.
import numpy as np
from bokeh.driving import count
from bokeh.plotting import figure, curdoc
from bokeh.layouts import gridplot
from bokeh.models import Slider, Column, Row, ColumnDataSource, MultiSelect, Button
from netCDF4 import Dataset
import datetime
# data
#data = Dataset('/daq/spt3g_software/dfmux/bin/output.nc', 'r', format='NETCDF4')
data = Dataset('20160714_warm_overbiased_noise.nc', 'r', format='NETCDF4')
vars = data.variables.keys()[1:11]
# plots
d = {('y_%s'%name):[] for name in vars}
d['t'] = []
source = ColumnDataSource(data=d)
figs = [figure(x_axis_type="datetime", title=name) for name in vars]
plots = [f.line(x='t', y=('y_%s'%f.title.text), source=source, color="navy", line_width=1) for f in figs]
grid = gridplot(figs, ncols=3, plot_width=500, plot_height=250)
# UI definition
npoints = 2000
slider_npoints = Slider(title="# of points", value=npoints, start=1000, end=10000, step=1000.)
detector_select = MultiSelect(title="Timestreams:", value=[], options=vars)
update_detector_button = Button(label="update detectors", button_type="success")
# UI event handlers
def update_detector_handler():
global figs, plots, grid, source
d = {('y_%s'%name):[] for name in detector_select.value}
d['t'] = []
source = ColumnDataSource(data=d)
figs = [figure(x_axis_type="datetime", title=name) for name in detector_select.value]
plots = [f.line(x='t', y=('y_%s'%f.title.text), source=source, color="navy", line_width=1) for f in figs]
grid = gridplot(figs, ncols=3, plot_width=500, plot_height=250)
curdoc().clear()
curdoc().add_root(Column(Row(slider_npoints, Column(detector_select, update_detector_button)), grid))
update_detector_button.on_click(update_detector_handler)
# callback updater
@count()
def update(t):
data = Dataset('20160714_warm_overbiased_noise.nc', 'r', format='NETCDF4')
#data = Dataset('/daq/spt3g_software/dfmux/bin/output.nc', 'r', format='NETCDF4')
npoints = int(slider_npoints.value)
new_data = {('y_%s'%f.title.text):data[f.title.text][-npoints:] for f in figs}
new_data['t'] = data['Time'][-npoints:]*1e3
source.stream(new_data, npoints)
# define HTML layout and behavior
curdoc().add_root(Column(Row(slider_npoints, Column(detector_select, update_detector_button)), grid))
curdoc().add_periodic_callback(update, 500)
A similar problem was answered on the Bokeh Github page here.
Essentially, instead of messing with curdoc()
you instead modify the children of the layout object e.g. someLayoutHandle.children
.
A simple example is using a toggle button to add and remove a graph:
from bokeh.client import push_session
from bokeh.layouts import column, row
from bokeh.models import Toggle
from bokeh.plotting import figure, curdoc
import numpy as np
# Create an arbitrary figure
p1 = figure(name = 'plot1')
# Create sin and cos data
x = np.linspace(0, 4*np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)
# Create two plots
r1 = p1.circle(x,y1)
# Create the toggle button
toggle = Toggle(label = 'Add Graph',active=False)
mainLayout = column(row(toggle,name='Widgets'),p1,name='mainLayout')
curdoc().add_root(mainLayout)
session = push_session(curdoc())
# Callback which either adds or removes a plot depending on whether the toggle is active
def toggleCallback(attr):
# Get the layout object added to the documents root
rootLayout = curdoc().get_model_by_name('mainLayout')
listOfSubLayouts = rootLayout.children
# Either add or remove the second graph
if toggle.active == False:
plotToRemove = curdoc().get_model_by_name('plot2')
listOfSubLayouts.remove(plotToRemove)
if toggle.active == True:
if not curdoc().get_model_by_name('plot2'):
p2 = figure(name='plot2')
plotToAdd = p2
p2.line(x,y2)
# print('Remade plot 2')
else:
plotToAdd = curdoc().get_model_by_name('plot2')
listOfSubLayouts.append(plotToAdd)
# Set the callback for the toggle button
toggle.on_click(toggleCallback)
session.show()
session.loop_until_closed()
The part which gave me the most trouble was making sure that the plot I wanted to add was part of curdoc()
, which is why the definition is in the callback function. If it is not within the callback, each time plot2 is removed it cannot be found by the bokeh backend. To check that this is the case, uncomment the print statement in the callback function.
I hope this helps!