Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
752e538
chore: add .worktrees/ to .gitignore
CSSFrancis May 11, 2026
dd77825
refactor: split widgets.py into widgets/ package (_base, _widgets2d, …
CSSFrancis May 11, 2026
641a446
fix: restore Callable type annotations in widgets/_base.py
CSSFrancis May 11, 2026
cbea202
refactor: extract shared helpers into _utils.py
CSSFrancis May 11, 2026
24bbc73
fix: remove dead top-level import base64 from _utils.py
CSSFrancis May 11, 2026
d935b5e
refactor: extract Plot3D into plot3d/ subpackage
CSSFrancis May 11, 2026
46f6bb2
refactor: extract Plot2D and PlotMesh into plot2d/ subpackage
CSSFrancis May 11, 2026
a80de93
fix: add module docstrings to subpackage __init__.py files
CSSFrancis May 11, 2026
6733cf8
refactor: extract Plot1D, Line1D, PlotBar into plot1d/ subpackage
CSSFrancis May 11, 2026
5457ba5
fix: remove unused imports from plot1d/ modules
CSSFrancis May 11, 2026
e0ade7c
refactor: extract Axes and InsetAxes into axes/ subpackage
CSSFrancis May 11, 2026
fb1c58f
fix: add __all__ to axes/__init__.py and remove dead imports from fig…
CSSFrancis May 11, 2026
d0903fd
refactor: extract Figure, GridSpec, SubplotSpec into figure/ subpackage
CSSFrancis May 11, 2026
24f46e0
fix: remove dead imports from figure/ modules
CSSFrancis May 11, 2026
9fece84
refactor: update __init__.py to import from new subpackages and remov…
CSSFrancis May 11, 2026
c87f4c2
fix: update stale figure_plots import in test_benchmarks_py.py
CSSFrancis May 11, 2026
145be85
refactor: update stale figure_plots docstring cross-references in Exa…
CSSFrancis May 11, 2026
437b5ef
refactor: update figure_plots documentation to reflect new module str…
CSSFrancis May 12, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ docs/_static/anywidget_config.js

# macOS
.DS_Store

# Git worktrees
.worktrees/
6 changes: 3 additions & 3 deletions Examples/Interactive/plot_interactive_fft.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
* The left panel shows a synthetic real-space image (a periodic lattice with
noise, similar to an atomic-resolution STEM image).
* A yellow rectangle widget marks the region-of-interest (ROI).
* Whenever the ROI is moved or resized the :meth:`~anyplotlib.figure_plots.Plot2D.on_release`
* Whenever the ROI is moved or resized the :meth:`~anyplotlib.plot2d.Plot2D.on_release`
callback re-computes ``numpy.fft.fft2`` on the cropped pixels, applies a
Hann window to reduce edge ringing, takes the log-magnitude, and pushes the
result into the right panel with
:meth:`~anyplotlib.figure_plots.Plot2D.update`.
* A second :meth:`~anyplotlib.figure_plots.Plot2D.on_change` callback updates
:meth:`~anyplotlib.plot2d.Plot2D.update`.
* A second :meth:`~anyplotlib.plot2d.Plot2D.on_change` callback updates
a lightweight text readout (ROI size in pixels) on every drag frame without
re-running the FFT.

Expand Down
2 changes: 1 addition & 1 deletion Examples/Interactive/plot_point_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* **Derivative** (central finite difference): ``dy/dx ≈ [f(xq+h) − f(xq−h)] / 2h``
* **Tangent line**: ``y_tan(x) = yq + slope · (x − xq)``

The tangent line is added with :meth:`~anyplotlib.figure_plots.Plot1D.add_line`
The tangent line is added with :meth:`~anyplotlib.plot1d.Plot1D.add_line`
and the previous one is removed, so only one tangent is shown at a time.

.. note::
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_arrows.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
======

Draw vector arrows on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_arrows`.
:meth:`~anyplotlib.plot2d.Plot2D.add_arrows`.
Use ``markers["arrows"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_circles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
=======

Mark circular features on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_circles`.
:meth:`~anyplotlib.plot2d.Plot2D.add_circles`.
Use ``markers["circles"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_ellipses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
========

Draw ellipses on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_ellipses`.
:meth:`~anyplotlib.plot2d.Plot2D.add_ellipses`.
Use ``markers["ellipses"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_horizontal_lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
================

Draw static horizontal threshold lines on a 1-D plot with
:meth:`~anyplotlib.figure_plots.Plot1D.add_hlines`.
:meth:`~anyplotlib.plot1d.Plot1D.add_hlines`.
Use ``markers["hlines"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_line_segments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
=============

Draw line segments on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_lines`.
:meth:`~anyplotlib.plot2d.Plot2D.add_lines`.
Use ``markers["lines"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
======

Mark specific (x, y) positions on a 1-D plot with
:meth:`~anyplotlib.figure_plots.Plot1D.add_points`.
:meth:`~anyplotlib.plot1d.Plot1D.add_points`.
Use ``markers["points"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_polygons.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
========

Draw closed polygons on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_polygons`.
:meth:`~anyplotlib.plot2d.Plot2D.add_polygons`.
Use ``markers["polygons"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_rectangles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
==========

Draw bounding boxes on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_rectangles`.
:meth:`~anyplotlib.plot2d.Plot2D.add_rectangles`.
Use ``markers["rectangles"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_squares.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
=======

Draw squares on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_squares`.
:meth:`~anyplotlib.plot2d.Plot2D.add_squares`.
Use ``markers["squares"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_texts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
===========

Place text annotations on a 2-D image with
:meth:`~anyplotlib.figure_plots.Plot2D.add_texts`.
:meth:`~anyplotlib.plot2d.Plot2D.add_texts`.
Use ``markers["texts"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Markers/plot_vertical_lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
==============

Draw static vertical marker lines on a 1-D plot with
:meth:`~anyplotlib.figure_plots.Plot1D.add_vlines`.
:meth:`~anyplotlib.plot1d.Plot1D.add_vlines`.
Use ``markers["vlines"]["name"].set(...)`` to update them live.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Widgets/plot_widget1d_hline.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
==========================

A draggable horizontal line on a 1-D plot panel.
Add it with :meth:`~anyplotlib.figure_plots.Plot1D.add_hline_widget`.
Add it with :meth:`~anyplotlib.plot1d.Plot1D.add_hline_widget`.
Drag the line up or down to change the selected y value.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Widgets/plot_widget1d_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
================

A draggable range selector on a 1-D plot panel with two handles.
Add it with :meth:`~anyplotlib.figure_plots.Plot1D.add_range_widget`.
Add it with :meth:`~anyplotlib.plot1d.Plot1D.add_range_widget`.
Drag either handle to resize the selected interval, or drag the band
to move it.
"""
Expand Down
2 changes: 1 addition & 1 deletion Examples/Widgets/plot_widget1d_vline.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
========================

A draggable vertical line on a 1-D plot panel.
Add it with :meth:`~anyplotlib.figure_plots.Plot1D.add_vline_widget`.
Add it with :meth:`~anyplotlib.plot1d.Plot1D.add_vline_widget`.
Drag the line left or right to change the selected x position.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Widgets/plot_widget2d_circle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
=================

A draggable, resizable circle overlay on a 2-D image panel.
Add it with :meth:`~anyplotlib.figure_plots.Plot2D.add_widget` using
Add it with :meth:`~anyplotlib.plot2d.Plot2D.add_widget` using
``kind="circle"``.
"""
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion Examples/Widgets/plot_widget2d_rectangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
====================

A draggable, resizable rectangle overlay on a 2-D image panel.
Add it with :meth:`~anyplotlib.figure_plots.Plot2D.add_widget` using
Add it with :meth:`~anyplotlib.plot2d.Plot2D.add_widget` using
``kind="rectangle"``.
"""
import numpy as np
Expand Down
5 changes: 4 additions & 1 deletion anyplotlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from anyplotlib.figure import Figure, GridSpec, SubplotSpec, subplots
from anyplotlib.figure_plots import Axes, InsetAxes, Plot1D, Plot2D, PlotMesh, Plot3D, PlotBar
from anyplotlib.axes import Axes, InsetAxes
from anyplotlib.plot1d import Plot1D, PlotBar
from anyplotlib.plot2d import Plot2D, PlotMesh
from anyplotlib.plot3d import Plot3D
from anyplotlib.callbacks import CallbackRegistry, Event
from anyplotlib.widgets import (
Widget, RectangleWidget, CircleWidget, AnnularWidget,
Expand Down
170 changes: 170 additions & 0 deletions anyplotlib/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""
_utils.py
=========
Shared low-level utilities used across plot subpackages.
"""

from __future__ import annotations

import numpy as np

_LINESTYLE_ALIASES: dict[str, str] = {
"-": "solid",
"--": "dashed",
":": "dotted",
"-.": "dashdot",
"solid": "solid",
"dashed": "dashed",
"dotted": "dotted",
"dashdot": "dashdot",
}


def _arr_to_b64(arr: np.ndarray, dtype) -> str:
"""Encode a NumPy array as base-64 (little-endian raw bytes).

Uses little-endian byte order so the result is compatible with
JavaScript's ``Float64Array`` / ``Float32Array`` / ``Int32Array``
on all modern platforms (x86, ARM).
"""
import base64
le_dtype = np.dtype(dtype).newbyteorder("<")
return base64.b64encode(np.asarray(arr).astype(le_dtype).tobytes()).decode("ascii")


def _norm_linestyle(ls: str) -> str:
"""Normalise a linestyle name or shorthand to its canonical form.

Accepted values
---------------
``"solid"`` / ``"-"``, ``"dashed"`` / ``"--"``,
``"dotted"`` / ``":"``, ``"dashdot"`` / ``"-."``.

Raises
------
ValueError
If *ls* is not a recognised name or shorthand.
"""
canonical = _LINESTYLE_ALIASES.get(ls)
if canonical is None:
raise ValueError(
f"Unknown linestyle {ls!r}. Expected one of: "
"'solid', 'dashed', 'dotted', 'dashdot', "
"or shorthands '-', '--', ':', '-.'."
)
return canonical


def _normalize_image(data: np.ndarray):
"""Normalise data to uint8, returning (img_u8, vmin, vmax)."""
img = data.astype(np.float64, copy=False)
vmin = float(np.nanmin(img))
vmax = float(np.nanmax(img))
if vmax > vmin:
buf = np.empty_like(img)
np.subtract(img, vmin, out=buf)
np.divide(buf, vmax - vmin, out=buf)
np.multiply(buf, 255.0, out=buf)
img_u8 = buf.astype(np.uint8)
else:
img_u8 = np.zeros(data.shape, dtype=np.uint8)
return img_u8, vmin, vmax


# Mapping from common matplotlib colormap names to their nearest colorcet
# equivalents so callers can keep using familiar names without any matplotlib
# dependency.
_CMAP_ALIASES: dict[str, str] = {
"viridis": "bmy", # blue→magenta→yellow, perceptually uniform
"plasma": "fire", # warm sequential (dark→bright)
"inferno": "kb", # dark→blue→white
"magma": "kbc", # dark→blue→cyan sequential
"cividis": "bgy", # accessible, blue→green→yellow sequential
"hot": "fire",
"afmhot": "fire",
"jet": "rainbow4",
"hsv": "rainbow4",
"nipy_spectral": "rainbow4",
"RdBu": "coolwarm",
"bwr": "cwr", # blue→white→red diverging
"seismic": "coolwarm",
}


def _build_colormap_lut(name: str) -> list:
"""Return a 256-entry ``[[r, g, b], ...]`` LUT for the named colormap.

Priority order:

1. **colorcet** — preferred; common matplotlib names are remapped via
:data:`_CMAP_ALIASES` so callers can use ``"viridis"`` etc.
2. **matplotlib** — fallback when colorcet is not installed (e.g. in
Pyodide before micropip finishes, or in minimal test environments).
3. **Built-in gray ramp** — final fallback for unknown names.
"""
# ── 1. Try colorcet ───────────────────────────────────────────────────
try:
import colorcet as cc
resolved = _CMAP_ALIASES.get(name, name)
palette = cc.palette.get(resolved)
if palette is not None:
n = len(palette)
lut: list = []
for i in range(256):
h = palette[int(round(i * (n - 1) / 255))].lstrip("#")
lut.append([int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)])
return lut
except Exception:
pass

# ── 2. Try matplotlib ─────────────────────────────────────────────────
try:
import matplotlib.cm as _cm
import numpy as _np
cmap = _cm.get_cmap(name, 256)
rgba = cmap(_np.linspace(0, 1, 256))
return [[int(r * 255), int(g * 255), int(b * 255)]
for r, g, b, _ in rgba]
except Exception:
pass

# ── 3. Gray ramp fallback ─────────────────────────────────────────────
return [[v, v, v] for v in range(256)]


def _resample_mesh(data: np.ndarray, x_edges, y_edges) -> np.ndarray:
"""Resample a mesh to a regular pixel grid via nearest-neighbour lookup.

For uniform edges this is an identity operation. For non-uniform edges
(e.g. log-spaced) it maps each uniform output pixel to the nearest input
cell, producing a visually correct linear-axis image.

Parameters
----------
data : ndarray, shape (M, N) — one value per mesh cell.
x_edges : array-like, length N+1 — column edge coordinates.
y_edges : array-like, length M+1 — row edge coordinates.

Returns
-------
ndarray, shape (M, N)
"""
rows, cols = data.shape
x_edges = np.asarray(x_edges, dtype=float)
y_edges = np.asarray(y_edges, dtype=float)

# Cell centres
x_c = (x_edges[:-1] + x_edges[1:]) / 2.0
y_c = (y_edges[:-1] + y_edges[1:]) / 2.0

# Uniform sample points (same count as original cells)
x_samp = np.linspace(x_c[0], x_c[-1], cols)
y_samp = np.linspace(y_c[0], y_c[-1], rows)

# Nearest-neighbour cell lookup via edge-sorted searchsorted
xi = np.searchsorted(x_edges, x_samp) - 1
xi = np.clip(xi, 0, cols - 1)
yi = np.searchsorted(y_edges, y_samp) - 1
yi = np.clip(yi, 0, rows - 1)

return data[np.ix_(yi, xi)]
6 changes: 6 additions & 0 deletions anyplotlib/axes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""anyplotlib.axes — grid-cell and inset axes containers."""

from anyplotlib.axes._axes import Axes
from anyplotlib.axes._inset_axes import InsetAxes

__all__ = ["Axes", "InsetAxes"]
Loading
Loading