holoviz / holoviz/param

Make it easy to use Traitlets including Ipywidgets/ AnyWidgets with Param

Open
#891 1 comment 2 reactions 0 assignees View on GitHub
type-feature
Dominant language
Python
Stars
521
Forks
86
Avg merge
1d 13h
Merged PRs (30d)
35

Description

Today you can `panel.bind` to the `value` trait of an `ipywidgets`. But ipywidgets sometimes don't have a `value` trait or sometimes it has lots of extra, interesting traits.

In https://discourse.holoviz.org/t/panel-works-with-anywidget/6466 I demonstrate how you can easily *create an observer*, A Parameterized object with parameters corresponding to the `ipywidgets` that observe for trait changes. Such an observer makes it easy to `.watch`, `@depends`, `bind` and more to the ipywidget.

Please consolidate this functionality and add it to Param.

It should probably be easy to only observe a specified selection of traits (white list) as well as a specified selection of traits not to watch (black list).

```python
# pip install panel ipywidgets_bokeh anywidget
import anywidget
import traitlets

class CounterWidget(anywidget.AnyWidget):
_esm = """
export function render({ model, el }) {
let getCount = () => model.get("count");
let button = document.createElement("button");
button.classList.add("counter-button");
button.innerHTML = `Count is ${getCount()}`;
button.addEventListener("click", () => {
model.set("count", getCount() + 1);
model.save_changes();
});
model.on("change:count", () => {
button.innerHTML = `Count is ${getCount()}`;
});
el.appendChild(button);
}
"""
_css="""
.counter-button { background-color: pink; font-size: 48px}
.counter-button:hover { background-color: pink; }
"""
count = traitlets.Int(0).tag(sync=True)

counter = CounterWidget()

# HELP FUNCTIONALITY to convert Traitlets Classes/ Events to Param Classes/ Events

import param

_ipywidget_classes = {}
_any_widget_traits = set(anywidget.AnyWidget().traits())

def create_observer(obj, traits=None)->param.Parameterized:
"""Returns a Parameterized class with parameters corresponding to the traits of the obj

Args:
traits: A list of traits to observe. If None all traits not on the base AnyWidget will be
observed.
"""
if not traits:
traits = list(set(obj.traits())-_any_widget_traits)
name = type(obj).__name__
if name in _ipywidget_classes:
observer_class = _ipywidget_classes[name]
else:
observer_class = param.parameterized_class(name, {trait: param.Parameter() for trait in traits})
_ipywidget_classes[name] = observer_class

values = {trait: getattr(obj, trait) for trait in traits}
observer = observer_class(**values)
obj.observe(lambda event: setattr(observer, event["name"], event["new"]), names=traits)
return observer

# THE PANEL APP

import panel as pn
pn.extension("ipywidgets")

observer = create_observer(counter)

def some_output(count):
return f"The count is {count}!"

component = pn.Column(counter, pn.bind(some_output, observer.param.count))

pn.template.FastListTemplate(
site="Panel",
title="Works with AnyWidget",
main=[component],
).servable()
```

https://github.com/holoviz/param/assets/42288570/d68bb4dc-d83f-44fd-8e0e-4628f75fc86f

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.