I heard that @philippjfr Rudiger and the Bokeh team had made it possible to use AnyWidget in the same way as other Ipywidgets with Panel. But so far I had not seen an example. So here is one.
This example is not the simplest Hello World example. Its a bit more complicated because I wanted to show how to create general functionality to observe all the traits (~parameters) of an AnyWidget
and bind to them. This is the create_observer
function.
# 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()