class DateRangeSlider(_SliderBase): value = param.Tuple(default=(None, None), length=2) value_throttled = param.Tuple(default=None, length=2, constant=True) start = param.Date(default=None) end = param.Date(default=None) step = param.Number(default=1) _source_transforms = {'value': None, 'value_throttled': None, 'start': None, 'end': None, 'step': None} _widget_type = _BkDateRangeSlider def __init__(self, **params): if 'value' not in params: params['value'] = (params.get('start', self.start), params.get('end', self.end)) super(DateRangeSlider, self).__init__(**params) def _process_property_change(self, msg): msg = super(DateRangeSlider, self)._process_property_change(msg) if 'value' in msg: v1, v2 = msg['value'] msg['value'] = (value_as_datetime(v1), value_as_datetime(v2)) if 'value_throttled' in msg: v1, v2 = msg['value_throttled'] msg['value_throttled'] = (value_as_datetime(v1), value_as_datetime(v2)) return msg
class RangeXY(LinkedStream): """ Axis ranges along x- and y-axis in data coordinates. """ x_range = param.Tuple(default=None, length=2, constant=True, doc=""" Range of the x-axis of a plot in data coordinates""") y_range = param.Tuple(default=None, length=2, constant=True, doc=""" Range of the y-axis of a plot in data coordinates""")
def params_from_kwargs(**kwargs): """ Utility to promote keywords with literal values to the appropriate parameter type with the specified default value unless the value is already a parameter. """ params = {} for k, v in kwargs.items(): kws = dict(default=v) if isinstance(v, param.Parameter): params[k] = v elif isinstance(v, bool): params[k] = param.Boolean(**kws) elif isinstance(v, int): params[k] = param.Integer(**kws) elif isinstance(v, float): params[k] = param.Number(**kws) elif isinstance(v, str): params[k] = param.String(**kws) elif isinstance(v, dict): params[k] = param.Dict(**kws) elif isinstance(v, tuple): params[k] = param.Tuple(**kws) elif isinstance(v, list): params[k] = param.List(**kws) elif isinstance(v, np.ndarray): params[k] = param.Array(**kws) else: params[k] = param.Parameter(**kws) return params
class _RangeSliderBase(_SliderBase): value = param.Tuple(length=2, doc=""" The selected range of the slider. Updated when a handle is dragged.""") value_start = param.Parameter( readonly=True, doc="""The lower value of the selected range.""") value_end = param.Parameter( readonly=True, doc="""The upper value of the selected range.""") __abstract = True def __init__(self, **params): if 'value' not in params: params['value'] = (params.get('start', self.start), params.get('end', self.end)) params['value_start'], params['value_end'] = params['value'] with edit_readonly(self): super().__init__(**params) @param.depends('value', watch=True) def _sync_values(self): vs, ve = self.value with edit_readonly(self): self.param.update(value_start=vs, value_end=ve) def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg: msg['value'] = tuple(msg['value']) if 'value_throttled' in msg: msg['value_throttled'] = tuple(msg['value_throttled']) return msg
class TestSet(param.Parameterized): numpy_params = ['r'] pandas_params = ['s','t','u'] conditionally_unsafe = ['f', 'o'] a = param.Integer(default=5, doc='Example doc', bounds=(2,30), inclusive_bounds=(True, False)) b = param.Number(default=4.3, allow_None=True) c = param.String(default='foo') d = param.Boolean(default=False) e = param.List([1,2,3], class_=int) f = param.List([1,2,3]) g = param.Date(default=datetime.datetime.now()) h = param.Tuple(default=(1,2,3), length=3) i = param.NumericTuple(default=(1,2,3,4)) j = param.XYCoordinates(default=(32.1, 51.5)) k = param.Integer(default=1) l = param.Range(default=(1.1,2.3), bounds=(1,3)) m = param.String(default='baz', allow_None=True) n = param.ObjectSelector(default=3, objects=[3,'foo'], allow_None=False) o = param.ObjectSelector(default=simple_list, objects=[simple_list], allow_None=False) p = param.ListSelector(default=[1,4,5], objects=[1,2,3,4,5,6]) q = param.CalendarDate(default=datetime.date.today()) r = None if np is None else param.Array(default=ndarray) s = None if pd is None else param.DataFrame(default=df1, columns=2) t = None if pd is None else param.DataFrame(default=pd.DataFrame( {'A':[1,2,3], 'B':[1.1,2.2,3.3]}), columns=(1,4), rows=(2,5)) u = None if pd is None else param.DataFrame(default=df2, columns=['A', 'B']) v = param.Dict({'1':2})
class Bounds(BaseShape): """ An arbitrary axis-aligned bounding rectangle defined by the (left, bottom, right, top) coordinate positions. If supplied a single real number as input, this value will be treated as the radius of a square, zero-center box which will be used to compute the corresponding lbrt tuple. """ lbrt = param.Tuple(default=(-0.5, -0.5, 0.5, 0.5), doc=""" The (left, bottom, right, top) coordinates of the bounding box.""") group = param.String(default='Bounds', constant=True, doc="The assigned group name.") __pos_params = ['lbrt'] def __init__(self, lbrt, **params): if not isinstance(lbrt, tuple): lbrt = (-lbrt, -lbrt, lbrt, lbrt) super(Bounds, self).__init__(lbrt=lbrt, **params) (l, b, r, t) = self.lbrt xdim, ydim = self.kdims self.data = [ OrderedDict([(xdim.name, np.array([l, l, r, r, l])), (ydim.name, np.array([b, t, t, b, b]))]) ]
class Element3D(Element2D): extents = param.Tuple(default=(None, None, None, None, None, None), doc="""Allows overriding the extents of the Element in 3D space defined as (xmin, ymin, zmin, xmax, ymax, zmax).""")
class Surface(Image, Element3D): """ Surface Element represents a 3D surface in space. The data should be supplied as a dense NxM matrix. """ extents = param.Tuple(default=(None, None, None, None, None, None), doc=""" Allows overriding the extents of the Element in 3D space defined as (xmin, ymin, zmin, xmax, ymax, zmax).""") kdims = param.List(default=[Dimension('x'), Dimension('y')], bounds=(2, 2), doc=""" The Surface x and y dimensions of the space defined by the supplied extent.""") vdims = param.List(default=[Dimension('z')], bounds=(1, 1), doc=""" The Surface height dimension.""") group = param.String(default='Surface', constant=True) def __init__(self, data, kdims=None, vdims=None, extents=None, **params): extents = extents if extents else (None, None, None, None, None, None) Image.__init__(self, data, kdims=kdims, vdims=vdims, extents=extents, **params)
class _RangeSliderBase(_SliderBase): value = param.Tuple(length=2) value_start = param.Parameter(readonly=True) value_end = param.Parameter(readonly=True) __abstract = True def __init__(self, **params): if 'value' not in params: params['value'] = (params.get('start', self.start), params.get('end', self.end)) params['value_start'], params['value_end'] = params['value'] with edit_readonly(self): super().__init__(**params) @param.depends('value', watch=True) def _sync_values(self): vs, ve = self.value with edit_readonly(self): self.param.set_param(value_start=vs, value_end=ve) def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg: msg['value'] = tuple(msg['value']) if 'value_throttled' in msg: msg['value_throttled'] = tuple(msg['value_throttled']) return msg
class DatetimeRangeInput(CompositeWidget): value = param.Tuple(default=(None, None), length=2) start = param.Date(default=None) end = param.Date(default=None) format = param.String(default='%Y-%m-%d %H:%M:%S', doc=""" Datetime format used for parsing and formatting the datetime.""") _composite_type = Column def __init__(self, **params): self._text = StaticText(margin=(5, 0, 0, 0), style={'white-space': 'nowrap'}) self._start = DatetimeInput(sizing_mode='stretch_width', margin=(5, 0, 0, 0)) self._end = DatetimeInput(sizing_mode='stretch_width', margin=(5, 0, 0, 0)) if 'value' not in params: params['value'] = (params['start'], params['end']) super().__init__(**params) self._msg = '' self._composite.extend([self._text, self._start, self._end]) self._updating = False self._update_widgets() self._update_label() @param.depends('name', '_start.name', '_end.name', watch=True) def _update_label(self): self._text.value = f'{self.name}{self._start.name}{self._end.name}{self._msg}' @param.depends('_start.value', '_end.value', watch=True) def _update(self): if self._updating: return if (self._start.value is not None and self._end.value is not None and self._start.value > self._end.value): self._msg = ' (start of range must be <= end)' self._update_label() return elif self._msg: self._msg = '' self._update_label() try: self._updating = True self.value = (self._start.value, self._end.value) finally: self._updating = False @param.depends('value', 'start', 'end', 'name', 'format', watch=True) def _update_widgets(self): if self._updating: return try: self._updating = True self._start.param.set_param(value=self.value[0], start=self.start, end=self.end, format=self.format) self._end.param.set_param(value=self.value[1], start=self.start, end=self.end, format=self.format) finally: self._updating = False
class DateRangeSlider(_RangeSliderBase): value = param.Tuple(default=(None, None), length=2) value_start = param.Date(default=None, readonly=True) value_end = param.Date(default=None, readonly=True) value_throttled = param.Tuple(default=None, length=2, constant=True) start = param.Date(default=None) end = param.Date(default=None) step = param.Number(default=1) _source_transforms = { 'value': None, 'value_throttled': None, 'start': None, 'end': None, 'step': None } _rename = {'name': 'title', 'value_start': None, 'value_end': None} _widget_type = _BkDateRangeSlider def _process_param_change(self, msg): msg = super()._process_param_change(msg) if msg.get('value') == (None, None): del msg['value'] if msg.get('value_throttled') == (None, None): del msg['value_throttled'] return msg def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg: v1, v2 = msg['value'] msg['value'] = (value_as_datetime(v1), value_as_datetime(v2)) if 'value_throttled' in msg: v1, v2 = msg['value_throttled'] msg['value_throttled'] = (value_as_datetime(v1), value_as_datetime(v2)) return msg
class Element2D(Element): extents = param.Tuple(default=(None, None, None, None), doc=""" Allows overriding the extents of the Element in 2D space defined as four-tuple defining the (left, bottom, right and top) edges.""") __abstract = True
class BoundsX(LinkedStream): """ A stream representing the bounds of a box selection as an tuple of the left and right coordinates. """ boundsx = param.Tuple(default=None, constant=True, length=2, allow_None=True, doc=""" Bounds defined as (left, right) tuple.""")
class BoundsY(LinkedStream): """ A stream representing the bounds of a box selection as an tuple of the bottom and top coordinates. """ boundsy = param.Tuple(default=None, constant=True, length=2, allow_None=True, doc=""" Bounds defined as (bottom, top) tuple.""")
class Element3D(Element2D): extents = param.Tuple(default=(None, None, None, None, None, None), doc=""" Allows overriding the extents of the Element in 3D space defined as (xmin, ymin, zmin, xmax, ymax, zmax).""") __abstract = True _selection_streams = ()
class _BigDumbParams(param.Parameterized): action = param.Action(default_action, allow_None=True) array = param.Array(np.array([1.0, 2.0])) boolean = param.Boolean(True, allow_None=True) callable = param.Callable(default_action, allow_None=True) class_selector = param.ClassSelector(int, is_instance=False, allow_None=True) color = param.Color("#FFFFFF", allow_None=True) composite = param.Composite(["action", "array"], allow_None=True) try: data_frame = param.DataFrame( pd.DataFrame({"A": 1.0, "B": np.arange(5)}), allow_None=True ) except TypeError: data_frame = param.DataFrame(pd.DataFrame({"A": 1.0, "B": np.arange(5)})) date = param.Date(datetime.now(), allow_None=True) date_range = param.DateRange((datetime.min, datetime.max), allow_None=True) dict_ = param.Dict({"foo": "bar"}, allow_None=True, doc="dict means dictionary") dynamic = param.Dynamic(default=default_action, allow_None=True) file_selector = param.FileSelector( os.path.join(FILE_DIR_DIR, "LICENSE"), path=os.path.join(FILE_DIR_DIR, "*"), allow_None=True, ) filename = param.Filename( os.path.join(FILE_DIR_DIR, "LICENSE"), allow_None=True ) foldername = param.Foldername(os.path.join(FILE_DIR_DIR), allow_None=True) hook_list = param.HookList( [CallableObject(), CallableObject()], class_=CallableObject, allow_None=True ) integer = param.Integer(10, allow_None=True) list_ = param.List([1, 2, 3], allow_None=True, class_=int) list_selector = param.ListSelector([2, 2], objects=[1, 2, 3], allow_None=True) magnitude = param.Magnitude(0.5, allow_None=True) multi_file_selector = param.MultiFileSelector( [], path=os.path.join(FILE_DIR_DIR, "*"), allow_None=True, check_on_set=True, ) number = param.Number(-10.0, allow_None=True, doc="here is a number") numeric_tuple = param.NumericTuple((5.0, 10.0), allow_None=True) object_selector = param.ObjectSelector( False, objects={"False": False, "True": 1}, allow_None=True ) path = param.Path(os.path.join(FILE_DIR_DIR, "LICENSE"), allow_None=True) range_ = param.Range((-1.0, 2.0), allow_None=True) series = param.Series(pd.Series(range(5)), allow_None=True) string = param.String("foo", allow_None=True, doc="this is a string") tuple_ = param.Tuple((3, 4, "fi"), allow_None=True) x_y_coordinates = param.XYCoordinates((1.0, 2.0), allow_None=True)
class DateRangeSlider(_SliderBase): value = param.Tuple(default=(None, None), length=2) value_throttled = param.Tuple(default=None, length=2) start = param.Date(default=None) end = param.Date(default=None) step = param.Number(default=1) _widget_type = _BkDateRangeSlider def _process_property_change(self, msg): msg = super(DateRangeSlider, self)._process_property_change(msg) if 'value' in msg: v1, v2 = msg['value'] msg['value'] = (value_as_datetime(v1), value_as_datetime(v2)) if 'value_throttled' in msg: v1, v2 = msg['value_throttled'] msg['value_throttled'] = (value_as_datetime(v1), value_as_datetime(v2)) return msg
class Surface(Image, Element3D): """ A Surface represents a regularly sampled 2D grid with associated values defining the height along the z-axis. The key dimensions of a Surface represent the 2D coordinates along the x- and y-axes while the value dimension declares the height at each grid location. The data of a Surface is usually defined as a 2D array of values and either a bounds tuple defining the extent in the 2D space or explicit x- and y-coordinate arrays. """ extents = param.Tuple(default=(None, None, None, None, None, None), doc=""" Allows overriding the extents of the Element in 3D space defined as (xmin, ymin, zmin, xmax, ymax, zmax).""") group = param.String(default='Surface', constant=True) kdims = param.List(default=[Dimension('x'), Dimension('y')], bounds=(2, 2), doc=""" The Surface x and y dimensions of the space defined by the supplied extent.""") vdims = param.List(default=[Dimension('z')], bounds=(1, 1), doc=""" The Surface height dimension.""") def __init__(self, data, kdims=None, vdims=None, extents=None, **params): extents = extents if extents else (None, None, None, None, None, None) Image.__init__(self, data, kdims=kdims, vdims=vdims, extents=extents, **params) def _get_selection_expr_for_stream_value(self, **kwargs): expr, bbox, _ = super( Surface, self)._get_selection_expr_for_stream_value(**kwargs) return expr, bbox, None
class Surface(Image, Element3D): """ Surface Element represents a 3D surface in space. The data should be supplied as a dense NxM matrix. """ extents = param.Tuple(default=(None, None, None, None, None, None), doc="""Allows overriding the extents of the Element in 3D space defined as (xmin, ymin, zmin, xmax, ymax, zmax).""") key_dimensions = param.List(default=[Dimension('x'), Dimension('y')], bounds=(2, 2), doc=""" The Surface x and y dimensions of the space defined by the supplied extent.""") value_dimensions = param.List(default=[Dimension('z')], bounds=(1, 1), doc=""" The Surface height dimension.""") group = param.String(default='Surface', constant=True) def __init__(self, data, extents=None, **params): extents = extents if extents else (None, None, None, None, None, None) Image.__init__(self, data, extents=extents, **params) def range(self, dim, data_range=True): dim_idx = dim if isinstance(dim, int) else self.get_dimension_index(dim) if dim_idx in [0, 1]: l, b, r, t = self.bounds.lbrt() if dim_idx == 0: return (l, r) elif dim_idx == 1: return (b, t) return super(Image, self).range(dim, data_range=data_range)
class Theme(param.Parameterized): """The Theme model provides parameters and functionality like links to spinner images and css. - Provide theming to your Template and Application - implement a custom subclass Theme """ spinner_static_url = param.String( assets.SPINNER_PANEL_STATIC_LIGHT_400_340) spinner_url = param.String(assets.SPINNER_PANEL_BREATH_LIGHT_400_340) css = param.String() color_cycle = param.Tuple(_COLOR_CYCLE) bokeh_disable_logo = param.Boolean(True) bokeh_theme_json = param.Dict() @property def holoviews_color_cycle(self) -> Cycle: """Returns the HoloViews color Cycle to be used when plotting with the Theme as the active Theme. Returns: Cycle: A HoloViews color Cyle. """ if self.color_cycle: color_cycle = self.color_cycle else: color_cycle = _COLOR_CYCLE return Cycle(list(color_cycle)) @property def bokeh_theme(self) -> BokehTheme: """Returns the Bokeh Theme to be used when plotting with the Theme as the active Theme. Returns: BokehTheme: A Bokeh Theme """ if self.bokeh_theme_json: return BokehTheme(json=self.bokeh_theme_json) return BokehTheme(json={})
def define(cls, name, **kwargs): """ Utility to quickly and easily declare Stream classes. Designed for interactive use such as notebooks and shouldn't replace parameterized class definitions in source code that is imported. Takes a stream class name and a set of keywords where each keyword becomes a parameter. If the value is already a parameter, it is simply used otherwise the appropriate parameter type is inferred and declared, using the value as the default. Supported types: bool, int, float, str, dict, tuple and list """ params = {'name': param.String(default=name)} for k, v in kwargs.items(): kws = dict(default=v, constant=True) if isinstance(v, param.Parameter): params[k] = v elif isinstance(v, bool): params[k] = param.Boolean(**kws) elif isinstance(v, int): params[k] = param.Integer(**kws) elif isinstance(v, float): params[k] = param.Number(**kws) elif isinstance(v, str): params[k] = param.String(**kws) elif isinstance(v, dict): params[k] = param.Dict(**kws) elif isinstance(v, tuple): params[k] = param.Tuple(**kws) elif isinstance(v, list): params[k] = param.List(**kws) elif isinstance(v, np.ndarray): params[k] = param.Array(**kws) else: params[k] = param.Parameter(**kws) # Dynamic class creation using type return type(name, (Stream,), params)
class MyParameterized(param.Parameterized): enable = param.Boolean(True, doc="A sample Boolean parameter", allow_None=True) what_proportion = param.Magnitude(default=0.9) age = param.Number(49, bounds=(0, 100), doc="Any Number between 0 to 100") how_many = param.Integer() favorite_quote = param.String(default="Hello, world!") choose_file_or_folder = param.Path(search_paths='./') choose_folder = param.Foldername(search_paths="./") choose_file = param.Filename(search_paths="./") select_a_file = param.FileSelector(path='./*') select_multiple_files = param.MultiFileSelector(path='./*') favorite_color = param.ObjectSelector( default="green", objects=["red", "yellow", "green"]) favorite_fruit = param.Selector(default="Apple", objects=["Orange", "Apple", "Mango"]) select_multiple = param.ListSelector(default=[3, 5], objects=[1, 2, 3, 4, 5]) birthday = param.CalendarDate(dt.date(2017, 1, 1), bounds=(dt.date(2017, 1, 1), dt.date(2017, 2, 1))) appointment = param.Date(dt.datetime(2017, 1, 1), bounds=(dt.datetime(2017, 1, 1), dt.datetime(2017, 2, 1))) least_favorite_color = param.Color(default='#FF0000') dataset = param.DataFrame(pd.util.testing.makeDataFrame().iloc[:3]) this_strange_thing = param.Tuple(default=(False, ), allow_None=True) some_numbers = param.NumericTuple(default=(1, 2, 3.0, 4.0)) home_city = param.XYCoordinates(default=(-111.65, 40.23)) bounds = param.Range(default=(-10, 10))
class image_overlay(Operation): """ Operation to build a overlay of images to a specification from a subset of the required elements. This is useful for reordering the elements of an overlay, duplicating layers of an overlay or creating blank image elements in the appropriate positions. For instance, image_overlay may build a three layered input suitable for the RGB factory operation even if supplied with one or two of the required channels (creating blank channels for the missing elements). Note that if there is any ambiguity regarding the match, the strongest match will be used. In the case of a tie in match strength, the first layer in the input is used. One successful match is always required. """ output_type = Overlay spec = param.String(doc=""" Specification of the output Overlay structure. For instance: Image.R * Image.G * Image.B Will ensure an overlay of this structure is created even if (for instance) only (Image.R * Image.B) is supplied. Elements in the input overlay that match are placed in the appropriate positions and unavailable specification elements are created with the specified fill group.""") fill = param.Number(default=0) default_range = param.Tuple(default=(0,1), doc=""" The default range that will be set on the value_dimension of any automatically created blank image elements.""") group = param.String(default='Transform', doc=""" The group assigned to the resulting overlay.""") @classmethod def _match(cls, el, spec): "Return the strength of the match (None if no match)" spec_dict = dict(zip(['type', 'group', 'label'], spec.split('.'))) if not isinstance(el, Image) or spec_dict['type'] != 'Image': raise NotImplementedError("Only Image currently supported") sanitizers = {'group':group_sanitizer, 'label':label_sanitizer} strength = 1 for key in ['group', 'label']: attr_value = sanitizers[key](getattr(el, key)) if key in spec_dict: if spec_dict[key] != attr_value: return None strength += 1 return strength def _match_overlay(self, raster, overlay_spec): """ Given a raster or input overlay, generate a list of matched elements (None if no match) and corresponding tuple of match strength values. """ ordering = [None]*len(overlay_spec) # Elements to overlay strengths = [0]*len(overlay_spec) # Match strengths elements = raster.values() if isinstance(raster, Overlay) else [raster] for el in elements: for pos in range(len(overlay_spec)): strength = self._match(el, overlay_spec[pos]) if strength is None: continue # No match elif (strength <= strengths[pos]): continue # Weaker match else: # Stronger match ordering[pos] = el strengths[pos] = strength return ordering, strengths def _process(self, raster, key=None): specs = tuple(el.strip() for el in self.p.spec.split('*')) ordering, strengths = self._match_overlay(raster, specs) if all(el is None for el in ordering): raise Exception("The image_overlay operation requires at least one match") completed = [] strongest = ordering[np.argmax(strengths)] for el, spec in zip(ordering, specs): if el is None: spec_dict = dict(zip(['type', 'group', 'label'], spec.split('.'))) el = Image(np.ones(strongest.data.shape) * self.p.fill, group=spec_dict.get('group','Image'), label=spec_dict.get('label','')) el.vdims[0].range = self.p.default_range completed.append(el) return np.prod(completed)
class Dimension(param.Parameterized): """ Dimension objects are used to specify some important general features that may be associated with a collection of values. For instance, a Dimension may specify that a set of numeric values actually correspond to 'Height' (dimension name), in units of meters, and that allowed values must be floats greater than zero. In addition, Dimensions can be declared as cyclic, support categorical data using a finite set of allowed, ordered values and support a custom, pretty-printed representation. """ name = param.String(doc=""" Optional name associated with the Dimension. For instance, 'height' or 'weight'.""") cyclic = param.Boolean(default=False, doc=""" Whether the range of this feature is cyclic such that the maximum allowed value (defined by the range parameter) is continuous with the minimum allowed value.""") value_format = param.Callable(default=None, doc=""" Formatting function applied to each value before display.""") range = param.Tuple(default=(None, None), doc=""" Specifies the minimum and maximum allowed values for a Dimension. None is used to represent an unlimited bound.""") soft_range = param.Tuple(default=(None, None), doc=""" Specifies a minimum and maximum reference value, which may be overridden by the data.""") type = param.Parameter(default=None, doc=""" Optional type associated with the Dimension values. The type may be an inbuilt constructor (such as int, str, float) or a custom class object.""") unit = param.String(default=None, allow_None=True, doc=""" Optional unit string associated with the Dimension. For instance, the string 'm' may be used represent units of meters and 's' to represent units of seconds.""") values = param.ClassSelector(class_=(str, list), default=[], doc=""" Optional set of allowed values for the dimension that can also be used to retain a categorical ordering. Setting values to 'initial' indicates that the values will be added during construction.""" ) # Defines default formatting by type type_formatters = {} unit_format = ' ({unit})' presets = {} # A dictionary-like mapping name, (name,) or # (name, unit) to a preset Dimension object def __init__(self, name, **params): """ Initializes the Dimension object with the given name. """ if isinstance(name, Dimension): existing_params = dict(name.get_param_values()) elif (name, params.get('unit', None)) in self.presets.keys(): preset = self.presets[(str(name), str(params['unit']))] existing_params = dict(preset.get_param_values()) elif name in self.presets.keys(): existing_params = dict(self.presets[str(name)].get_param_values()) elif (name, ) in self.presets.keys(): existing_params = dict( self.presets[(str(name), )].get_param_values()) else: existing_params = {'name': name} all_params = dict(existing_params, **params) name = all_params['name'] label = name if isinstance(name, tuple): name, label = name all_params['name'] = name self.label = label if not isinstance(params.get('values', None), basestring): all_params['values'] = sorted( list(unique_array(params.get('values', [])))) elif params['values'] != 'initial': raise Exception( "Values argument can only be set with the string 'initial'.") super(Dimension, self).__init__(**all_params) def __call__(self, name=None, **overrides): """ Derive a new Dimension that inherits existing parameters except for the supplied, explicit overrides """ settings = dict(self.get_param_values(onlychanged=True), **overrides) if name is not None: settings['name'] = name return self.__class__(**settings) @property def pprint_label(self): "The pretty-printed label string for the Dimension" unit = ('' if self.unit is None else type(self.unit)( self.unit_format).format(unit=self.unit)) return safe_unicode(self.label) + safe_unicode(unit) def pprint_value(self, value): """ Applies the defined formatting to the value. """ own_type = type(value) if self.type is None else self.type formatter = (self.value_format if self.value_format else self.type_formatters.get(own_type)) if formatter: if callable(formatter): return formatter(value) elif isinstance(formatter, basestring): if isinstance(value, dt.datetime): return value.strftime(formatter) elif isinstance(value, np.datetime64): return dt64_to_dt(value).strftime(formatter) elif re.findall(r"\{(\w+)\}", formatter): return formatter.format(value) else: return formatter % value return value def __repr__(self): return self.pprint() def pprint_value_string(self, value): """ Pretty prints the dimension name and value using the global title_format variable, including the unit string (if set). Numeric types are printed to the stated rounding level. """ unit = '' if self.unit is None else ' ' + safe_unicode(self.unit) value = self.pprint_value(value) return title_format.format(name=safe_unicode(self.label), val=value, unit=unit) def __hash__(self): """ The hash allows two Dimension objects to be compared; if the hashes are equal, all the parameters of the Dimensions are also equal. """ return sum([ hash(value) for _, value in self.get_param_values() if not isinstance(value, list) ]) def __setstate__(self, d): """ Compatibility for pickles before alias attribute was introduced. """ super(Dimension, self).__setstate__(d) self.label = self.name def __str__(self): return self.pprint_label def __eq__(self, other): "Implements equals operator including sanitized comparison." dim_matches = [self.name, self.label, dimension_sanitizer(self.label)] if self is other: return True elif isinstance(other, Dimension): return bool({other.name, other.label} & set(dim_matches)) else: return other in dim_matches def __ne__(self, other): "Implements not equal operator including sanitized comparison." return not self.__eq__(other) def __lt__(self, other): "Dimensions are sorted alphanumerically by name" return self.name < other.name if isinstance( other, Dimension) else self.name < other
class VTKVolume(PaneBase): max_data_size = param.Number(default=(256**3) * 2 / 1e6, doc=""" Maximum data size transfert allowed without subsampling""") origin = param.Tuple(default=None, length=3, allow_None=True) spacing = param.Tuple(default=(1, 1, 1), length=3, doc=""" Distance between voxel in each direction""") render_background = param.Color(default='#52576e', doc=""" Allows to specify the background color of the 3D rendering. The value must be specified as an hexadecimal color string """) colormap = param.Selector(default='erdc_rainbow_bright', objects=PRESET_CMAPS, doc=""" Name of the colormap used to transform pixel value in color """) rescale = param.Boolean(default=False, doc=""" If set to True the colormap is rescale beween min and max value of the non transparent pixels Else the full range of the pixel values are used """) shadow = param.Boolean(default=True, doc=""" If set to False, then the mapper for the volume will not perform shading computations, it is the same as setting ambient=1, diffuse=0, specular=0 """) sampling = param.Number(default=0.4, bounds=(0, 1), step=1e-2, doc=""" Parameter to adjust the distance between samples used for rendering. The lower the value is the more precise is the representation but it is more computationnaly intensive """) edge_gradient = param.Number(default=0.4, bounds=(0, 1), step=1e-2, doc=""" Parameter to adjust the opacity of the volume based on the gradient between voxels """) interpolation = param.Selector( default='fast_linear', objects=['fast_linear', 'linear', 'nearest'], doc=""" interpolation type for sampling a volume. `nearest` interpolation will snap to the closest voxel, `linear` will perform trilinear interpolation to compute a scalar value from surrounding voxels. `fast_linear` under WebGL 1 will perform bilinear interpolation on X and Y but use nearest for Z. This is slightly faster than full linear at the cost of no Z axis linear interpolation. """) ambient = param.Number(default=0.2, step=1e-2, doc=""" Value to control the ambient lighting. It is the light an object gives even in the absence of strong light. It is constant in all directions. """) diffuse = param.Number(default=0.7, step=1e-2, doc=""" Value to control the diffuse Lighting. It relies on both the light direction and the object surface normal. """) specular = param.Number(default=0.3, step=1e-2, doc=""" Value to control specular lighting. It is the light reflects back toward the camera when hitting the object """) specular_power = param.Number(default=8., doc=""" Specular power refers to how much light is reflected in a mirror like fashion, rather than scattered randomly in a diffuse manner """) slice_i = param.Integer(per_instance=True, doc=""" Integer parameter to control the position of the slice normal to the X direction """) slice_j = param.Integer(per_instance=True, doc=""" Integer parameter to control the position of the slice normal to the Y direction """) slice_k = param.Integer(per_instance=True, doc=""" Integer parameter to control the position of the slice normal to the Z direction """) display_volume = param.Boolean(default=True, doc=""" If set to True, the 3D respresentation of the volume is displayed using ray casting """) display_slices = param.Boolean(default=False, doc=""" If set to true, the orthgonal slices in the three (X, Y, Z) directions are displayed. Postition of each slice can be controlled using slice_(i,j,k) parameters """) _serializers = {} _rename = {'max_data_size': None, 'spacing': None, 'origin': None} _updates = True def __init__(self, object=None, **params): super(VTKVolume, self).__init__(object, **params) self._sub_spacing = self.spacing self._volume_data = self._get_volume_data() if self._volume_data: self.param.slice_i.bounds = (0, self._volume_data['dims'][0] - 1) self.slice_i = (self._volume_data['dims'][0] - 1) // 2 self.param.slice_j.bounds = (0, self._volume_data['dims'][1] - 1) self.slice_j = (self._volume_data['dims'][1] - 1) // 2 self.param.slice_k.bounds = (0, self._volume_data['dims'][2] - 1) self.slice_k = (self._volume_data['dims'][2] - 1) // 2 @classmethod def applies(cls, obj): if ((isinstance(obj, np.ndarray) and obj.ndim == 3) or any([isinstance(obj, k) for k in cls._serializers.keys()])): return True elif 'vtk' not in sys.modules: return False else: import vtk return isinstance(obj, vtk.vtkImageData) def _get_model(self, doc, root=None, parent=None, comm=None): """ Should return the bokeh model to be rendered. """ if 'panel.models.vtk' not in sys.modules: if isinstance(comm, JupyterComm): self.param.warning( 'VTKVolumePlot was not imported on instantiation ' 'and may not render in a notebook. Restart ' 'the notebook kernel and ensure you load ' 'it as part of the extension using:' '\n\npn.extension(\'vtk\')\n') from ...models.vtk import VTKVolumePlot else: VTKVolumePlot = getattr(sys.modules['panel.models.vtk'], 'VTKVolumePlot') props = self._process_param_change(self._init_properties()) volume_data = self._volume_data model = VTKVolumePlot(data=volume_data, **props) if root is None: root = model self._link_props(model, ['colormap'], doc, root, comm) self._models[root.ref['id']] = (model, parent) return model def _update_object(self, ref, doc, root, parent, comm): self._legend = None super(VTKVolume, self)._update_object(ref, doc, root, parent, comm) def _init_properties(self): return { k: v for k, v in self.param.get_param_values() if v is not None and k not in ['default_layout', 'object', 'max_data_size', 'spacing', 'origin'] } def _update(self, model): self._volume_data = self._get_volume_data() if self._volume_data: self.param.slice_i.bounds = (0, self._volume_data['dims'][0] - 1) self.slice_i = (self._volume_data['dims'][0] - 1) // 2 self.param.slice_j.bounds = (0, self._volume_data['dims'][1] - 1) self.slice_j = (self._volume_data['dims'][1] - 1) // 2 self.param.slice_k.bounds = (0, self._volume_data['dims'][2] - 1) self.slice_k = (self._volume_data['dims'][2] - 1) // 2 model.data = self._volume_data @classmethod def register_serializer(cls, class_type, serializer): """ Register a seriliazer for a given type of class. A serializer is a function which take an instance of `class_type` (like a vtk.vtkImageData) as input and return a numpy array of the data """ cls._serializers.update({class_type: serializer}) def _volume_from_array(self, sub_array): return dict( buffer=base64encode( sub_array.ravel( order='F' if sub_array.flags['F_CONTIGUOUS'] else 'C')), dims=sub_array.shape if sub_array.flags['F_CONTIGUOUS'] else sub_array.shape[::-1], spacing=self._sub_spacing if sub_array.flags['F_CONTIGUOUS'] else self._sub_spacing[::-1], origin=self.origin, data_range=(sub_array.min(), sub_array.max()), dtype=sub_array.dtype.name) def _get_volume_data(self): if self.object is None: return None elif isinstance(self.object, np.ndarray): return self._volume_from_array(self._subsample_array(self.object)) else: available_serializer = [ v for k, v in self._serializers.items() if isinstance(self.object, k) ] if not available_serializer: import vtk from vtk.util import numpy_support def volume_serializer(imageData): array = numpy_support.vtk_to_numpy( imageData.GetPointData().GetScalars()) dims = imageData.GetDimensions()[::-1] self.spacing = imageData.GetSpacing()[::-1] self.origin = imageData.GetOrigin() return self._volume_from_array( self._subsample_array(array.reshape(dims, order='C'))) self.register_serializer(vtk.vtkImageData, volume_serializer) serializer = volume_serializer else: serializer = available_serializer[0] return serializer(self.object) def _subsample_array(self, array): original_shape = array.shape spacing = self.spacing extent = tuple( (o_s - 1) * s for o_s, s in zip(original_shape, spacing)) dim_ratio = np.cbrt( (np.prod(original_shape) / 1e6) / self.max_data_size) max_shape = tuple(int(o_s / dim_ratio) for o_s in original_shape) dowsnscale_factor = [ max(o_s, m_s) / m_s for m_s, o_s in zip(max_shape, original_shape) ] if any([d_f > 1 for d_f in dowsnscale_factor]): try: import scipy.ndimage as nd sub_array = nd.interpolation.zoom( array, zoom=[1 / d_f for d_f in dowsnscale_factor], order=0) except ImportError: sub_array = array[::int(np.ceil(dowsnscale_factor[0])), ::int( np.ceil(dowsnscale_factor[1]) ), ::int(np.ceil(dowsnscale_factor[2]))] self._sub_spacing = tuple(e / (s - 1) for e, s in zip(extent, sub_array.shape)) else: sub_array = array self._sub_spacing = self.spacing return sub_array
class LoadingStyler(param.Parameterized): """A utility that can be used to select and style the loading spinner""" spinner = param.ObjectSelector(default=DEFAULT_URL, objects=config.SPINNERS, doc="The loading spinner to use") spinner_height = param.Integer(50, bounds=(1, 100)) background_rgb = param.Tuple((255, 255, 255)) background_alpha = param.Number(0.5, bounds=(0.0, 1.0), step=0.01, doc="The background alpha") color = param.Color(config.DEFAULT_COLOR) style = param.String("", doc="The CSS Style applied to the loading spinner") settings_panel = param.Parameter( doc="A panel containing the settings of the LoadingStyler") style_panel = param.Parameter( doc="An 'invisible' HTML pane containing the css style") def __init__(self, **params): super().__init__(**params) self.settings_panel = pn.Param( self, parameters=[ "spinner", "spinner_height", "background_alpha", "color", "style", ], widgets={ "style": { "type": pn.widgets.TextAreaInput, "sizing_mode": "stretch_both", "disabled": True, } }, ) self.style_panel = pn.pane.HTML(sizing_mode="fixed", width=0, height=0, margin=0) self._toggle_color() self._update_style() @property def _spinner_url(self): spinner = self.spinner if callable(spinner): return spinner(self.color) # pylint: disable=not-callable return spinner @param.depends("spinner", watch=True) def _toggle_color(self): color_picker: pn.widgets.ColorPicker = [ widget for widget in self.settings_panel if isinstance(widget, pn.widgets.ColorPicker) ][0] color_picker.disabled = not callable(self.spinner) @param.depends("spinner", "spinner_height", "color", "background_rgb", "background_alpha", watch=True) def _update_style(self): self.style = f""" .bk.pn-loading:before {{ background-image: url('{self._spinner_url}'); background-size: auto {self.spinner_height}%; background-color: rgb({self.background_rgb[0]},{self.background_rgb[1]},{self.background_rgb[2]},{self.background_alpha}); }}""" @param.depends("style", watch=True) def _update_loading_spinner_css(self): self.style_panel.object = f"""<style>{self.style}</style>"""
class Dimension(param.Parameterized): """ Dimension objects are used to specify some important general features that may be associated with a collection of values. For instance, a Dimension may specify that a set of numeric values actually correspond to 'Height' (dimension name), in units of meters, with a descriptive label 'Height of adult males'. All dimensions object have a name that identifies them and a label containing a suitable description. If the label is not explicitly specified it matches the name. These two parameters define the core identity of the dimension object and must match if two dimension objects are to be considered equivalent. All other parameters are considered optional metadata and are not used when testing for equality. Unlike all the other parameters, these core parameters can be used to construct a Dimension object from a tuple. This format is sufficient to define an identical Dimension: Dimension('a', label='Dimension A') == Dimension(('a', 'Dimension A')) Everything else about a dimension is considered to reflect non-semantic preferences. Examples include the default value (which may be used in a visualization to set an initial slider position), how the value is to rendered as text (which may be used to specify the printed floating point precision) or a suitable range of values to consider for a particular analysis. Units ----- Full unit support with automated conversions are on the HoloViews roadmap. Once rich unit objects are supported, the unit (or more specifically the type of unit) will be part of the core dimension specification used to establish equality. Until this feature is implemented, there are two auxillary parameters that hold some partial information about the unit: the name of the unit and whether or not it is cyclic. The name of the unit is used as part of the pretty-printed representation and knowing whether it is cyclic is important for certain operations. """ name = param.String(doc=""" Short name associated with the Dimension, such as 'height' or 'weight'. Valid Python identifiers make good names, because they can be used conveniently as a keyword in many contexts.""") label = param.String(default=None, doc=""" Unrestricted label used to describe the dimension. A label should succinctly describe the dimension and may contain any characters, including Unicode and LaTeX expression.""") cyclic = param.Boolean(default=False, doc=""" Whether the range of this feature is cyclic such that the maximum allowed value (defined by the range parameter) is continuous with the minimum allowed value.""") value_format = param.Callable(default=None, doc=""" Formatting function applied to each value before display.""") range = param.Tuple(default=(None, None), doc=""" Specifies the minimum and maximum allowed values for a Dimension. None is used to represent an unlimited bound.""") soft_range = param.Tuple(default=(None, None), doc=""" Specifies a minimum and maximum reference value, which may be overridden by the data.""") type = param.Parameter(default=None, doc=""" Optional type associated with the Dimension values. The type may be an inbuilt constructor (such as int, str, float) or a custom class object.""") step = param.Number(default=None, doc=""" Optional floating point step specifying how frequently the underlying space should be sampled. May be used to define a discrete sampling of over the range.""") unit = param.String(default=None, allow_None=True, doc=""" Optional unit string associated with the Dimension. For instance, the string 'm' may be used represent units of meters and 's' to represent units of seconds.""") values = param.List(default=[], doc=""" Optional specification of the allowed value set for the dimension that may also be used to retain a categorical ordering.""") # Defines default formatting by type type_formatters = {} unit_format = ' ({unit})' presets = {} # A dictionary-like mapping name, (name,) or # (name, unit) to a preset Dimension object def __init__(self, spec, **params): """ Initializes the Dimension object with the given name. """ if 'name' in params: raise KeyError( 'Dimension name must only be passed as the positional argument' ) if isinstance(spec, Dimension): existing_params = dict(spec.get_param_values()) elif (spec, params.get('unit', None)) in self.presets.keys(): preset = self.presets[(str(spec), str(params['unit']))] existing_params = dict(preset.get_param_values()) elif spec in self.presets: existing_params = dict(self.presets[spec].get_param_values()) elif (spec, ) in self.presets: existing_params = dict(self.presets[(spec, )].get_param_values()) else: existing_params = {} all_params = dict(existing_params, **params) if isinstance(spec, tuple): name, label = spec all_params['name'] = name all_params['label'] = label if 'label' in params and (label != params['label']): if params['label'] != label: self.warning( 'Using label as supplied by keyword ({!r}), ignoring ' 'tuple value {!r}'.format(params['label'], label)) all_params['label'] = params['label'] elif isinstance(spec, basestring): all_params['name'] = spec all_params['label'] = params.get('label', spec) if all_params['name'] == '': raise ValueError('Dimension name cannot be the empty string') if all_params['label'] in ['', None]: raise ValueError( 'Dimension label cannot be None or the empty string') values = params.get('values', []) if isinstance(values, basestring) and values == 'initial': self.warning( "The 'initial' string for dimension values is no longer supported." ) values = [] all_params['values'] = list(unique_array(values)) super(Dimension, self).__init__(**all_params) @property def spec(self): "Returns the corresponding tuple specification" return (self.name, self.label) def __call__(self, spec=None, **overrides): "Aliased to clone method. To be deprecated in 2.0" return self.clone(spec=spec, **overrides) def clone(self, spec=None, **overrides): """ Derive a new Dimension that inherits existing parameters except for the supplied, explicit overrides """ settings = dict(self.get_param_values(onlychanged=True), **overrides) if spec is None: spec = (self.name, overrides.get('label', self.label)) if 'label' in overrides and isinstance(spec, basestring): spec = (spec, overrides['label']) elif 'label' in overrides and isinstance(spec, tuple): if overrides['label'] != spec[1]: self.warning( 'Using label as supplied by keyword ({!r}), ignoring ' 'tuple value {!r}'.format(overrides['label'], spec[1])) spec = (spec[0], overrides['label']) return self.__class__( spec, **{ k: v for k, v in settings.items() if k not in ['name', 'label'] }) def __hash__(self): """ The hash allows Dimension objects to be used as dictionary keys in Python 3. """ return hash(self.spec) def __setstate__(self, d): """ Compatibility for pickles before alias attribute was introduced. """ super(Dimension, self).__setstate__(d) self.label = self.name def __eq__(self, other): "Implements equals operator including sanitized comparison." if isinstance(other, Dimension): return self.spec == other.spec # For comparison to strings. Name may be sanitized. return other in [self.name, self.label, dimension_sanitizer(self.name)] def __ne__(self, other): "Implements not equal operator including sanitized comparison." return not self.__eq__(other) def __lt__(self, other): "Dimensions are sorted alphanumerically by name" return self.name < other.name if isinstance( other, Dimension) else self.name < other def __str__(self): return self.name def __repr__(self): return self.pprint() @property def pprint_label(self): "The pretty-printed label string for the Dimension" unit = ('' if self.unit is None else type(self.unit)( self.unit_format).format(unit=self.unit)) return bytes_to_unicode(self.label) + bytes_to_unicode(unit) def pprint(self): changed = dict(self.get_param_values(onlychanged=True)) if len(set([changed.get(k, k) for k in ['name', 'label']])) == 1: return 'Dimension({spec})'.format(spec=repr(self.name)) ordering = sorted(sorted(changed.keys()), key=lambda k: (-float('inf') if self.params(k).precedence is None else self.params(k).precedence)) kws = ", ".join('%s=%r' % (k, changed[k]) for k in ordering if k != 'name') return 'Dimension({spec}, {kws})'.format(spec=repr(self.name), kws=kws) def pprint_value(self, value): """ Applies the defined formatting to the value. """ own_type = type(value) if self.type is None else self.type formatter = (self.value_format if self.value_format else self.type_formatters.get(own_type)) if formatter: if callable(formatter): return formatter(value) elif isinstance(formatter, basestring): if isinstance(value, dt.datetime): return value.strftime(formatter) elif isinstance(value, np.datetime64): return dt64_to_dt(value).strftime(formatter) elif re.findall(r"\{(\w+)\}", formatter): return formatter.format(value) else: return formatter % value return unicode(bytes_to_unicode(value)) def pprint_value_string(self, value): """ Pretty prints the dimension name and value using the global title_format variable, including the unit string (if set). Numeric types are printed to the stated rounding level. """ unit = '' if self.unit is None else ' ' + bytes_to_unicode(self.unit) value = self.pprint_value(value) return title_format.format(name=bytes_to_unicode(self.label), val=value, unit=unit)
class EditableRangeSlider(CompositeWidget, _SliderBase): """ The EditableRangeSlider widget allows selecting a floating-point range using a slider with two handles and for more precise control also offers a set of number input boxes. Reference: https://panel.holoviz.org/reference/widgets/EditableRangeSlider.html :Example: >>> EditableRangeSlider( ... value=(1.0, 1.5), start=0.0, end=2.0, step=0.25, name="A tuple of floats" ... ) """ value = param.Range( default=(0, 1), doc="Current range value. Updated when a handle is dragged") value_throttled = param.Range(default=None, constant=True, doc=""" The value of the slider. Updated when the handle is released.""") start = param.Number(default=0., doc="Lower bound of the range.") end = param.Number(default=1., doc="Upper bound of the range.") step = param.Number(default=0.1, doc="Slider and number input step.") editable = param.Tuple(default=(True, True), doc=""" Whether the lower and upper values are editable.""") format = param.ClassSelector(default='0.0[0000]', class_=( str, TickFormatter, ), doc=""" Allows defining a custom format string or bokeh TickFormatter.""") show_value = param.Boolean(default=False, readonly=True, precedence=-1, doc=""" Whether to show the widget value.""") _composite_type = Column def __init__(self, **params): if not 'width' in params and not 'sizing_mode' in params: params['width'] = 300 super().__init__(**params) self._label = StaticText(margin=0, align='end') self._slider = RangeSlider(margin=(0, 0, 5, 0), show_value=False) self._slider.param.watch(self._sync_value, 'value') self._slider.param.watch(self._sync_value, 'value_throttled') self._start_edit = FloatInput(min_width=50, margin=0, format=self.format, css_classes=['slider-edit']) self._end_edit = FloatInput(min_width=50, margin=(0, 0, 0, 10), format=self.format, css_classes=['slider-edit']) self._start_edit.param.watch(self._sync_start_value, 'value') self._start_edit.param.watch(self._sync_start_value, 'value_throttled') self._end_edit.param.watch(self._sync_end_value, 'value') self._end_edit.param.watch(self._sync_end_value, 'value_throttled') sep = StaticText(value='...', margin=(0, 2, 0, 2), align='end') edit = Row(self._label, self._start_edit, sep, self._end_edit, sizing_mode='stretch_width', margin=0) self._composite.extend([edit, self._slider]) self._slider.jscallback(args={ 'start': self._start_edit, 'end': self._end_edit }, value=""" let [min, max] = cb_obj.value start.value = min end.value = max """) self._start_edit.jscallback(args={'slider': self._slider}, value=""" if (cb_obj.value < slider.start) { slider.start = cb_obj.value } else if (cb_obj.value > slider.end) { slider.end = cb_obj.value } """) self._end_edit.jscallback(args={'slider': self._slider}, value=""" if (cb_obj.value < slider.start) { slider.start = cb_obj.value } else if (cb_obj.value > slider.end) { slider.end = cb_obj.value } """) self._update_editable() self._update_layout() self._update_name() self._update_slider() self._update_value() @param.depends('editable', watch=True) def _update_editable(self): self._start_edit.disabled = not self.editable[0] self._end_edit.disabled = not self.editable[1] @param.depends('name', watch=True) def _update_name(self): if self.name: label = f'{self.name}:' margin = (0, 10, 0, 0) else: label = '' margin = (0, 0, 0, 0) self._label.param.update(**{'margin': margin, 'value': label}) @param.depends('width', 'height', 'sizing_mode', watch=True) def _update_layout(self): self._start_edit.sizing_mode = self.sizing_mode self._end_edit.sizing_mode = self.sizing_mode if self.sizing_mode not in ('stretch_width', 'stretch_both'): w = (self.width or 300) // 4 self._start_edit.width = w self._end_edit.width = w @param.depends('start', 'end', 'step', 'bar_color', 'direction', 'show_value', 'tooltips', 'name', 'format', watch=True) def _update_slider(self): self._slider.param.update( **{ 'format': self.format, 'start': self.start, 'end': self.end, 'step': self.step, 'bar_color': self.bar_color, 'direction': self.direction, 'show_value': self.show_value, 'tooltips': self.tooltips, }) self._start_edit.step = self.step self._end_edit.step = self.step @param.depends('value', watch=True) def _update_value(self): self._slider.value = self.value self._start_edit.value = self.value[0] self._end_edit.value = self.value[1] def _sync_value(self, event): with param.edit_constant(self): self.param.update(**{event.name: event.new}) def _sync_start_value(self, event): if event.name == 'value': end = self.value[1] if self.value else self.end else: end = self.value_throttled[1] if self.value_throttled else self.end with param.edit_constant(self): self.param.update(**{event.name: (event.new, end)}) def _sync_end_value(self, event): if event.name == 'value': start = self.value[0] if self.value else self.start else: start = self.value_throttled[ 0] if self.value_throttled else self.start with param.edit_constant(self): self.param.update(**{event.name: (start, event.new)})
class DateRangeSlider(_RangeSliderBase): """ The DateRangeSlider widget allows selecting a date range using a slider with two handles. Supports datetime.datetime, datetime.data and np.datetime64 ranges. Reference: https://panel.holoviz.org/reference/widgets/DateRangeSlider.html :Example: >>> import datetime as dt >>> DateRangeSlider( ... value=(dt.datetime(2025, 1, 9), dt.datetime(2025, 1, 16)), ... start=dt.datetime(2025, 1, 1), ... end=dt.datetime(2025, 1, 31), ... step=2, ... name="A tuple of datetimes" ... ) """ value = param.Tuple( default=(None, None), length=2, doc= """The selected range as a tuple of values. Updated when one of the handles is dragged. Supports datetime.datetime, datetime.date, and np.datetime64 ranges.""" ) value_start = param.Date(default=None, readonly=True, doc=""" The lower value of the selected range.""") value_end = param.Date(default=None, readonly=True, doc=""" The upper value of the selected range.""") value_throttled = param.Tuple(default=None, length=2, constant=True, doc=""" The selected range as a tuple of values. Updated one of the handles is released. Supports datetime.datetime, datetime.date and np.datetime64 ranges""") start = param.Date(default=None, doc=""" The lower bound.""") end = param.Date(default=None, doc=""" The upper bound.""") step = param.Number(default=1, doc=""" The step size. Default is 1 (day).""") _source_transforms = { 'value': None, 'value_throttled': None, 'start': None, 'end': None, 'step': None } _rename = {'name': 'title', 'value_start': None, 'value_end': None} _widget_type = _BkDateRangeSlider def _process_param_change(self, msg): msg = super()._process_param_change(msg) if msg.get('value') == (None, None): del msg['value'] elif 'value' in msg: v1, v2 = msg['value'] if isinstance(v1, dt.datetime): v1 = datetime_as_utctimestamp(v1) if isinstance(v2, dt.datetime): v2 = datetime_as_utctimestamp(v2) msg['value'] = (v1, v2) if msg.get('value_throttled') == (None, None): del msg['value_throttled'] return msg def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg: v1, v2 = msg['value'] msg['value'] = (value_as_datetime(v1), value_as_datetime(v2)) if 'value_throttled' in msg: v1, v2 = msg['value_throttled'] msg['value_throttled'] = (value_as_datetime(v1), value_as_datetime(v2)) return msg
class EditableRangeSlider(CompositeWidget, _SliderBase): """ The EditableRangeSlider extends the RangeSlider by adding text input fields to manually edit the range and potentially override the bounds. """ editable = param.Tuple(default=(True, True), doc=""" Whether the lower and upper values are editable.""") end = param.Number(default=1., doc="Upper bound of the range.") format = param.ClassSelector(default='0.0[0000]', class_=string_types + (TickFormatter, ), doc=""" Allows defining a custom format string or bokeh TickFormatter.""") show_value = param.Boolean(default=False, readonly=True, precedence=-1, doc=""" Whether to show the widget value.""") start = param.Number(default=0., doc="Lower bound of the range.") step = param.Number(default=0.1, doc="Slider and number input step.") value = param.Range(default=(0, 1), doc="Current range value.") value_throttled = param.Range(default=None, constant=True) _composite_type = Column def __init__(self, **params): if not 'width' in params and not 'sizing_mode' in params: params['width'] = 300 super().__init__(**params) self._label = StaticText(margin=0, align='end') self._slider = RangeSlider(margin=(0, 0, 5, 0), show_value=False) self._slider.param.watch(self._sync_value, 'value') self._slider.param.watch(self._sync_value, 'value_throttled') self._start_edit = FloatInput(min_width=50, margin=0, format=self.format, css_classes=['slider-edit']) self._end_edit = FloatInput(min_width=50, margin=(0, 0, 0, 10), format=self.format, css_classes=['slider-edit']) self._start_edit.param.watch(self._sync_start_value, 'value_throttled') self._end_edit.param.watch(self._sync_end_value, 'value_throttled') sep = StaticText(value='...', margin=(0, 2, 0, 2), align='end') edit = Row(self._label, self._start_edit, sep, self._end_edit, sizing_mode='stretch_width', margin=0) self._composite.extend([edit, self._slider]) self._slider.jscallback(args={ 'start': self._start_edit, 'end': self._end_edit }, value=""" let [min, max] = cb_obj.value start.value = min end.value = max """) self._start_edit.jscallback(args={'slider': self._slider}, value=""" if (cb_obj.value < slider.start) { slider.start = cb_obj.value } else if (cb_obj.value > slider.end) { slider.end = cb_obj.value } slider.value = [cb_obj.value, slider.value[1]] """) self._end_edit.jscallback(args={'slider': self._slider}, value=""" if (cb_obj.value < slider.start) { slider.start = cb_obj.value } else if (cb_obj.value > slider.end) { slider.end = cb_obj.value } slider.value = [slider.value[0], cb_obj.value] """) self._update_editable() self._update_layout() self._update_name() self._update_slider() self._update_value() @param.depends('editable', watch=True) def _update_editable(self): self._start_edit.disabled = not self.editable[0] self._end_edit.disabled = not self.editable[1] @param.depends('name', watch=True) def _update_name(self): if self.name: label = f'{self.name}:' margin = (0, 10, 0, 0) else: label = '' margin = (0, 0, 0, 0) self._label.param.set_param(**{'margin': margin, 'value': label}) @param.depends('width', 'height', 'sizing_mode', watch=True) def _update_layout(self): self._start_edit.sizing_mode = self.sizing_mode self._end_edit.sizing_mode = self.sizing_mode if self.sizing_mode not in ('stretch_width', 'stretch_both'): w = (self.width or 300) // 4 self._start_edit.width = w self._end_edit.width = w @param.depends('start', 'end', 'step', 'bar_color', 'direction', 'show_value', 'tooltips', 'name', 'format', watch=True) def _update_slider(self): self._slider.param.set_param( **{ 'format': self.format, 'start': self.start, 'end': self.end, 'step': self.step, 'bar_color': self.bar_color, 'direction': self.direction, 'show_value': self.show_value, 'tooltips': self.tooltips, }) self._start_edit.step = self.step self._end_edit.step = self.step @param.depends('value', watch=True) def _update_value(self): self._slider.value = self.value self._start_edit.value = self.value[0] self._end_edit.value = self.value[1] def _sync_value(self, event): with param.edit_constant(self): self.param.set_param(**{event.name: event.new}) def _sync_start_value(self, event): with param.edit_constant(self): self.param.set_param( **{event.name: (event.new, self.value_throttled[1])}) def _sync_end_value(self, event): with param.edit_constant(self): self.param.set_param( **{event.name: (self.value_throttled[0], event.new)})