I am endeavouring to build some custom components that I then want to link (or allow other to link) together via the link
method. I found being able to just inherit from panel.viewable.Viewer
very effective, but then I wanted to add the extra linking functionality. Inheriting from panel.reactive.Reactive
made all of that work perfectly. The catch is that the __panel__
override no longer seems to produce the same results. I have an example case below, taken from the Custom Component docs.
import panel as pn
pn.extension()
class CustomReactivePane(pn.viewable.Viewer, pn.reactive.Reactive):
value = param.Range(doc="A numeric range.")
width = param.Integer(default=300)
def __init__(self, **params):
self._start_input = pn.widgets.FloatInput()
self._end_input = pn.widgets.FloatInput(align='end')
super().__init__(**params)
self._layout = pn.Row(self._start_input, self._end_input)
self._sync_widgets()
def __panel__(self):
return self._layout
@param.depends('value', 'width', watch=True)
def _sync_widgets(self):
self._start_input.name = self.name
self._start_input.value = self.value[0]
self._end_input.value = self.value[1]
self._start_input.width = self.width//2
self._end_input.width = self.width//2
@param.depends('_start_input.value', '_end_input.value', watch=True)
def _sync_params(self):
self.value = (self._start_input.value, self._end_input.value)
If I then have
range_widget = CustomReactivePane(name='Range', value=(0, 10))
pn.Column(
'## This is a custom widget',
range_widget
)
what I get as a result in a notebook is:
Column
[0] Markdown(str)
[1] CustomReactivePane(name='Range', value=(0, 10))
which contrasts with the nice actual widget panel I get if I either use range_widget._layout
or remove the pn.reactive.Reactive
from the inheritance. I’ve tried a few things such as explicitly calling the Viewer
init etc. but to no success. Is there an easy way to have a custom panel class that is both easily displayed and reactive?
Definitely need to document how to extend some of the core components. By inheriting from Reactive
you are sending it down a very different codepath which expects calling the standard rendering machinery that invokes ._get_model()
. You can simply forward the args and kwargs to the _layout and it’ll work:
import panel as pn
pn.extension()
class CustomReactivePane(pn.reactive.Reactive):
value = param.Range(doc="A numeric range.")
width = param.Integer(default=300)
def __init__(self, **params):
self._start_input = pn.widgets.FloatInput()
self._end_input = pn.widgets.FloatInput(align='end')
super().__init__(**params)
self._layout = pn.Row(self._start_input, self._end_input)
self._sync_widgets()
def _get_model(self, *args, **kwargs):
return self._layout._get_model(*args, **kwargs)
@param.depends('value', 'width', watch=True)
def _sync_widgets(self):
self._start_input.name = self.name
self._start_input.value = self.value[0]
self._end_input.value = self.value[1]
self._start_input.width = self.width//2
self._end_input.width = self.width//2
@param.depends('_start_input.value', '_end_input.value', watch=True)
def _sync_params(self):
self.value = (self._start_input.value, self._end_input.value)
you might also want to extend pn.widgets.base.CompositeWidget
, which directly forwards layout parameters to the container and handles a bit of other housekeeping around making the subcomponents discoverable to Panel:
class CustomReactivePane(pn.widgets.base.CompositeWidget):
value = param.Range(doc="A numeric range.")
_composite_type = pn.Row
def __init__(self, **params):
self._start_input = pn.widgets.FloatInput()
self._end_input = pn.widgets.FloatInput(align='end')
super().__init__(**params)
self._composite[:] = [self._start_input, self._end_input]
self._sync_widgets()
@param.depends('value', 'width', watch=True)
def _sync_widgets(self):
self._start_input.name = self.name
self._start_input.value = self.value[0]
self._end_input.value = self.value[1]
self._start_input.width = self.width//2
self._end_input.width = self.width//2
@param.depends('_start_input.value', '_end_input.value', watch=True)
def _sync_params(self):
self.value = (self._start_input.value, self._end_input.value)
CustomReactivePane(value=(0, 0), name='Test')
1 Like
Thanks, option one worked well in my actual use case. I’ll have to look into the CompositeWidget
a little more. I am pretty new to Panel so this has been quite a learning curve for me, but it really is very powerful. The fact that there is a lot more power not currently explained in the docs is … both awesome (because it does what I want – honestly being able to just use link
on a few of my custom components and have everything work as I intended was magical) and understandable (there is a lot to the library, and only so much one can document).
2 Likes