Sphinx-anywidget#9
Conversation
Adds anyplotlib/sphinx_anywidget/ — a self-contained Sphinx extension that
makes any anywidget.AnyWidget-based figure interactive in documentation
pages via Pyodide, with no server or Jupyter kernel required.
Key changes
-----------
anyplotlib/sphinx_anywidget/
__init__.py Sphinx extension entry point; auto-injects JS/CSS,
builds wheel at docs-build time, registers directive
_scraper.py Generic AnywidgetScraper (replaces ViewerScraper);
detects '# Interactive' comment tag to opt-in per block
_repr_utils.py Self-contained HTML builder (no anyplotlib dep);
uses awi_state/awi_event message protAdds anyplotlib/sphinx_anywidget/ — a self-contained Sphinx extension that
makes any anywidget.AnyWidget-based figure interactive in documentation
pages via Pyodide, with no server or Jupyter kernel required.
Key changes
-----------
anyplotlib/sphinx_anywidget/
__init__.py Sphinx extension entry point;?akes any anywidget.AnyWidget-based figure inywidget monkey-patch replaces thpages via Pyodide, with no server or Jupyter kernel required.
Key chanecKey changes
-----------
anyplotlib/sphinx_anywidget/
_ ----------inanyplotlibivate-btn, awi-static-icon)
any builds wheel at docs-build time, registers directf- _scraper.py Generic Anywie_fig_id attribute
- _push() / _push_layout() / _push_widget() always write to traitlets;
the generic monkey- _repr_utils.py Sethe change via
traitlets.observe(names=All) — no uses awi_state/awi_event message protAdds anyplstmakes any anywidget.AnyWidget-based figure interactive in documentation
pages via Pyodide, with no server or Jupyter kernel required.
Keespages via Pyodide, with no server or Jupyter kernel required.
Key channsKey changes
-----------
anyplotlib/sphinx_anywidget/
__ini_g----------f
anyplotlibtm __init__.py SphitKey chanecKey changes
-----------
anyplotlib/sphinx_anywidget/
_ ----------inanyplotlibivate-btn, awi-static-icon)
any builds wheel at docs-build time, registers to-------raits directly
anyplotlib b _ ----------inanyplogeany builds wheel at docs-build timta - _push() / _push_layout() / _push_widget() always write to traitlets;
the generic monkey- _repr_utils.py ton
- Update mock r the generic monkey- _repr_(_patched_init pattern)
Usage in other pro traitlets.observe(names=All) — no nxpages via Pyodide, with no server or Jupyter kernel required.
Keespages via Pyodide, with no server or Jupyter kernel required.
Key channsKey changes
-----------
anyplotlib/sphinxteracKeespages via Pyodide, with no server or Jupyter kerngit log --oneline -3
…teractive figures
…bridge.js for anyplotlib extension
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #9 +/- ##
===========================================
- Coverage 87.79% 73.52% -14.27%
===========================================
Files 7 12 +5
Lines 1901 2278 +377
===========================================
+ Hits 1669 1675 +6
- Misses 232 603 +371 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Long time coming.... But live responsive interactive documentation should be possible with this... I need to set up github to host docs from PRs still but once that is up it should be really nice for demoing a feature, writing documentation etc. Screen.Recording.2026-05-03.at.2.30.58.PM.mov |
There was a problem hiding this comment.
Pull request overview
This PR adds a new anyplotlib.sphinx_anywidget Sphinx extension for Pyodide-backed interactive docs, updates the existing anyplotlib iframe bridge to support parent-page communication, and adds related tests/docs plumbing. It also includes a smaller plotting change for deterministic panel IDs and more resilient colormap lookup.
Changes:
- Added a generic
sphinx_anywidgetextension with a scraper, directive, wheel builder, static bridge assets, and docs integration. - Expanded iframe/parent messaging and added Pyodide-oriented end-to-end tests plus example markers for interactive docs.
- Improved plotting internals by making panel IDs deterministic and adding fallback colormap resolution.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_pyodide_e2e.py |
Adds multi-tier Playwright/Pyodide bridge tests for iframe messaging and mocked boot flow. |
test_sphinx_anywidget.py |
Adds a root-level smoke test script for the new extension imports/helpers. |
docs/conf.py |
Enables the new Sphinx extension and adds docs-time wheel-building logic. |
docs/_static/pyodide_bridge.js |
Introduces a docs-side Pyodide bridge script for anyplotlib figures. |
docs/_sg_html_scraper.py |
Replaces the old scraper implementation with a compatibility shim. |
anyplotlib/sphinx_anywidget/static/anywidget_overlay.css |
Adds styling for per-figure activation badges. |
anyplotlib/sphinx_anywidget/static/anywidget_bridge.js |
Implements the generic anywidget Pyodide bridge and message routing. |
anyplotlib/sphinx_anywidget/_wheel_builder.py |
Adds helper logic to build and rename a stable wheel for Pyodide installs. |
anyplotlib/sphinx_anywidget/_scraper.py |
Adds the generic Sphinx Gallery scraper and interactive iframe generation. |
anyplotlib/sphinx_anywidget/_repr_utils.py |
Adds standalone HTML generation utilities for generic anywidget widgets. |
anyplotlib/sphinx_anywidget/_directive.py |
Adds the .. anywidget-figure:: directive for embedding widgets in RST pages. |
anyplotlib/sphinx_anywidget/__init__.py |
Registers the extension, assets, directive, and wheel-build hook. |
anyplotlib/figure_plots.py |
Makes panel IDs deterministic and adds colormap fallback handling. |
anyplotlib/figure.py |
Refactors event dispatch into a reusable _dispatch_event() method. |
anyplotlib/_repr_utils.py |
Extends the anyplotlib iframe model bridge for inbound/outbound postMessage state/events. |
_test_scraper.py |
Adds a root-level scraper thumbnail smoke test script. |
Examples/Interactive/plot_segment_by_contrast.py |
Marks the example as interactive for Sphinx Gallery/Pyodide docs. |
Examples/Interactive/plot_point_widget.py |
Marks the example as interactive for Sphinx Gallery/Pyodide docs. |
Examples/Interactive/plot_key_bindings.py |
Marks the example as interactive for Sphinx Gallery/Pyodide docs. |
Examples/Interactive/plot_interactive_fitting.py |
Marks the example as interactive and declares extra Pyodide packages. |
Examples/Interactive/plot_interactive_fft.py |
Marks the example as interactive for Sphinx Gallery/Pyodide docs. |
.gitignore |
Adds ignore rules for build, docs, wheel, and local environment artifacts. |
.github/workflows/docs.yml |
Builds the Pyodide wheel in docs workflows before running Sphinx. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Use a stable ID based on the source file name | ||
| stem = src_path.stem | ||
| fig_id = f"rst_{stem}" | ||
|
|
||
| docs_static = Path(env.app.outdir).parent / "_static" / "viewer_widgets" | ||
| docs_static.mkdir(parents=True, exist_ok=True) | ||
| html_name = f"{fig_id}.html" | ||
| html_path = docs_static / html_name |
| wheels = sorted(wheels_dir.glob(f"{package_name}*.whl")) | ||
| if not wheels: | ||
| print(f"\n[sphinx_anywidget] WARNING: no wheel found for {package_name!r}") | ||
| return None | ||
|
|
||
| stable = wheels_dir / f"{package_name}-0.0.0-py3-none-any.whl" | ||
| stable.unlink(missing_ok=True) | ||
| wheels[-1].rename(stable) |
| _vs = _val if isinstance(_val, str) else _jj.dumps(_val, default=str) | ||
| import js as _js | ||
| _js.window._anywidgetPush(_fid, _tname, _vs) |
| is_interactive = "interactive" in self.options | ||
| max_width = self.options.get("width", 684) |
| if not pkg: | ||
| print( | ||
| "[sphinx_anywidget] WARNING: anywidget_pyodide_package not set; " | ||
| "Pyodide interactive mode disabled." | ||
| ) | ||
| return |
| import json as _j | ||
| val = change['new'] | ||
| val_str = val if isinstance(val, str) else _j.dumps(val, default=str) | ||
| js.window._anywidgetPush(fid, tname, val_str) |
| script_tag = ( | ||
| f'<script type="text/x-python"' | ||
| f' data-fig-id="{fig_id}"' | ||
| f' data-fig-index="0"' | ||
| f' data-src-file="{stem}"' | ||
| f' data-src="{data_src}"></script>' | ||
| ) |
| @@ -0,0 +1,39 @@ | |||
| """Quick end-to-end test for the Playwright-based scraper thumbnail.""" | |||
… dynamic package detection
|
@copilot re-review after changes |
…ions Agent-Logs-Url: https://github.com/CSSFrancis/anyplotlib/sessions/5e87efc9-9236-4c2f-abda-66cedd64e096 Co-authored-by: CSSFrancis <41125831+CSSFrancis@users.noreply.github.com>
All unresolved review comments have been addressed. The main remaining items were the two test files living outside
The root-level copies were removed. The earlier-resolved issues (fig_id hash, wheel name normalisation, trait type round-trip, Commit: 25921c6 |
|
This looks great indeed! Please let me know if I can be of any help for the PR doc preview. |
… and energy-span filter
|
@ericpre it actually has to be served as an HTML server for it to work. Otherwise it's just static |
|
Yes, after starting a server, it works indeed! |
|
@ericpre should be live now. My idea here is mostly that this should be a starting point for the hyperspy docs but also for general interactive code snippets. I'd like to make it easy to go from a sphinx gallery example --> Interactive documentation as a way to help people present their data. It should be possible to load a simple 10 mb dataset and make that interactive which is kind of what I'm looking for. I'll make a couple more examples (and hopefully I can publish a couple of things along the way). |

This pull request introduces several enhancements and new features focused on improving interactive documentation support, widget communication, and robustness of the plotting library. The most significant addition is the new
sphinx_anywidgetSphinx extension, which enables fully interactive figures in documentation using Pyodide, without requiring a server or Jupyter kernel. Additionally, the PR improves the widget–parent page communication bridge, makes colormap handling more robust, and ensures deterministic panel IDs for subplot panels. Minor changes include code comments for Sphinx Gallery compatibility and a new end-to-end test for the Playwright-based thumbnail scraper.Sphinx extension for interactive documentation:
anyplotlib.sphinx_anywidgetSphinx extension, which enables interactive anywidget-based figures in documentation via Pyodide, provides a custom RST directive (.. anywidget-figure::), and integrates with Sphinx Gallery. This includes automatic wheel building for the target package and seamless asset injection. [1] [2]Widget–parent page communication improvements:
_repr_utils.pyto support robust two-way communication between widgets and the parent page usingpostMessage, including loop-prevention flags, event forwarding, and handling inbound state updates for Pyodide-based interactivity. [1] [2] [3] [4] [5]Plotting library robustness and determinism:
figure_plots.pyto prefercolorcetbut gracefully fall back tomatplotlibor a gray ramp if unavailable, making color mapping more robust across environments.Testing and Sphinx Gallery compatibility:
_test_scraper.py.# Interactivecomments to several example scripts for Sphinx Gallery compatibility. [1] [2] [3] [4] [5]Event dispatching improvements:
figure.pyto centralize event dispatch logic, allowing both traitlet observers and the Pyodide bridge to process interaction events consistently.