Build a basic widget with AnyWidget in a Solara app
Let’s build the app below (try it out by clicking on the button and moving the slider).
AnyWidget is a Python library that simplifies creating and publishing custom Jupyter Widgets. Since Jupyter Widgets have a VIP treatment in Solara ☀️, we expect them to work especially well there. This is indeed the case.
First things first, let’s install AnyWidget and Solara ☀️.
$ pip install anywidget solara
Let’s now take the starting example from AnyWidget:
import anywidget
import traitlets
class CounterWidget(anywidget.AnyWidget):
= """
_esm 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);
}
export default { render };
"""
="""
_css .counter-button { background-color: #ea580c; }
.counter-button:hover { background-color: #9a3412; }
"""
= traitlets.Int(0).tag(sync=True) count
We can create our Solara app by adding just a few lines of code:
import anywidget
import traitlets
class CounterWidget(anywidget.AnyWidget):
= """
_esm 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);
}
export default { render };
"""
="""
_css .counter-button { background-color: #ea580c; }
.counter-button:hover { background-color: #9a3412; }
"""
= traitlets.Int(0).tag(sync=True)
count
import solara
@solara.component
def Page():
with solara.Column(style={"padding":"30px"}):
"#Anywidget+Solara")
solara.Markdown(
CounterWidget.element() Page()
From Solara documentation, we know that in Solara, we should not create widgets, but elements instead. We can create elements by using the .element(...)
method (as we did above). This method takes the same arguments as the widget constructor, but returns an element instead of a widget. The element can be used in the same way as a widget, but it is not a widget. It is a special object that can be used in Solara.
To make it more interesting, let’s modify our CounterWidget
by changing the _css
and adding some confetti from the canvas-confetti javascript package.
import anywidget
import traitlets
class CounterWidget(anywidget.AnyWidget):
= """
_esm import confetti from "https://esm.sh/canvas-confetti@1.6.0"
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()}`;
confetti({ angle: getCount() });
});
el.appendChild(button);
}
export default { render };
"""
="""
_css .counter-button { background:blue; padding:10px 50px;}
.counter-button:hover { background-color:green; }
"""
= traitlets.Int(0).tag(sync=True)
count
import solara
@solara.component
def Page():
with solara.Column(style={"padding":"30px"}):
"#Anywidget+Solara")
solara.Markdown(
CounterWidget.element() Page()
If you want to access the value of the CounterWidget
counter, we can do it through a reactive variable counter
(thanks to Jonathan and Maarten for this suggestion):
import anywidget
import traitlets
class CounterWidget(anywidget.AnyWidget):
= """
_esm import confetti from "https://esm.sh/canvas-confetti@1.6.0"
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()}`;
confetti({ angle: getCount() });
});
el.appendChild(button);
}
export default { render };
"""
="""
_css .counter-button { background:blue; padding:10px 50px;}
.counter-button:hover { background-color:green; }
"""
= traitlets.Int(0).tag(sync=True)
count
import solara
= solara.reactive(0)
counter @solara.component
def Page():
with solara.Column(style={"padding":"30px"}):
"#Anywidget+Solara")
solara.Markdown(=counter.value, on_count=counter.set)
CounterWidget.element(countf"## Counter value is {counter.value}")
solara.Markdown( Page()
We can add a slider from Jupyter Widgets and link it to the reactive variable counter
.
import anywidget
import traitlets
class CounterWidget(anywidget.AnyWidget):
= """
_esm import confetti from "https://esm.sh/canvas-confetti@1.6.0"
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()}`;
confetti({ angle: getCount() });
});
el.appendChild(button);
}
export default { render };
"""
="""
_css .counter-button { background:blue; padding:10px 50px;}
.counter-button:hover { background-color:green; }
"""
= traitlets.Int(0).tag(sync=True)
count
import solara
import ipywidgets as widgets
= solara.reactive(0)
counter @solara.component
def Page():
with solara.Column(style={"padding":"30px"}):
"#Anywidget+Solara")
solara.Markdown(=counter.value, on_count=counter.set)
CounterWidget.element(countmin=-180, max=180, value=counter.value, on_value=counter.set)
widgets.IntSlider.element(f"## Counter value is {counter.value}")
solara.Markdown( Page()
And that’s all. You can generate some other confetti animations from here. Create your own widgets with AnyWidget or use widgets from Jupyter Widgets or Solara itself and add them to your Solara app.