Source code for panel.widgets.base

"""
Defines the Widget base class which provides bi-directional
communication between the rendered dashboard and the Widget
parameters.
"""
from __future__ import annotations

import math

from typing import (
    TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Mapping, Optional,
    Tuple, Type, TypeVar,
)

import param  # type: ignore

from bokeh.models import ImportedStyleSheet, Tooltip
from bokeh.models.dom import HTML
from param.parameterized import register_reference_transform

from .._param import Margin
from ..layout.base import Row
from ..reactive import Reactive
from ..viewable import Layoutable, Viewable

if TYPE_CHECKING:
    from bokeh.document import Document
    from bokeh.model import Model
    from pyviz_comms import Comm

    from ..layout.base import ListPanel

    T = TypeVar('T')


[docs]class Widget(Reactive): """ Widgets allow syncing changes in bokeh widget models with the parameters on the Widget instance. """ disabled = param.Boolean(default=False, doc=""" Whether the widget is disabled.""") name = param.String(default='') height = param.Integer(default=None, bounds=(0, None)) width = param.Integer(default=None, bounds=(0, None)) margin = Margin(default=(5, 10), doc=""" Allows to create additional space around the component. May be specified as a two-tuple of the form (vertical, horizontal) or a four-tuple (top, right, bottom, left).""") _rename: ClassVar[Mapping[str, str | None]] = {'name': 'title'} # Whether the widget supports embedding _supports_embed: ClassVar[bool] = False # Declares the Bokeh model type of the widget _widget_type: ClassVar[Type[Model] | None] = None __abstract = True def __init__(self, **params): if 'name' not in params: params['name'] = '' if '_supports_embed' in params: self._supports_embed = params.pop('_supports_embed') if '_param_pane' in params: self._param_pane = params.pop('_param_pane') else: self._param_pane = None super().__init__(**params)
[docs] @classmethod def from_param(cls: Type[T], parameter: param.Parameter, **params) -> T: """ Construct a widget from a Parameter and link the two bi-directionally. Parameters ---------- parameter: param.Parameter A parameter to create the widget from. params: dict Keyword arguments to be passed to the widget constructor Returns ------- Widget instance linked to the supplied parameter """ from ..param import Param layout = Param( parameter, widgets={parameter.name: dict(type=cls, **params)}, display_threshold=-math.inf ) return layout[0]
@property def _linked_properties(self) -> Tuple[str]: props = list(super()._linked_properties) if 'description' in props: props.remove('description') return tuple(props) @property def rx(self): return self.param.value.rx def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]: params = super()._process_param_change(params) if self._widget_type is not None and 'stylesheets' in params: css = getattr(self._widget_type, '__css__', []) params['stylesheets'] = [ ImportedStyleSheet(url=ss) for ss in css ] + params['stylesheets'] if "description" in params: description = params["description"] renderer_options = params.pop("renderer_options", {}) if isinstance(description, str): from ..pane.markup import Markdown parser = Markdown._get_parser('markdown-it', (), **renderer_options) html = parser.render(description) params['description'] = Tooltip( content=HTML(html), position='right', stylesheets=[':host { white-space: initial; max-width: 300px; }'], syncable=False ) elif isinstance(description, Tooltip): description.syncable = False return params def _get_model( self, doc: Document, root: Optional[Model] = None, parent: Optional[Model] = None, comm: Optional[Comm] = None ) -> Model: model = self._widget_type(**self._get_properties(doc)) root = root or model self._models[root.ref['id']] = (model, parent) self._link_props(model, self._linked_properties, doc, root, comm) return model def _get_embed_state( self, root: 'Model', values: Optional[List[Any]] = None, max_opts: int = 3 ) -> Tuple['Widget', 'Model', List[Any], Callable[['Model'], Any], str, str]: """ Returns the bokeh model and a discrete set of value states for the widget. Arguments --------- root: bokeh.model.Model The root model of the widget values: list (optional) An explicit list of value states to embed max_opts: int The maximum number of states the widget should return Returns ------- widget: panel.widget.Widget The Panel widget instance to modify to effect state changes model: bokeh.model.Model The bokeh model to record the current value state on values: list A list of value states to explore. getter: callable A function that returns the state value given the model on_change: string The name of the widget property to attach a callback on js_getter: string JS snippet that returns the state value given the model """
[docs]class CompositeWidget(Widget): """ A baseclass for widgets which are made up of two or more other widgets """ _composite_type: ClassVar[Type[ListPanel]] = Row _linked_properties: ClassVar[Tuple[str]] = () __abstract = True def __init__(self, **params): super().__init__(**params) layout_params = [p for p in Layoutable.param if p != 'name'] layout = {p: getattr(self, p) for p in layout_params if getattr(self, p) is not None} if layout.get('width', self.width) is None and 'sizing_mode' not in layout: layout['sizing_mode'] = 'stretch_width' if layout.get('sizing_mode') not in (None, 'fixed') and layout.get('width'): min_width = layout.pop('width') if not layout.get('min_width'): layout['min_width'] = min_width self._composite = self._composite_type(**layout) self._models = self._composite._models self._internal_callbacks.append( self.param.watch(self._update_layout_params, layout_params) ) def _update_layout_params(self, *events: param.parameterized.Event) -> None: updates = {event.name: event.new for event in events} self._composite.param.update(**updates)
[docs] def select( self, selector: Optional[type | Callable[['Viewable'], bool]] = None ) -> List[Viewable]: """ Iterates over the Viewable and any potential children in the applying the Selector. Arguments --------- selector: type or callable or None The selector allows selecting a subset of Viewables by declaring a type or callable function to filter by. Returns ------- viewables: list(Viewable) """ objects = super().select(selector) for obj in self._composite.objects: objects += obj.select(selector) return objects
def _cleanup(self, root: Model | None = None) -> None: self._composite._cleanup(root) super()._cleanup(root) def _get_model( self, doc: Document, root: Optional[Model] = None, parent: Optional[Model] = None, comm: Optional[Comm] = None ) -> Model: model = self._composite._get_model(doc, root, parent, comm) root = root or model self._models[root.ref['id']] = (model, parent) return model def __contains__(self, object: Any) -> bool: return object in self._composite.objects @property def _synced_params(self) -> List[str]: return []
def _widget_transform(obj): return obj.param.value if isinstance(obj, Widget) else obj register_reference_transform(_widget_transform)