How to throttle values on mouseup?

Edit:Ignore this misunderstanding on my end and read my second post for the actual question


What is detailed in https://panel.holoviz.org/reference/widgets/IntRangeSlider.html seems not to work anymore, I get TypeError: 'NumericTuple' object is not subscriptable. Sample code:

# Imports and version numbers
import panel as pn
from sinfo import sinfo

pn.extension()
sinfo()  # This just prints session info
-----
panel       0.9.5
param       1.9.3
sinfo       0.3.1
-----
IPython             6.5.0
jupyter_client      5.2.3
jupyter_core        4.6.3
jupyterlab          2.1.0
notebook            5.6.0
-----
Python 3.7.6 | packaged by conda-forge | (default, Mar 23 2020, 23:03:20) [GCC 7.3.0]
Linux-5.6.2-arch1-2-x86_64-with-arch
4 logical CPU cores
-----
Session information updated at 2020-04-08 12:00
int_range_slider = pn.widgets.IntRangeSlider(
    name='Integer Range Slider',
    start=0, end=10, value=(2, 8), step=2)

int_range_slider
# Shows the slider as in the docs
int_range_slider.param.value
<param.NumericTuple at 0x7f8d145f5470>
int_range_slider.param.value[0]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-4-cf168b05d937> in <module>()
----> 1 int_range_slider.param.value[0]


TypeError: 'NumericTuple' object is not subscriptable

Not sure what you’re trying to do here? The docs state:

int_range_slider.value

you seem to be accessing the parameter itself, not its value.

Sorry, I messed up with .param when I was simplifying an example including @pn.depends with .param.value. My actual question boils down to how to throttle values to only update on mouseup and not continuously as a slider is dragged. I had some issues with this previously, but saw that a PR was merged to fix it.

I thought callback_policy was deprecated, but if I look at the documentation of IntRangeSlider, it is still there. However, in the docs for RangeSlider, there is no callback_policy and instead there is value_throttled which is referencing callback_throttle, which in turn does not exist on this doc page but it does exist on the IntRangeSlider doc page where value_throttled does not exist. I am somewhat confused by this, but here is what I tried so far:

This works (no value throttling):

int_range_slider = pn.widgets.IntRangeSlider()

@pn.depends(int_range_slider.param.value, watch=True)
def print_slider_value(slider_value):
    return slider_value[0]

pn.Column(int_range_slider, print_slider_value)

To get throttling I tried changing int_range_slider.param.value to int_range_slider.param.value_throttled, but this raises TypeError: 'NoneType' object is not subscriptable. This can be fixed by specifying value_throttled=(3, 4) when initializing the slider (although it was not needed to set value= when using int_range_slider.param.value). However, while the docstring for value_throttled reads “upper and lower bounds of selected range where events are throttled”, then entire range seems to be throttled no matter what value I specify.

So my question is: “What is the proper way to throttle the entire slider value range so that it only updates on mouseup?”. This is what I have currently:

int_range_slider = pn.widgets.IntRangeSlider(value_throttled=(8, 9))

@pn.depends(int_range_slider.param.value_throttled, watch=True)
def print_slider_value(slider_value):
    return slider_value[0]

pn.Column(int_range_slider, print_slider_value)

I thought callback_policy was deprecated, but if I look at the documentation of IntRangeSlider, it is still there. However, in the docs for RangeSlider, there is no callback_policy and instead there is value_throttled which is referencing callback_throttle, which in turn does not exist on this doc page but it does exist on the IntRangeSlider doc page where value_throttled does not exist.

Looks like I missed updating some of the docs, an issue or PR on that would be good.

To get throttling I tried changing int_range_slider.param.value to int_range_slider.param.value_throttled, but this raises TypeError: ‘NoneType’ object is not subscriptable.

We should really automatically set the value_throttled value on instantiation, an issue for that would be appreciated.

then entire range seems to be throttled no matter what value I specify.

