0

Is it possible to do simple dynamic selection of data with a slider in Bokeh without a custom Python callback? Here is what I can do using a callback, but it would need a Bokeh server to work in exported html:

import numpy as np
import pandas as pd
import ipywidgets as widgets
from bokeh.io import show, push_notebook
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource

x = np.arange(0, 10, 0.1)
dfs = []
ts = range(100)
for t in ts:
    y = x**(t/50.)
    dfs.append(pd.DataFrame({"x": x, "y": y, "t": t}))

df = pd.concat(dfs)
cds = ColumnDataSource(df)

p = figure(x_range=(0,10), y_range=(0,100))
p.scatter(x='x', y='y', source=cds)

handle = show(p, notebook_handle=True)

def update(t):
    cds.data = df[df.t == t]
    push_notebook(handle=handle)

slider = widgets.SelectionSlider(value=0, options=ts)
io = widgets.interactive_output(update, {"t": slider})
display(slider, io)

enter image description here

However it really feels like a custom callback should not be necessary for this, and it is quite a pain in my larger application. Is there not some clever way it can be done purely with Bokeh-native objects, like a CDSView or filter or something? I'm sure some custom javascript can do it too, but again I'd rather not if possible.

3
  • "but it would need a Bokeh server to work in exported html". Not necessarily anymore. Bokeh now works with pyodide and so you can run it inside your browser using webassembly. So it would appear to a user as active but using static serving and now server side client. For something akin to what you get from exported HTML you can use Voici. (Also see The Gallery of Voici Dashboards.) Perhaps. I say 'perhaps' because I am not recalling having tried that with Bokeh, or ... Commented Aug 20, 2024 at 14:42
  • <continued> coming across a good example, and there were still some rough edges last I tried it here. I do know Bokeh and widgets controlling it should work without a real serve. I have an example here. Scroll down to the bottom. When I just tried it now, I had to reload the page a second time to get the plot to show up in the bottom cell, but it does. You'll see the widgets still work. This is because nbviewer can handle javascript. And I discuss exporting the HTML, too. Commented Aug 20, 2024 at 14:47
  • Related to all this is py.cafe. This is newer and could possibly replace your making HTML. However, I cannot say I have seen a bokeh example there yet. It is very new though. Commented Aug 20, 2024 at 15:35

1 Answer 1

0

Well, I had an idea of trying to do it by linking the slider value to the 'group' attribute of a GroupFilter, something like this:

t_selector = GroupFilter(column_name='t', group=0)
view = CDSView(filter=t_selector)

p.scatter(x='x', y='y', source=cds, view=view)

t_slider = Slider(start=ts[0], end=ts[-1], step=tstep)
t_slider.js_link('value', t_selector, 'group')

But apparently GroupFilter only works with strings for the groups (though https://github.com/bokeh/bokeh/issues/7524 exists to remedy that, and there seems to be some activity since I gave it a nudge).

So I gave up and just added the custom javascript, which I guess is fine:

import numpy as np
import pandas as pd
from bokeh.io import show, push_notebook
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CDSView, CustomJS, CustomJSFilter, Slider

x = np.arange(0, 10, 0.1)
dfs = []
tstep = 1
ts = range(0, 100, tstep)
for t in ts:
    y = x**(t/50.)
    dfs.append(pd.DataFrame({"x": x, "y": y, "t": t}))

df = pd.concat(dfs)
cds = ColumnDataSource(df)

t_slider = Slider(start=ts[0], end=ts[-1], step=tstep, value=0)

# Callback to notify downstream objects of data change
change_callback = CustomJS(args=dict(source=cds), code="""
    source.change.emit();
""")
t_slider.js_on_change('value', change_callback)

# JS filter to select data rows matching t value on slider
js_filter = CustomJSFilter(args=dict(slider=t_slider), code="""
const indices = [];

// iterate through rows of data source and see if each satisfies some constraint
for (let i = 0; i < source.get_length(); i++){
    if (source.data['t'][i] == slider.value){
        indices.push(true);
    } else {
        indices.push(false);
    }
}
return indices;
""")

    
# Use the filter in a view
view = CDSView(filter=js_filter)
                           
p = figure(x_range=(0,10), y_range=(0,100))
p.scatter(x='x', y='y', source=cds, view=view)

layout = column(p, t_slider)
    
show(layout)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.