Hi,
I’m aware that python callbacks (.on_change or .on_click) require bokeh servers to run, and I see that any periodic or time-based callbacks from the bokeh.document class require the bokeh server to python code.
I would like to create a button.js_on_click() (or a toggle equivalent) that updates the value of a slider periodically, executing only CustomJS code, so that the application can be exported to an HTML document and the javascript callbacks will allow it to work independently. The slider has it’s own callback which would then be triggered after the value update, it will change the view of some other graphs (updated using CDS.change.emit()).
Is this possible?
I’ve added somewhat minimal code to help frame the problem, it runs on its own:
import numpy as np
import pandas as pd
import bokeh
from bokeh.plotting import figure, output_file, show
from bokeh.palettes import RdYlGn10
from bokeh.layouts import grid, column, gridplot, layout
from bokeh.models import CustomJS, Slider, ColumnDataSource
from bokeh.models.widgets import Button, TableColumn, Toggle
dict_slice = {} # dict for slice of dataset - a dictionary to be passed into ColumnDataSource (I had problems passing a dataframe before)
dict_full = {} # dict for full dataset - same
# Create fake data to be used in a plot
randoms = np.random.rand(4,100).tolist() # Four lines, 20 datapoints in each line, 5 'slides' in the animation
x_scale = [*range(1,21)]*5 # The x-axis
slices_to_view = [1]*20 + [2]*20 + [3]*20 + [4]*20 + [5]*20
full_data = pd.DataFrame(data=[slices_to_view,x_scale,randoms[0],randoms[1],randoms[2],randoms[3]],index=['Slice','Xaxis','Line1','Line2','Line3','Line4']).T
display(full_data)
slice_data = full_data[full_data.Slice==1]
display(slice_data)
# Add the data to dictionaries
for col in full_data.columns:
dict_slice[col] = slice_data[col]
dict_full[col] = full_data[col]
# Put the data into CDS format
sliceCDS = ColumnDataSource(dict_slice)
fullCDS = ColumnDataSource(dict_full)
# Create an additional CDS which contains a unique index for the slices
indexCDS = ColumnDataSource(dict(
index=[*range(1,6)]
)
)
# Construct graph of the initial view, using source=sliceCDS
def graph():
p = figure(toolbar_location=None)
for ii in range(1,5):
p.line(x='Xaxis',y=f'Line{ii}',source=sliceCDS,
line_color=RdYlGn10[10-2*ii],line_width=2,legend=f'Line{ii} ',muted_alpha=0.2,muted_color=RdYlGn10[10-2*ii])
p.legend.location = 'top_left'
p.legend.click_policy = 'mute'
return p
# Create slider callback
SliderCallback = CustomJS(args = dict(sliceCDS=sliceCDS, fullCDS=fullCDS, indexCDS=indexCDS), code = """
const new_value = cb_obj.value;
// Take the 'Slice' column from the full data
const slice_col = fullCDS.data['Slice'];
// Select only the values equal to the new slice number
const mask = slice_col.map((item) => item==new_value);
// Update the data for sliceCDS with a slice of data from fullCDS
for(i=1; i<5; i++){
sliceCDS.data['Line' + i.toString()] = fullCDS.data['Line' + i.toString()].filter((item,i) => mask[i]);
}
// Update the sliceCDS
sliceCDS.change.emit();
""")
# Set up slider
slider = bokeh.models.Slider(title="Slice view number: ",start=1, end=5,
value=1,step=1)
slider.js_on_change('value', SliderCallback)
##### Unimplemented section - where the button animation code would go
# Set up Play/Pause button/toggle JS
# toggl_js = CustomJS(args=dict(slider=slider,indexCDS=indexCDS),code="""
# const currdate = slider.value
# const datesrem = datearray.filter((item) => item >= currdate)
# sl.value = currdate
# """)
# toggl = Toggle(label='Play/Pause')
# toggl.js_on_change(toggl_js)
# butt = Button(label="BUTTON", button_type="success")
# butt.js_on_click(toggl_js)
# Set up plot
output_file('AddAnimationPlz.html')
p = figure(plot_width=1500,plot_height=900)
show(bokeh.layouts.layout([[graph()],[slider]],sizing_mode='stretch_both'))