holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.83k stars 519 forks source link

Document how to use .from_param to create a widget from a reactive parameter #6911

Open MarcSkovMadsen opened 5 months ago

MarcSkovMadsen commented 5 months ago

I can't figure out how to create a widget from a reactive parameter.

More specifically

import panel as pn

pn.extension()

zoom = pn.rx(2)

slider = pn.widgets.IntSlider.from_param(zoom, start=1, end=10)

I get

AttributeError: 'rx' object has no attribute 'name'

Traceback (most recent call last):
  File "/home/jovyan/repos/private/panel-geospatial/.venv/lib/python3.11/site-packages/panel/io/handlers.py", line 389, in run
    exec(self._code, module.__dict__)
  File "/home/jovyan/repos/private/panel-geospatial/pages/03_mapbox.py", line 11, in <module>
    pn.widgets.IntSlider.from_param(zoom, start=1, end=10)
  File "/home/jovyan/repos/private/panel-geospatial/.venv/lib/python3.11/site-packages/panel/widgets/base.py", line 93, in from_param
    parameter, widgets={parameter.name: dict(type=cls, **params)},
                        ^^^^^^^^^^^^^^
  File "/home/jovyan/repos/private/panel-geospatial/.venv/lib/python3.11/site-packages/param/reactive.py", line 1032, in __getattribute__
    return super().__getattribute__(name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'rx' object has no attribute 'name'

I cannot find this documented anywhere.

MarcSkovMadsen commented 5 months ago

A workaround would be to define the widget first and the from it the reactive parameter. But the problem is that then I cannot update the value of the reactive parameter

AttributeError: Setting the value of a reactive expression is only supported if it wraps a concrete value. A reactive expression wrapping a Parameter or another dynamic reference cannot be updated.

I believe it should be possible to update it.

import panel as pn

pn.extension()

zoom_input = pn.widgets.IntSlider(value=2, start=1, end=10, name="Zoom")
zoom = zoom_input.rx()
zoom.rx.value=3
MarcSkovMadsen commented 5 months ago

This does not work either

import panel as pn

pn.extension()

zoom = pn.rx(2)
zoom_input = pn.widgets.FloatSlider(value=zoom, start=1, end=10, step=1.0, name="Zoom")
zoom_input.value=3
print(zoom.rx.value)

Here the problem is that there is only one-way binding from reactive parameter to widget. Not the other way.

philippjfr commented 5 months ago

There is no way to reverse bind an rx expression since it isn't possible to invert arbitrary expressions.

philippjfr commented 5 months ago

We could add support for inverting an expression that simply mirrors a parameter value but as soon as you do something with it there's no way.

MarcSkovMadsen commented 4 months ago

This can be achieved by this code

import panel as pn

pn.extension()

frequency = pn.rx(1)

slider = pn.widgets.FloatSlider(value=1, start=0, end=10)

def set(value):
    frequency.rx.value=value

slider.rx().rx.watch(set)

pn.Column(slider, frequency).servable()

I think that is lengthy. In react you always get value, set_value where set_value is a function to update the value. I think there should be a set, set_value or update special method on .rx to make this easier.

import panel as pn

pn.extension()

frequency = pn.rx(1)

slider = pn.widgets.FloatSlider(value=1, start=0, end=10)

slider.rx().rx.watch(frequency.rx.set_value)

pn.Column(slider, frequency).servable()

Do you agree? Should I file a request with param?