Combine Existing Components#
This guide addresses how to build custom components that are combinations of existing components.
The simplest way to extend Panel is to implement a so called Viewer
component that can wrap multiple existing Panel components into an easily reusable unit that behaves like a native Panel component.
Let’s create a composite EditableRange
component made up of two FloatInput
widgets. First, we will create the widgets:
import param
import panel as pn
from panel.viewable import Viewer
pn.extension() # for notebook
class EditableRange(Viewer):
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)
Then, we set up callbacks to sync the parameters on the underlying widgets with the parameters on the Viewer
component.
import param
import panel as pn
from panel.viewable import Viewer
pn.extension() # for notebook
class EditableRange(Viewer):
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()
@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)
Finally, we’ll implement the required __panel__
method, which returns the Panel layout to be rendered. Panel will call this method when displaying the component.
import param
import panel as pn
from panel.viewable import Viewer
pn.extension() # for notebook
class EditableRange(Viewer):
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)
range_widget = EditableRange(name='Range', value=(0, 10))
pn.Column(
'#### This is a custom widget',
range_widget
)