What is your expected behavior here as that is exactly what I would expect from value_throttled?

Thanks for the quick reply! I’ll open the issues you suggested.

What is your expected behavior here as that is exactly what I would expect from value_throttled ?

To me, the phrasing in the docs implies that the only a certain range of the slider will be throttled: “upper and lower bounds of selected range where events are throttled”. So if I set it to (3, 5) I expect the slider to throttle if moved in the range of 3-5, but not outside this range. Currently, it seems to behave like a boolean, setting parameter to anything toggles the entire range.

Also, what is the length of the throttle? Is in until mouseup? Or somehow specified by callback_throttle (or has this also been deprecated as it is not listed as a separate parameter in the RangeSlider doc?)

So if I set it to (3, 5) I expect the slider to throttle if moved in the range of 3-5, but not outside this range. Currently, it seems to behave like a boolean, setting parameter to anything toggles the entire range.

I’m having some trouble parsing what you’re trying to say here. I certainly think value_throttled is not the clearest name for the parameter, but we inherited that from bokeh. All it means though is that the value is only updated on 'mouseup' events on the slider, if you want to submit a PR to clarify that in the docs that would be hugely appreciated.

Sorry if that was unclear. I’m happy to submit a PR, I just need to get a better understanding of the parameter first.

The reason I was confused, is that value_throttled does not change the initial slider range, so it was not clear to me what the numbers in the tuple were doing and it seems like they just acted as a toggle for throttling, rather than specifying a range and only trottle when the slider is in that range.

Take this example, using value, both the initial slider value and the and the param.value attribute are set to (7, 9).

int_range_slider = pn.widgets.IntRangeSlider(end=10, value=(7, 9))

@pn.depends(int_range_slider.param.value, watch=True)
def print_slider_value(slider_value):
    return slider_value

pn.Column(int_range_slider, print_slider_value)

image

On the other hand,value_throttled only sets param.value_throttled, not the slider value:

int_range_slider = pn.widgets.IntRangeSlider(end=10, value_throttled=(7, 9))

@pn.depends(int_range_slider.param.value_throttled, watch=True)
def print_slider_value(slider_value):
    return slider_value

pn.Column(int_range_slider, print_slider_value)

image

But when I move the slider, it updates both the the value of the slider and .value_throttled. So if I wanted to set the initial range of the slider, I can’t do it with just value_throttled, I also need to set value to the same tuple. You can see it more clearly if I change the return statement to

    return slider_value, int_range_slider.value

image

Dragging the slider around, updates both values… most of the times. Occasionally I come across strange behavior where they are updated differently, see the video below.

Personally, I think the most intuitive behavior would be to make value_throttled a boolean parameter and rely exclusively on value to specify the value range, and not have the user switch between using either param.value or param.value_throttled, but instead always be using param.value in @pn.depends, which would avoid confusion stemming from not realizes that you need to change both the initialization and the decoration to throttle a value. This parameter name could be changed to throttle_value to avoid confusion with how they are named in bokeh and under the hood it could set the param.value attribute to the bokeh value_throttled equivalent, but there would be no user facing change. So they code would look like:

int_range_slider = pn.widgets.IntRangeSlider(end=10, throttle_value=True)

@pn.depends(int_range_slider.param.value, watch=True)
def print_slider_value(slider_value):
    return slider_value

There might be technical limitation that I am not aware of preventing this change, so simply view this as a suggestion of which syntax I think most clearly communicates the purpose of these parameters, and is simplest to understand and also use since it requires changes only in one place to switch between throttled and continuously updated widgets.

Ok that was pretty long, thanks for reading all the way here and let me know if I can clarify anything.

@philippjfr Sorry for the multiple replies, as a new user I can only include one image per post so I had to split it into multiple posts.

@philippjfr I opened GH issues related to what we have been discussing here. Happy to PR for the documentation, but I would first like to confirm my understanding as per my last comments above, most of which I also included in https://github.com/holoviz/panel/issues/1255