Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/widget-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ jobs:
required = {
"quantem/widget/static/chooselattice.js",
"quantem/widget/static/show1d.js",
"quantem/widget/static/plot2d.js",
"quantem/widget/static/show2d.js",
"quantem/widget/static/show3d.js",
"quantem/widget/static/show3dslices.js",
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ new `rcN` heading when that rc is published to TestPyPI.

## Unreleased

- Add `Plot2D` for scalar maps with calibrated Cartesian axes, colormap
selection, viewport controls, and editable Matplotlib figure export.

- Maintainer docs split pull requests into discuss-first (new widgets,
cross-widget refactors) and incremental in-widget fixes, and add a
`widget-tutorials/` upload page for the public
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ for backend setup, Colab instructions, and verification.

| Widget | Use it for | Learn more |
|---|---|---|
| `Plot2D` | Scalar maps with physical axes, color scales, and calibrated hover | [tutorial](docs/tutorials/plot2d.ipynb) · [API](docs/api/plot2d.md) |
| `Show1D` | Scientific traces, reconstruction metrics, and live monitors | [API](https://electronmicroscopy.github.io/quantem.widget/api/show1d.html) |
| `Show2D` | Images, contrast, FFTs, ROIs, profiles, and scale bars | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/show2d.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/show2d.html) |
| `Mask2D` | Draw one rectangle, square, or circle and use its Boolean mask directly in Python | [guide and API](https://electronmicroscopy.github.io/quantem.widget/api/mask2d.html) |
Expand All @@ -53,6 +54,16 @@ for backend setup, Colab instructions, and verification.

## Documentation

For a scalar map, import `Plot2D` from the same package:

```python
from quantem.widget import Plot2D

# values.shape == (len(angle), len(radius)); coordinates are bin centers.
plot = Plot2D(values, x=radius, y=angle,
x_label="Distance (Å)", y_label="Angle (°)")
```

Visit the **[quantem.widget documentation](https://electronmicroscopy.github.io/quantem.widget/)**
for installation, tutorials, API references, command-line workflows, data I/O,
HTML sharing, and WebGPU export guidance.
Expand Down
4 changes: 4 additions & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ parts:
title: Compare datasets or tilts
- file: tutorials/showptycho
- file: tutorials/show1d
- file: tutorials/plot2d
- file: tutorials/show2d
- file: tutorials/show3d
- file: tutorials/show3dslices
Expand Down Expand Up @@ -67,6 +68,7 @@ parts:
- file: api/datasets
- file: api/viewer-ui
- file: api/show1d
- file: api/plot2d
- file: api/show2d
- file: api/mask2d
- file: api/show3d
Expand Down Expand Up @@ -117,6 +119,8 @@ parts:
sections:
- file: maintainer/storyboard-show2d
title: Show2D
- file: maintainer/storyboard-plot2d
title: Plot2D
- file: maintainer/storyboard-show3d
title: Show3D
- file: maintainer/storyboard-show3dslices
Expand Down
1 change: 1 addition & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ readers and test agents.

| Widget | Class | Offline export |
|---|---|---|
| [Plot2D](plot2d) | `quantem.widget.Plot2D` | PNG from browser; Matplotlib figures via Python; no standalone HTML API |
| [Show1D](show1d) | `quantem.widget.show1d.Show1D` | state JSON, CSV, PNG/PDF via Python, interactive HTML |
| [Show2D](show2d) | `quantem.widget.show2d.Show2D` | state JSON, PNG, interactive HTML (`encoding="full"` / `encoding="uint8"`) |
| [Mask2D](mask2d) | `quantem.widget.mask2d.Mask2D` | Boolean mask and optional selected geometry in Python |
Expand Down
77 changes: 77 additions & 0 deletions docs/api/plot2d.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Plot2D

See the [interactive tutorial](../tutorials/plot2d.ipynb).

Use `quantem.widget.Plot2D` for a calibrated scalar map, such as G3 versus
distance and angle. Use Show2D for spatial images. Plot2D owns axes, colorbar,
zoom/pan and hover; it does not calculate correlations or train a model.

```python
import numpy as np
import quantem.widget as qw

qw.profile(check_updates=False)
radius = (np.arange(100) + 0.5) * 0.1
angle = (np.arange(36) + 0.5) * 5
values = np.cos(np.deg2rad(angle[:, None])) ** 2 * radius[None, :]
plot = qw.Plot2D(
values, x=radius, y=angle,
x_label="Second-neighbor distance (Å)", y_label="Shared-root angle (°)",
colorbar_label="Illustrative value", width=500, max_width=600,
)
plot
```

`x` contains column bin centers; `y` contains row bin centers. Both must be
increasing uniform grids. Row zero is at the bottom, matching Cartesian plots.
Source values and browser hover transport retain float64. The shared QuantEM
WebGPU colormap renderer uses float32 display buffers. Canvas fallback is
explicitly labeled when WebGPU is unavailable; it is not GPU acceleration.

Wheel over the map to zoom, then drag to pan. Zoom buttons and Reset View are
also available. The Color menu changes map and colorbar together; each plot is
independent. A browser-local animation-frame scheduler handles gestures without
Python round trips. Stable view bounds are saved after interaction.

Map pixels, hover values and color-scale metadata update together after
rendering completes, including during rapid replacements.

`plot.set_data(next_values)` preserves the original grid, color limits and
viewport. `plot.horizontal_line = 92.5` adds an angle-reading line without
resending the map. `plot.figure()` returns a closed Matplotlib figure with
the current axes, values, colormap and viewport, for example
`plot.figure().savefig("g3.svg")`. By default, saved snapshots omit the map array and retain a static PNG
preview. Use `save_state=True` to embed the complete float64 map for interactive
restoration in supporting frontends. Static previews record the view when
Python renders the preview or creates a full snapshot; they do not track
browser-only gestures continuously. Notebook-manager save/restore behavior
varies by frontend. Keep scientific data files separately.

## Current scope

This API targets small, finite scalar maps, not large spatial images. It does
not support nonuniform coordinates, logarithmic axes, or standalone
`export_html`. Full interactive embedding is opt-in with `save_state=True`;
keep large data outside notebooks. Static previews use stride sampling above
512 bins per axis, preserve calibrated bounds, and are for viewing only. Controls, axes and the canvas follow the
notebook or documentation light/dark theme.

## Reference

```{eval-rst}
.. autoclass:: quantem.widget.Plot2D
:members: set_data, figure
```

## Interactive controls

| Control | Behavior |
|---|---|
| Color | Recolor the map and scale without modifying values. |
| Zoom In / Zoom Out | Zoom about the viewport center. |
| Wheel / drag | Zoom about the pointer; pan within the full grid. |
| Reset View / double-click | Restore full physical bounds. |
| Save PNG | Save the current canvas, including labels and color scale. |
| Hover | Inspect original `(row, col)`, calibrated coordinates and value. |

The [storyboard](../maintainer/storyboard-plot2d.md) defines browser signoff.
20 changes: 20 additions & 0 deletions docs/maintainer/storyboard-plot2d.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Plot2D storyboard

Use the [Plot2D tutorial](../tutorials/plot2d.ipynb) for the deterministic
calibration fixture. Repeat with a representative measured or computed scalar
map for scientific signoff. These are required stories, not completed results.

| ID | Action | Required evidence |
|---|---|---|
| P2D-01 | Hover low/high row and column bins | Physical bin centers and original float64 values match the input; row zero is at the bottom. |
| P2D-02 | Wheel zoom in/out, drag, then Reset View | Scientific pixels and axis limits change together; reset restores the full grid; source values stay unchanged. |
| P2D-03 | Change Color on one of two plots | Map and colorbar change together, numerical limits stay fixed, and the other plot stays unchanged. |
| P2D-04 | Move the tutorial angle slider; replace values with set_data | Reading line moves without resending data; replacement map updates without changing viewport or color limits. |
| P2D-05 | Use narrow/wide layouts, light/dark notebook themes | Labels, menus and axes remain readable; max_width is respected; no clipped controls. |
| P2D-06 | Save PNG, export a Matplotlib figure | Both exports show the current axes, values, viewport, color limits and reading line. |
| P2D-07 | Save notebook after interaction, close and reopen | Test default static preview and opt-in save_state=True separately. Default snapshot omits data_bytes; opt-in preserves float64 arrays. Saved view restores without a kernel where supported; payload size and fallback freshness are stated. |
| P2D-08 | Rapidly replace maps/colormaps while hovering; close during rendering | Hover corresponds to the visible map; stale asynchronous work cannot paint later; resources are released without errors. |

Record browser/adapter, map shape and dtype, screenshots before/after, console
errors, first-paint time and gesture-to-paint latency. Python state tests and
cell execution alone are not browser signoff.
167 changes: 167 additions & 0 deletions docs/tutorials/plot2d.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Inspect a calibrated scalar map\n",
"\n",
"Use Plot2D when each array axis is a scientific quantity, such as distance and angle, rather than image position. This small synthetic map teaches display and selection; it is not a physical G3 calculation or a model prediction.\n",
"\n",
"Run using a development build containing Plot2D. No GPU training, real-data download, or learned weights are required."
],
"id": "plot2d-00"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import ipywidgets as widgets\n",
"import quantem.widget as qw\n",
"\n",
"qw.profile(check_updates=False)"
],
"id": "plot2d-01"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Make a known map\n",
"\n",
"Columns are radius bin centers in Å; rows are angle bin centers in degrees. Row zero is shown at the bottom. The illustrative peak is near 2.4 Å and 110°. The 36 × 60 float64 map occupies about 17 kB before widget metadata."
],
"id": "plot2d-02"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"hide-input"
],
"mystnb": {
"code_prompt_show": "Show illustrative map generation"
}
},
"outputs": [],
"source": [
"radius = (np.arange(60) + 0.5) * 0.1\n",
"angle = (np.arange(36) + 0.5) * 5.0\n",
"values = np.exp(-((radius[None, :] - 2.4) / 0.35) ** 2\n",
" - ((angle[:, None] - 110.0) / 15.0) ** 2)"
],
"id": "plot2d-03"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Inspect with physical axes\n",
"\n",
"Hover near the peak and read its bin, radius, angle and value. Wheel to zoom, drag to pan, and select Reset View. Changing Color changes only the display. The source and hover values remain float64; browser colormapping uses float32 display buffers."
],
"id": "plot2d-04"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plot = qw.Plot2D(\n",
" values, x=radius, y=angle,\n",
" x_label=\"Distance (Å)\", y_label=\"Angle (°)\",\n",
" colorbar_label=\"Illustrative value\", title=\"Calibrated map\",\n",
" width=500, max_width=600, vmin=0, vmax=1, save_state=True,\n",
")\n",
"plot"
],
"id": "plot2d-05"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Move a reading line\n",
"\n",
"The slider selects an angle row; it does not change the data. The link is browser-local and does not require Python callbacks during dragging."
],
"id": "plot2d-06"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"angle_slider = widgets.FloatSlider(\n",
" value=107.5, min=2.5, max=177.5, step=5.0,\n",
" description=\"Angle (°)\", continuous_update=True,\n",
")\n",
"plot.horizontal_line = angle_slider.value\n",
"angle_link = widgets.jslink((angle_slider, \"value\"), (plot, \"horizontal_line\"))\n",
"angle_slider"
],
"id": "plot2d-07"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Replace values, keep the view\n",
"\n",
"Zoom first, then run the next cell. The same plot updates in place, retaining the grid, color limits and viewport. A second Plot2D display is not created."
],
"id": "plot2d-08"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plot.set_data(values * 0.75)"
],
"id": "plot2d-09"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Obtain an editable figure\n",
"\n",
"The figure retains the current data, labels and viewport. This is a deliberate static export preview, separate from the interactive plot; it is displayed once. To save it, call `figure.savefig(\"scalar-map.svg\")`.\n",
"\n",
"The widget follows the notebook light/dark theme. By default, saved snapshots omit the array and keep a static PNG preview. This small tutorial explicitly uses `save_state=True` so its complete float64 map remains interactive in supporting notebook and documentation frontends. Retain scientific data separately. Standalone HTML export is not part of this API."
],
"id": "plot2d-10"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"figure = plot.figure()\n",
"figure"
],
"id": "plot2d-11"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading