LogSlider: a slider for parameters with a high dynamic range

This slider has a log10 relation between the value and the slider location.

In engineering and physics a lot of relevant parameters span over multiple orders of magnitude. When using a slider with a linear scale this either gives a too small range, or too little granularity for the lowest values. This slider maps the value of the slider location to 10^value, which yields better control for such cases.

I have not tested it thoroughly yet. One issue is that margins are not the same as a standard slider. I am not sure if this is the best way to approach this; happy to hear your feedback.


import panel as pn
import numpy as np
import param

class LogSlider(pn.custom.PyComponent, pn.custom.WidgetBase):
    """ """
    start = param.Number(default=0.1, bounds=(0, None), doc="The lower bound.")
    end = param.Number(default=1000.0, bounds=(0, None), doc="The upper bound.")
    value = param.Number(default=1.0, bounds=(0, None), allow_None=True, doc="""
        The selected floating-point value of the slider. Updated when
        the handle is dragged.""")
    step = param.Number(default=0.1, bounds=(0, None), doc="Defines step size according to a multiplication of 10^step.")
    _slider = param.ClassSelector(default=pn.widgets.FloatSlider(show_value=False), class_=pn.widgets.FloatSlider, allow_refs=True)
    format = param.String(".2e", doc="""A custom format string.""")

    def __init__(self, **params):
        super().__init__(**params)
        self._process_range_change()
        self._process_value_change()    

    @param.depends('_slider.value', watch=True)
    def _process_slider_change(self):
        self.value = 10 ** self._slider.value 

    @param.depends('value', watch=True)
    def _process_value_change(self):
        with param.parameterized.discard_events(self._slider):
            self._slider.value = np.log10(self.value)
        self._slider.name = f"{self.name}: {self.value:{self.format}}"

    @param.depends('start', 'end', 'step', watch=True)
    def _process_range_change(self):
        self._slider.start = np.log10(self.start)
        self._slider.end = np.log10(self.end)
        self._slider.step = self.step

    def __panel__(self):
        return self._slider

    
LogSlider().servable()
2 Likes

Awesome! Maybe we can implement in Panel too…