[Bokeh 2.3.1]
Hey there,
I’m trying to subclass bokeh.models.widgets.Slider
to make it have a throttled behaviour (delay-based not mouse-up based)
For this I grabbed the code from here and modified it a bit: Server side throttled slider - #3 by Matt_Agee .
Modified Code
from time import perf_counter
from functools import partial
from bokeh.models.widgets import Slider
from bokeh.io import curdoc
class ThrottledSlider(Slider):
"""
Extends the bokeh RangeSlider to provide throttling of callbacks provided to on_update
"""
__view_model__ = "Slider"
__subtype__ = ""
def __init__(self, base_delay, doc=curdoc(), *args, **kwargs):
super().__init__(*args, **kwargs)
self._cb_set = False
self._base_delay = base_delay
self._delay = base_delay
self._doc = doc
def on_change(self, attrname, callback):
"""
Register a server side callback to be executed when slider attributes change
"""
print("a")
update_function = self._callback_wrapper(callback)
super().on_change(attrname, partial(self._callback_setter, update_function))
def _callback_setter(self, update_function, attrname, old, new):
if not self._cb_set:
self._doc.add_timeout_callback(partial(update_function, attrname, old), self._delay)
self._cb_set = True
def _callback_wrapper(self, callback):
"""
Wraps callback to use self.value instead of new.
This ensures the callback has the most recent slider value at execution.
Also modifies the delay between callback execution to account for time taken
performing the callback's work.
"""
def throttled_cb(*args):
start_time = perf_counter()
attr, old = args
callback(attr, old, self.value)
# Modify delay to account for time taken executing callback
self._delay = max(self._base_delay - 1000 * (perf_counter() - start_time), 50)
self._cb_set = False
return throttled_cb
However, the proposed workaround (setting __view_model__
and __subtype__
) does not seem to work anymore. (bokeh complains that the model was not registered)
So, as suggested in GoogleGroups I wrote some TypeScript:
TSCode
import * as p from "core/properties"
import {Slider, SliderView} from "models/widgets/slider"
export class ThrottledSliderView extends SliderView {
model: ThrottledSlider
}
export namespace ThrottledSlider {
export type Attrs = p.AttrsOf<Props>
export type Props = Slider.Props
}
export interface ThrottledSlider extends ThrottledSlider.Attrs {}
export class ThrottledSlider extends Slider {
properties: ThrottledSlider.Props
__view_type__: ThrottledSliderView
constructor(attrs?: Partial<ThrottledSlider.Attrs>) {
super(attrs)
}
static init_ThrottledSlider() {
this.prototype.default_view = ThrottledSliderView
}
}
The slider shows up, but on_change
does not fire. What is the right way to do it in 2021? How do I subclass Slider
trivially (and properly), so that the on_change
event fires properly?