Is there any way to throttle slider control w.r.t arrow keys?

For example purposes, I’ll be referring to this sample code, https://docs.bokeh.org/en/latest/docs/gallery/slider.html

Is there any way to throttle slider UI input from the user? For example, I want to click on the slider and hold the left or right arrow key to smoothly change the value. For my application, my python callback has some nonzero processing time, so if a user holds the arrow key, the backend is definitely behind. If possible, I’d like the throttle the JS side so this doesn’t happen. Ideas?

Have you tried to use the value_throttled property in your callbacks? See https://docs.bokeh.org/en/latest/docs/reference/models/widgets.sliders.html

(Disclaimer: I have used this before with mouse-actions on expensive backend operations tied to a slider, but have not experimented with behavior using arrow keys versus mouse actions.)

Yes I tried the obvious throttling options (value_throttled, callback_throttle) I found on the reference. The current interfaces seemed to only work for mouse actions and / or JS callbacks only.

Some helpful stuff I found below:


I’m not sure how keyboard events could be throttled with the current implementation. Unlike with the mouse case, there is not any event like “mouse up” that definitively marks the end of user interaction.

Certainly in the immediate term the only suggestion I can suggest is to put the callback on a button next to the slider, which a user could tab to and hit <enter> to click to “activate changes” after they configured the slider (and any other controls)

Great thanks for confirming. As a workaround, I added a play button to animate at a fixed rate, similar to the the gapminder gallery example.

@Ronald_Truong

For what its worth, another option follows using the sliders example cited in your original post. For illustration purposes, I implemented it only on the amplitude slider (amp_slider).

This workaround uses a periodic callback that inspects additional information in the tags attribute and modifies the tags attribute if the slider value has changed. The original amp_slider.js_on_change() is acting on changes to the tags attribute. Because a periodic callback is added to the document, this method requires to run as a bokeh server.

import numpy as np

from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider
from bokeh.plotting import ColumnDataSource, figure, curdoc

x = np.linspace(0, 10, 500)
y = np.sin(x)

source = ColumnDataSource(data=dict(x=x, y=y))

plot = figure(y_range=(-10, 10), plot_width=400, plot_height=400)

plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

amp_slider = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude")
freq_slider = Slider(start=0.1, end=10, value=1, step=.1, title="Frequency")
phase_slider = Slider(start=0, end=6.4, value=0, step=.1, title="Phase")
offset_slider = Slider(start=-5, end=5, value=0, step=.1, title="Offset")


callback = CustomJS(args=dict(source=source, amp=amp_slider, freq=freq_slider, phase=phase_slider, offset=offset_slider),
                    code="""
    const data = source.data;
    const A = amp.value;
    const k = freq.value;
    const phi = phase.value;
    const B = offset.value;
    const x = data['x']
    const y = data['y']
    for (var i = 0; i < x.length; i++) {
        y[i] = B + A*Math.sin(k*x[i]+phi);
    }
    source.change.emit();
""")

amp_slider.js_on_change('tags', callback)
freq_slider.js_on_change('value', callback)
phase_slider.js_on_change('value', callback)
offset_slider.js_on_change('value', callback)

layout = row(
    plot,
    column(amp_slider, freq_slider, phase_slider, offset_slider),
)


amp_slider.tags = [dict(prior_val=amp_slider.value, counter=0)]

def amp_tags_cb():
    if amp_slider.tags[0]['prior_val'] != amp_slider.value:
        amp_slider.tags = [dict(prior_val=amp_slider.value, counter=amp_slider.tags[0]['counter']+1)]

curdoc().add_periodic_callback(amp_tags_cb, period_milliseconds=3000)
curdoc().add_root(layout)
1 Like

Oh smart, that would totally work! Thanks for the suggestion.