Skip to content

Conversation

azjps
Copy link

@azjps azjps commented Sep 13, 2025

Motivation

Refer to #5096 for details: add support for anywidgets to be rendered in NiceGUI, such as the 70+ examples from the anywidgets gallery: https://try.anywidget.dev/. For example, this solves #657 (adding altair support); for convenience, this PR exposes a ui.altair(chart: altair.Chart) although its a one-line wrapper around ui.anywidget().

Roughly speaking the flow is:

  • python->JS: Use traitlets.HasTraits.observe() to listen to changes to anywidget's traitlets; on observing changes, call nicegui.Element.run_method("update_trait", <trait>) to invoke any callbacks on JS side registered by model.on("change:<trait>") in the anywidget'sesm
  • JS->python: model.save_changes() $emits the state of traits to ValueElement._handle_value_change, where any changed traits will be set on the anywidget

It also might simplify feature requests for other pure-JS libraries, for example it took me a couple of minutes to vibe code a TradingView LightWeight chart widget (#813) that I iterated on in Jupyter and then copy-pasted into NiceGUI, without needing to add any code to NiceGUI.

Implementation

I borrowed most of the structure behind the AFM implementation from the few examples out there such as marimo.

Known issues:

  • The model.send() method in AFM is left unimplemented, haven't checked how its used
  • The cleanup methods returned by initialize() / render() are not currently used
  • model.save_changes() currently sends all traits back to python including unchanged traits, which is unnecessarily expensive
  • altair: JS errors when altair.params are updated but this is an issue with altair: JupyterChart accesses .changed property in callbacks vega/altair#3868
  • Right now linking nicegui --> anywidget via python-side on_change callbacks is very jittery -- see the recording below, I'm not sure what throttling / debouncing needs to applied. Is there an easy way to link the two NiceGUI elements on the JS side?
Screen.Recording.2025-09-13.at.11.57.08.PM.mov

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • Pytests have been added (or are not necessary).
  • Documentation has been added (or is not necessary).

callbacks: {},
get: function (key) {
log('Getting value for', key, ':', this.attributes[key]);
const value = this.attributes[key];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this just return value, this raises an error:

Uncaught (in promise) DataCloneError: Failed to execute 'structuredClone' on 'Window': #<Object> could not be cloned.
    at reembed (1b7ef5d7-1cf7-4947-8f06-0f33e5db9f95:29:20)
    at Object.render (1b7ef5d7-1cf7-4947-8f06-0f33e5db9f95:146:11)
    at anywidget.js:96:47

@evnchn
Copy link
Collaborator

evnchn commented Sep 13, 2025

Thank you @azjps!

A very interesting PR which opens up a lot of possibilities.

May be a better idea to target this for 3.0 branch since I'm not sure how many releases will be left for 2.x, and 3.0 has different JS dependency management and other changes. Also considering release schedule and workload.

But please don't make that change until either @falkoschindler or @rodja agree - I'm just suggesting.

@evnchn evnchn added feature Type/scope: New feature or enhancement 🟡 medium Priority: Relevant, but not essential labels Sep 13, 2025
@evnchn
Copy link
Collaborator

evnchn commented Sep 16, 2025

#5129 (comment)

no new feature release ... (2.4.25? Assume is typo)

@rodja if I understand correctly there will not be a 2.25, if so @azjps unfortunately you may have to adapt the code for 3.0's JS dependency management...

@azjps
Copy link
Author

azjps commented Sep 18, 2025

@evnchn I suppose for #5021? Currently there's no 3rd party npm dependencies so if I rebase on top of the 3.0 branch, the examples still run fine. But I'm not too sure where the JS files should go now (in particular this one: nicegui/elements/lib/anywidget/widget.js) or if there's some better way to organize.

@evnchn
Copy link
Collaborator

evnchn commented Sep 18, 2025

Oh I thought nicegui/elements/lib/anywidget/widget.js was from NPM... I also recalled that there were merge conflicts, but now I don't see any (maybe I misremembered)? Sorry for the confusion.

Yes there is some changes in how things are organized in 3.0, but no I'm not familiar with it since I resumed contribution recently.

Regardless, it looks like this will be in 3.0 so whether we merge this into main then the 3.0 branch, or merge this into 3.0 then that goes into main, just Git logistics at this point. It'll be fine either way.

@falkoschindler
Copy link
Contributor

Very very interesting, @azjps! Unfortunately it'll have to wait until after 3.0. I'm looking forward to reviewing it!

@falkoschindler falkoschindler added this to the 3.x milestone Sep 23, 2025
@azjps
Copy link
Author

azjps commented Sep 26, 2025

@falkoschindler no problem, maybe it would be nice to include it as a selling feature with the 3.0 release? The current PR still works with 3.0.

@evnchn
Copy link
Collaborator

evnchn commented Sep 26, 2025

#5129 (review)

Trying to feature-freeze for 3.0, but you can argue this is an independent new element so we can slip it in though.

Main limiting factor is manpower to review, I think.

To be honest, feature-freezing is mutually in opposition to a banger 3.0 release, but I guess we have to strike a balance somewhere.

@azjps
Copy link
Author

azjps commented Oct 5, 2025

@evnchn Got it, yeah IMHO this is a new feature that is disjoint from any existing code, but up to you guys when you have capacity to roll out. I've added a trivial unit test and some barebones website documentation now to hopefully make it easier to review, can maybe flesh out a fuller example if I have some time but it would effectively just be an addendum to the anywidget documentation (https://anywidget.dev/en/getting-started/).

azjps added 5 commits October 5, 2025 13:53
Details: https://anywidget.dev/en/afm/#what-is-afm AFM describes the
basic contract for front-end frameworks to support anywidgets, namely
calling render({model, el}) to create a widget and implementing the
model.get/set/save_changes() API to synchronize state to the backend,
and .on() to register callbacks for changes from the backend.

Implement AFM by having set() $emit a update:traits callback
with the wiring done in ValueElement (which listens to
update:VALUE_PROP). Conversely, register traitlets observe()
handlers to call Element.run_method() to emit model callbacks
registered by on().

Add a simple example page which shows an anywidget and nicegui
button synchronized, and a altair slider and nicegui slider
synchronized.
* Only console.log when _debug property is set (default: False)
* Remove patched altair.JupyterChart; have just left a comment
explaining that the implementation is buggy currently
* Other minor formatting / docs cleanup
Altair is one of the more popular plotting libraries,
so add a dedicated wrapper for ease of reference,
although the implementation is just a very thin
wrapper over anywidget.

Also add an example of altair usage.
Move anywidget related python/JS code to anywidget/ directory.
@falkoschindler falkoschindler added 🟠 major Priority: Important, but not urgent and removed 🟡 medium Priority: Relevant, but not essential labels Oct 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New feature or enhancement 🟠 major Priority: Important, but not urgent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants