Developer Documentation#

This guide covers everything you need to contribute to anyplotlib — from setting up your environment to writing documentation and interactive gallery examples.


Environment Setup#

anyplotlib uses uv for dependency management.

# Clone and install all dev dependencies
git clone https://github.com/CSSFrancis/anyplotlib.git
cd anyplotlib
uv sync

# Run the full test suite
uv run pytest anyplotlib/tests/

The dev dependency group (declared in pyproject.toml) pulls in pytest, playwright, sphinx, docutils, and other tools needed for both tests and docs builds.


Architecture Overview#

The library is split into focused subpackages.

Module

Purpose

anyplotlib/figure/

Figure — the only anywidget.AnyWidget subclass. Owns all traitlets and is the Python ↔ JS bridge. Also contains GridSpec, SubplotSpec, and subplots().

anyplotlib/axes/

Axes and InsetAxes. Plain Python classes — no traitlets.

anyplotlib/plot1d/

Plot1D and PlotBar. Plain Python classes — no traitlets.

anyplotlib/plot2d/

Plot2D and PlotMesh. Plain Python classes — no traitlets.

anyplotlib/plot3d/

Plot3D. Plain Python class — no traitlets.

figure_esm.js

Pure-JS canvas renderer (≈ 4 000 lines).

anyplotlib/markers.py

Static visual overlays (circles, arrows, lines, etc.).

anyplotlib/widgets/

Interactive draggable overlays (RectangleWidget, CrosshairWidget, etc.).

anyplotlib/callbacks.py

Multi-tier event system (on_changed / on_release).

anyplotlib/sphinx_anywidget/

Sphinx extension for interactive docs via Pyodide.

Python → JS flow: plot._push()figure._push(panel_id) → serialises _state to JSON → writes to the dynamic traitlet panel_{id}_json (sync=True) → JS observer re-renders.

JS → Python flow: JS writes back to panel_{id}_json after a drag → Python observer calls Widget._update_from_js() and fires callbacks.


Running & Writing Tests#

Tests live in anyplotlib/tests/

Run the full suite:

uv run pytest anyplotlib/tests/

Run a specific module:

uv run pytest anyplotlib/tests/test_documentation/test_sphinx_anywidget.py -v

The Playwright end-to-end tests (test_pyodide_e2e.py) require the Playwright browsers. Install them once with:

uv run playwright install chromium

Writing Sphinx Documentation#

The docs are built with Sphinx using the pydata-sphinx-theme.

# Build HTML docs (outputs to build/html/)
make html

# Wipe build artefacts and rebuild from scratch
make clean && make html

The conf.py lives at docs/conf.py and already registers these extensions:

  • sphinx.ext.autodoc / autosummary — API reference from docstrings.

  • sphinx_gallery.gen_gallery — auto-generates the Examples gallery.

  • anyplotlib.sphinx_anywidget — interactive Pyodide figures.

  • sphinx_design — grid cards used on the index page.

Adding a new RST page#

  1. Create docs/my_page.rst.

  2. Add it to the toctree in docs/index.rst:

    .. toctree::
       :hidden:
       :maxdepth: 2
    
       my_page
    

Embedding a static figure in RST#

Use the .. anywidget-figure:: directive to embed an anyplotlib figure directly from a Python script, without Sphinx Gallery:

.. anywidget-figure:: ../Examples/PlotTypes/plot_image2d.py

The directive executes the script, captures the widget, renders it as a self-contained iframe, and embeds it in the page.

Embedding an interactive figure in RST#

Add the :interactive: flag to enable the ⚡ Pyodide activation badge:

.. anywidget-figure:: ../Examples/PlotTypes/plot_image2d.py
   :interactive:

When a reader clicks the badge, Pyodide boots in the browser, installs the anyplotlib wheel that was built at docs-build time, re-executes the script, and re-wires all live callbacks — no server required.

You can also control the display width:

.. anywidget-figure:: ../Examples/PlotTypes/plot_image2d.py
   :interactive:
   :width: 500

Declaring extra Pyodide packages#

If your example script needs additional pure-Python packages available in Pyodide, declare them at the top of the file:

_PYODIDE_PACKAGES = ["scipy", "scikit-image"]

The sphinx_anywidget extension (and the Sphinx Gallery scraper) detect this list automatically and pass it to micropip before executing the example.



Changelog Management#

anyplotlib uses towncrier for changelogs. Add a news fragment for every user-visible change:

# <issue_number>.<type>.rst  where type is one of:
#   new_feature | bugfix | deprecation | removal | doc | maintenance
echo "- Fixed the thing." > upcoming_changes/42.bugfix.rst

# Preview what the next changelog entry will look like
uv run towncrier --draft

# Build the changelog (done by maintainers before a release)
uv run towncrier build --version 0.2.0