Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Documenting Interactive Widgets

The xmris package heavily utilizes AnyWidget to provide interactive, browser-based UI components (like phase correction and spectra scrolling) directly inside Jupyter Notebooks.

However, standard Jupyter widgets require a live Python kernel to handle two-way communication. This creates a problem for our static documentation (built with Sphinx/MyST), which consists of pre-rendered HTML without an active Python backend.

To solve this, we created the Universal Static Exporter (export_widget_static). This utility intercepts a live widget, extracts its synchronized state, bundles its JavaScript and CSS, and injects a mock Jupyter model to render a fully functional, standalone HTML iframe.

1. How to Use the Static Exporter

When writing documentation tutorials, you will typically show the user how to instantiate the live widget, but then actually render the static version so it appears on the website.

To do this, use the :tags: [remove-input] MyST directive to hide the static export code from the reader.

import xarray as xr
import numpy as np

# 1. Create your dummy data
da = xr.DataArray(10 / (1 + 1j * np.linspace(-20, 20, 1024)) + np.random.randn(1024) * 0.05, dims=["ppm"])

To display the widget in the docs, pass the widget factory function and all its arguments directly to export_widget_static:

# this is a dummy cell for the reader
# we remove the output with the MyST cell tag `remove-output`
da.xmr.widget.phase_spectrum()
# this cell does the heavy lifting in an actual notebook we would
# remove the cell but keep the output using the MyST cell tag `remove-input`
from xmris.visualization.widget._static_exporter import export_widget_static
from xmris.visualization.widget.phase.phase import phase_spectrum

# This will render the interactive canvas in the docs!
export_widget_static(
    phase_spectrum,     # The widget generating function
    da,                 # Positional arguments
    width=700,          # Keyword arguments
    show_grid=True      # Keyword arguments
)
Loading...

2. Widget Development Conventions

Because the static documentation version has no Python backend, buttons that trigger Python code (e.g., “Save to Workspace”, “Close”, “Apply Phase”) will not work and can trap the user in a broken state.

The Rule: If your widget has a button that relies on the live Jupyter kernel, you must add the CSS class xmris-close-btn to it in your Javascript.

// Example in my_widget.js
const closeBtn = document.createElement("button");

// Add 'xmris-close-btn' so the documentation exporter knows to hide it!
closeBtn.className = "nmr-btn nmr-btn-outline xmris-close-btn"; 
closeBtn.textContent = "Finalize & Close";

closeBtn.onclick = () => { model.send({ event: "save" }); };

The export_widget_static utility automatically searches for and hides any DOM elements with the .xmris-close-btn class.

If you need to hide additional, specific elements, you can pass them via the hide_selectors argument:

export_widget_static(
    my_widget, data, 
    hide_selectors=["#save-tooltip", ".advanced-menu"]
)

3. Handling Large Datasets & Debugging

Web browsers enforce strict size limits on standalone HTML iframes. If our static widget payload exceeds ~2.5 MB, the browser will silently fail and render a blank white box.

To prevent this, export_widget_static includes automatic float compression and aggressive safety checks. If you attempt to export an array larger than max_points (default 100_000), Python will raise a ValueError during the documentation build.

Debugging your Export: If your widget is failing to render or you want to inspect the payload size, set debug=True.

export_widget_static(
    phase_spectrum,
    da,
    debug=True
)
--- Static Export Debug: PhaseWidget ---
  [Sync] height          : int = 400
  [Sync] imags           : Array/List (Size: 1024)
  [Sync] mag             : Array/List (Size: 1024)
  [Sync] p0              : float = 0.0
  [Sync] p1              : float = 0.0
  [Sync] pivot_val       : float = 511.0
  [Sync] reals           : Array/List (Size: 1024)
  [Sync] show_grid       : bool = True
  [Sync] show_pivot      : bool = True
  [Sync] tabbable        : NoneType = None
  [Sync] tooltip         : NoneType = None
  [Sync] width           : int = 740
  [Sync] x_coords        : Array/List (Size: 1024)
  [Sync] x_label         : str = Chemical Shift [ppm]

  JSON Payload Size : 31.41 KB (0.03 MB)
  Base64 URI Size   : 69.71 KB (0.07 MB)
--------------------------------------------------
Loading...

Output:

--- Static Export Debug: PhaseWidget ---
  [Sync] width           : int = 700
  [Sync] height          : int = 400
  [Sync] x_coords        : Array/List (Size: 1024)
  [Sync] reals           : Array/List (Size: 1024)
  
  JSON Payload Size : 18.21 KB (0.02 MB)
  Base64 URI Size   : 25.12 KB (0.02 MB)
--------------------------------------------------