"""The FeatureInput enables a user to select from a list of features and set their values""" import panel as pn import param class FeatureInput(pn.widgets.CompositeWidget): """The FeatureInput enables a user to select from a list of features and set their values ## Example ```python features = { "A": 1.0, "B": 2.0, "C": 3.0, "D": 4.0, } selected_features = ["A", "C"] widget = FeatureInput(features=features, selected_features=selected_features) ``` """ value = param.Dict( doc="The names of the features selected and their set values", allow_None=False ) features = param.Dict( doc="The names of the available features and their default values", allow_None=False, ) selected_features = param.ListSelector( doc="The list of selected features", allow_None=False ) _selected_widgets = param.ClassSelector( class_=pn.Column, doc="The widgets used to edit the selected features" ) _composite_type = pn.Column def __init__(self, **params): params["value"] = params.get("value", {}) params["features"] = params.get("features", {}) params["selected_features"] = params.get("selected_features", []) params["_selected_widgets"] = self.param._selected_widgets.class_() super().__init__(**params) selected_features_widget = pn.widgets.MultiChoice.from_param( self.param.selected_features, sizing_mode="stretch_width" ) self._composite[:] = [selected_features_widget, self._selected_widgets] @param.depends("features", watch=True, on_init=True) def _reset_selected_features(self): selected_features = [] for feature in self.selected_features.copy(): if feature in self.features.copy(): selected_features.append(feature) self.param.selected_features.objects = list(self.features) self.selected_features = selected_features @param.depends("selected_features", watch=True, on_init=True) def _handle_selected_features_change(self): org_value = self.value self._update_selected_widgets(org_value) self._update_value() def _update_value(self, *args): # pylint: disable=unused-argument new_value = {} for widget in self._selected_widgets: new_value[widget.name] = widget.value self.value = new_value def _update_selected_widgets(self, org_value): new_widgets = {} for feature in self.selected_features: value = org_value.get(feature, self.features[feature]) widget = self._new_widget(feature, value) new_widgets[feature] = widget self._selected_widgets[:] = list(new_widgets.values()) def _new_widget(self, feature, value): widget = pn.widgets.FloatInput( name=feature, value=value, sizing_mode="stretch_width" ) pn.bind(self._update_value, widget, watch=True) return widget def create_app(): features = { "Blade Length (m)": 73.5, "Cut-in Wind Speed (m/s)": 3.5, "Cut-out Wind Speed (m/s)": 25, "Grid Connection Capacity (MW)": 5, "Hub Height (m)": 100, "Rated Wind Speed (m/s)": 12, "Rotor Diameter (m)": 150, "Turbine Efficiency (%)": 45, "Water Depth (m)": 30, "Wind Speed (m/s)": 10, } selected_features = ["Wind Speed (m/s)", "Rotor Diameter (m)"] widget = FeatureInput( features=features, selected_features=selected_features, width=500, ) return pn.FlexBox( pn.Column( "## Widget", widget, ), pn.Column( "## Value", pn.pane.JSON(widget.param.value, width=500, height=200), ), ) if pn.state.served: pn.extension(design="material") create_app().servable()