Skip to content

fix: plotly-resampler (and other FigureWidget subclasses) not working in marimo by routing FigureWidget through the anywidget formatter and syncing widget state via _repr_mimebundle_().#8430

Merged
mscolnick merged 4 commits intomainfrom
ms/plotly-resampler
Feb 23, 2026

Conversation

@mscolnick
Copy link
Contributor

Fix plotly-resampler (and other FigureWidget subclasses) not working in marimo by routing FigureWidget through the anywidget formatter and syncing widget state via _repr_mimebundle_().

Two problems, two fixes:

  1. Wrong formatter: PlotlyFormatter registered a static HTML formatter for FigureWidget. Since FigureWidget appears before anywidget.AnyWidget in the MRO, the static formatter always won — rendering FigureWidget as a non-interactive marimo-plotly component. The widget comm was never initialized, so interactive features like plotly-resampler's dynamic resampling never worked. Fix: Remove the FigureWidget registration from PlotlyFormatter so it falls through to the AnyWidgetFormatter.

  2. Stale widget state: Plotly's FigureWidget maintains a split internal model — figure data lives in _data/_layout_obj, but widget traits (_widget_data, _widget_layout) are only synced during _repr_mimebundle_(). Without this call, the widget comm sends stale/empty state to the frontend. This is critical for plotly-resampler, which populates downsampled data during this sync. Fix: Call _repr_mimebundle_() in from_anywidget() before creating the wrapper, ensuring widget traits reflect the current figure state.

Closes #4091

Route plotly `FigureWidget` through the anywidget formatter instead of the static plotly formatter.

`FigureWidget` is a subclass of `anywidget.AnyWidget`, but marimo's `PlotlyFormatter` explicitly registered a formatter for it. Since `FigureWidget` appears before `anywidget.AnyWidget` in the MRO, the static plotly formatter always won, rendering FigureWidget as a non-interactive `marimo-plotly` HTML component. This broke interactive widget features like plotly-resampler's dynamic resampling, which requires the widget comm to be initialized.

The fix removes the `FigureWidget` registration from `PlotlyFormatter`, allowing the MRO walk to reach the `anywidget.AnyWidget` formatter instead. This properly initializes the widget comm and enables interactive features.

Closes #4091
@vercel
Copy link

vercel bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 23, 2026 7:10pm

Request Review

@mscolnick mscolnick requested a review from Copilot February 23, 2026 00:51
@mscolnick mscolnick added the bug Something isn't working label Feb 23, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes plotly-resampler (and other FigureWidget subclasses) not working in marimo by addressing two fundamental issues: (1) FigureWidget was being incorrectly routed through the static Plotly formatter instead of the anywidget formatter, and (2) Plotly's FigureWidget maintains a split internal model that requires syncing via _repr_mimebundle_() before the widget comm is initialized.

Changes:

  • Removed FigureWidget registration from PlotlyFormatter to allow it to fall through to AnyWidgetFormatter via MRO
  • Added _sync_widget_state() helper in from_anywidget.py to call _repr_mimebundle_() before creating widget wrappers
  • Added comprehensive tests for both the formatter routing and end-to-end rendering behavior

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
marimo/_output/formatters/plotly_formatters.py Removed FigureWidget formatter registration so it falls through to anywidget formatter
marimo/_plugins/ui/_impl/from_anywidget.py Added widget state synchronization via _repr_mimebundle_() before wrapping
tests/_output/formatters/test_plotly_formatters.py Added unit test verifying FigureWidget uses anywidget formatter
tests/_plugins/ui/_impl/test_anywidget.py Added integration test verifying FigureWidget renders as marimo-anywidget
marimo/_smoke_tests/plotly/_plotly_resampler.py Updated smoke test structure and __generated_with version

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# Some widgets (e.g. plotly FigureWidget, plotly-resampler) only sync
# their internal data to widget traits during _repr_mimebundle_().
# Without this, the comm's initial state may be stale/empty.
_sync_widget_state(widget)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@manzt, thoughts on special casing this for plotly? or i wonder if this is other things we may see/hit with other anywidgets.

calling _repr_mimebundle_ should be cheap anyways

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is a fine fix, but want to call out that it might be a partial solution.

Calling _repr_mimebundle_() eagerly at creation time works for plotly's trait-syncing case, but I could imagine a widget that does something meaningful at display time, e.g.:

widget = mo.ui.anywidget(FooWidget(...))  # _repr_mimebundle_ fires here now
widget.update_something()
widget.update_something()
widget  # but it should really fire here

anywidget will always use _repr_mimebundle_ because that's the protocol/convention for rendering.

@mscolnick mscolnick marked this pull request as ready for review February 23, 2026 16:21
@mscolnick mscolnick requested a review from manzt February 23, 2026 16:21
Copy link
Collaborator

@manzt manzt left a comment

Choose a reason for hiding this comment

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

I think this is fine fix, just a comment.

# Some widgets (e.g. plotly FigureWidget, plotly-resampler) only sync
# their internal data to widget traits during _repr_mimebundle_().
# Without this, the comm's initial state may be stale/empty.
_sync_widget_state(widget)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is a fine fix, but want to call out that it might be a partial solution.

Calling _repr_mimebundle_() eagerly at creation time works for plotly's trait-syncing case, but I could imagine a widget that does something meaningful at display time, e.g.:

widget = mo.ui.anywidget(FooWidget(...))  # _repr_mimebundle_ fires here now
widget.update_something()
widget.update_something()
widget  # but it should really fire here

anywidget will always use _repr_mimebundle_ because that's the protocol/convention for rendering.

@mscolnick mscolnick merged commit 72ab933 into main Feb 23, 2026
36 of 43 checks passed
@mscolnick mscolnick deleted the ms/plotly-resampler branch February 23, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plotly-resampler doesn't work with marimo

3 participants