ipywidgets: How to make a double slider widget that accepts a callback for its observe method

Viewed 26

I am trying to create an object that behaves like an ipywidget, but contains two sliders instead of a single one. This object would have a value property that would return a dict of values (one entry for each slider).

I would also like this widget to be embeddable inside other container widgets (VBox, HBox).

I can create a custom class which contains two sliders, and looks like a widget by giving it the proper _repr_mimebundle_:

import ipywidgets as ipw

class SliderWidget:
    def __init__(self):
        self._sliders = {name: ipw.IntSlider(description=name) for name in ('slider1', 'slider2')}

    def observe(self, callback, **kwargs):
        for sl in self._sliders.values():
            sl.observe(callback, **kwargs)

    @property
    def value(self):
        return {name: sl.value for name, sl in self._sliders.items()}

    @value.setter
    def value(self, val):
        for name, v in val.items():
            self._sliders[name].value = v
    
    def _repr_mimebundle_(self, include=None, exclude=None):
        return ipw.VBox(list(self._sliders.values()))._repr_mimebundle_(include=include, exclude=exclude)

which then looks as it should

enter image description here

and also works well with the observe method

def func(change):
    print(change)

sw.observe(func, names='value')

However, it cannot be embedded in other widgets, because it is not a Widget itself:

ipw.HBox([sw, sw])

TraitError: The 'children' trait of a HBox instance contains an Instance of a TypedTuple which expected a Widget, not the SliderWidget at '0x7fbb10757b20'.

To fix this, I have tried to inherit from VBox, which works nicely

class SliderWidget(ipw.VBox):
    def __init__(self):
        self._sliders = {name: ipw.IntSlider(description=name) for name in ('slider1', 'slider2')}
        super().__init__(list(self._sliders.values()))

    # def observe(self, callback, **kwargs):
    #     for sl in self._sliders.values():
    #         sl.observe(callback, **kwargs)

    @property
    def value(self):
        return {name: sl.value for name, sl in self._sliders.items()}

    @value.setter
    def value(self, val):
        for name, v in val.items():
            self._sliders[name].value = v

and can be embedded inside other widgets enter image description here

but unfortunately, I cannot seem to get the observe to work. If I uncomment the overriding of the observe method, I get this error upon creating the widget:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [68], in <cell line: 1>()
----> 1 sw = SliderWidget()
      2 sw

File ~/software/miniconda3/envs/mini/lib/python3.9/site-packages/traitlets/traitlets.py:1183, in HasDescriptors.__new__(*args, **kwargs)
   1181 else:
   1182     inst = new_meth(cls, *args, **kwargs)
-> 1183 inst.setup_instance(*args, **kwargs)
   1184 return inst

File ~/software/miniconda3/envs/mini/lib/python3.9/site-packages/traitlets/traitlets.py:1224, in HasTraits.setup_instance(*args, **kwargs)
   1222 self._trait_validators = {}
   1223 self._cross_validation_lock = False
-> 1224 super(HasTraits, self).setup_instance(*args, **kwargs)

File ~/software/miniconda3/envs/mini/lib/python3.9/site-packages/traitlets/traitlets.py:1206, in HasDescriptors.setup_instance(*args, **kwargs)
   1204 else:
   1205     if isinstance(value, BaseDescriptor):
-> 1206         value.instance_init(self)

File ~/software/miniconda3/envs/mini/lib/python3.9/site-packages/traitlets/traitlets.py:1148, in ObserveHandler.instance_init(self, inst)
   1147 def instance_init(self, inst):
-> 1148     inst.observe(self, self.trait_names, type=self.type)

TypeError: observe() takes 2 positional arguments but 3 were given
0 Answers
Related