|
| 1 | +from functools import partial |
| 2 | +from typing import Sequence |
| 3 | + |
| 4 | +import traitlets |
| 5 | +from ipywidgets import FloatRangeSlider |
| 6 | +from ipywidgets.widgets.trait_types import TypedTuple |
| 7 | + |
| 8 | +# Import from source to allow mkdocstrings to link to base class |
| 9 | +from ipywidgets.widgets.widget_box import VBox |
| 10 | + |
| 11 | + |
| 12 | +class MultiRangeSlider(VBox): |
| 13 | + """A widget for multiple ranged sliders. |
| 14 | +
|
| 15 | + This is designed to be used with the |
| 16 | + [DataFilterExtension][lonboard.experimental.DataFilterExtension] when you want to |
| 17 | + filter on 2 to 4 columns on the same time. |
| 18 | +
|
| 19 | + If you have only a single filter, use an ipywidgets |
| 20 | + [FloatRangeSlider][ipywidgets.widgets.widget_float.FloatRangeSlider] directly. |
| 21 | +
|
| 22 | + # Example |
| 23 | +
|
| 24 | + ```py |
| 25 | + from ipywidgets import FloatRangeSlider |
| 26 | +
|
| 27 | + slider1 = FloatRangeSlider( |
| 28 | + value=(2, 5), |
| 29 | + min=0, |
| 30 | + max=10, |
| 31 | + step=0.1, |
| 32 | + description="First slider: " |
| 33 | + ) |
| 34 | + slider2 = FloatRangeSlider( |
| 35 | + value=(30, 40), |
| 36 | + min=0, |
| 37 | + max=50, |
| 38 | + step=1, |
| 39 | + description="Second slider: " |
| 40 | + ) |
| 41 | + multi_slider = MultiRangeSlider([slider1, slider2]) |
| 42 | + multi_slider |
| 43 | + ``` |
| 44 | +
|
| 45 | + Then to propagate updates to a rendered layer, call |
| 46 | + [jsdlink][ipywidgets.widgets.widget_link.jsdlink] to connect the two widgets. |
| 47 | +
|
| 48 | + ```py |
| 49 | + from ipywidgets import jsdlink |
| 50 | +
|
| 51 | + jsdlink( |
| 52 | + (multi_slider, "value"), |
| 53 | + (layer, "filter_range") |
| 54 | + ) |
| 55 | + ``` |
| 56 | +
|
| 57 | + As you change the slider, the `filter_range` value on the layer class should be |
| 58 | + updated. |
| 59 | + """ |
| 60 | + |
| 61 | + # We use a tuple to force reassignment to update the list |
| 62 | + # This is because list mutations do not get propagated as events |
| 63 | + # https://github.com/jupyter-widgets/ipywidgets/blob/b2531796d414b0970f18050d6819d932417b9953/python/ipywidgets/ipywidgets/widgets/widget_box.py#L52-L54 |
| 64 | + value = TypedTuple(trait=TypedTuple(trait=traitlets.Float())).tag(sync=True) |
| 65 | + |
| 66 | + def __init__(self, children: Sequence[FloatRangeSlider], **kwargs): |
| 67 | + # We manage a list of lists to match what deck.gl expects for the |
| 68 | + # DataFilterExtension |
| 69 | + def callback(change, *, i: int): |
| 70 | + value = list(self.value) |
| 71 | + value[i] = change["new"] |
| 72 | + self.set_trait("value", value) |
| 73 | + self.send_state("value") |
| 74 | + |
| 75 | + initial_values = [] |
| 76 | + for i, child in enumerate(children): |
| 77 | + func = partial(callback, i=i) |
| 78 | + child.observe(func, "value") |
| 79 | + initial_values.append(child.value) |
| 80 | + |
| 81 | + super().__init__(children, value=initial_values, **kwargs) |
0 commit comments