Skip to content

Commit 64d6df3

Browse files
committed
Refactor layout handling for 2D panels to ensure canvas size matches grid cell allocation, removing aspect-locking logic. Update tests to verify correct canvas dimensions for various image aspect ratios and configurations. Add documentation for new layout behavior and introduce a navigator for figure_esm.js.
1 parent c4d7fb7 commit 64d6df3

File tree

13 files changed

+812
-362
lines changed

13 files changed

+812
-362
lines changed

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ make clean # wipe build artefacts
6868
|------|---------|
6969
| `anyplotlib/figure.py` | `Figure` widget; layout engine; JS↔Python dispatch |
7070
| `anyplotlib/figure_plots.py` | All plot classes, `Axes`, `GridSpec`, `subplots()` |
71-
| `anyplotlib/figure_esm.js` | All JS canvas rendering |
71+
| `anyplotlib/figure_esm.js` | All JS canvas rendering (2 805 lines — see `anyplotlib/FIGURE_ESM.md` for a full section map) |
72+
| `anyplotlib/FIGURE_ESM.md` | Line-numbered navigator for `figure_esm.js` — read this before editing the JS |
7273
| `anyplotlib/markers.py` | Static marker collections; `to_wire()` translation |
7374
| `anyplotlib/widgets.py` | Interactive overlay widgets |
7475
| `anyplotlib/callbacks.py` | `CallbackRegistry`, `Event` dataclass |

Examples/plot_image2d.py

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
"""
2-
2D Image
3-
========
4-
5-
Display a 2-D image with physical axes using
6-
:meth:`~anyplotlib.figure_plots.Axes.imshow`.
7-
The image is a synthetic STEM-like diffraction pattern with a physical
8-
length scale in nanometres. Circle markers highlight the first-order
9-
diffraction spots, and an annular integration widget is placed over the
10-
central beam. Pan and zoom with the mouse; press **R** to reset the view,
11-
**H** to toggle the histogram, **L** / **S** to cycle colour-scale modes.
2+
2D Image with Histogram
3+
=======================
4+
5+
Display a 2-D image with physical axes, a colourmap, and an interactive
6+
histogram below — all wired together with draggable threshold widgets.
7+
8+
Layout
9+
------
10+
A :class:`~anyplotlib.figure_plots.GridSpec` with two rows puts the image
11+
on top and a bar-chart histogram below. Two
12+
:class:`~anyplotlib.widgets.VLineWidget` handles on the histogram mark the
13+
``display_min`` / ``display_max`` thresholds; dragging them updates the
14+
image colour scale in real time.
15+
16+
Key bindings on the image panel: **R** reset view · **C** toggle colorbar ·
17+
**L** / **S** cycle colour-scale modes.
1218
"""
1319
import numpy as np
14-
import anyplotlib as vw
20+
import anyplotlib as apl
1521

1622

1723
rng = np.random.default_rng(1)
@@ -35,13 +41,21 @@ def _ring(r, r0, width, amp):
3541
+ rng.normal(scale=0.04, size=(N, N))
3642
)
3743

38-
# ── Plot ──────────────────────────────────────────────────────────────────────
39-
fig, ax = vw.subplots(1, 1, figsize=(500, 500))
40-
v = ax.imshow(image, axes=[x, y], units="nm")
44+
# ── Layout: image (top, 3×) + histogram bar chart (bottom, 1×) ────────────────
45+
gs = apl.GridSpec(2, 1, height_ratios=[3, 1])
46+
fig = apl.Figure(figsize=(500, 640))
47+
ax_img = fig.add_subplot(gs[0, 0])
48+
ax_hist = fig.add_subplot(gs[1, 0])
49+
50+
# ── Image panel ───────────────────────────────────────────────────────────────
51+
v = ax_img.imshow(image, axes=[x, y], units="nm")
4152
v.set_colormap("inferno")
4253

43-
# ── First-order spot markers ──────────────────────────────────────────────────
44-
# imshow axes are centre arrays: pixel = (phys - x[0]) / (x[1] - x[0])
54+
vmin_init = float(image.min())
55+
vmax_init = float(image.max())
56+
v.set_clim(vmin=vmin_init, vmax=vmax_init)
57+
58+
# First-order spot markers
4559
dx = x[1] - x[0]
4660

4761

@@ -57,22 +71,39 @@ def phys_to_px(val):
5771
edgecolors="#00e5ff", facecolors="#00e5ff22",
5872
labels=["g1", "g1_bar", "g2", "g2_bar"])
5973

60-
# ── Annular integration widget ────────────────────────────────────────────────
61-
cx = cy = float(phys_to_px(0.0))
62-
v.add_widget("annular", color="#ffcc00",
63-
cx=cx, cy=cy,
64-
r_outer=float(phys_to_px(2.8) - phys_to_px(0.0)),
65-
r_inner=float(phys_to_px(1.2) - phys_to_px(0.0)))
74+
# ── Histogram bar chart ────────────────────────────────────────────────────────
75+
counts, edges = np.histogram(image.ravel(), bins=64)
76+
bin_centers = 0.5 * (edges[:-1] + edges[1:])
77+
78+
h = ax_hist.bar(counts, x_centers=bin_centers, orient="v",
79+
color="#4fc3f7", y_units="count")
80+
81+
# ── Draggable threshold handles on the histogram ──────────────────────────────
82+
wlo = h.add_vline_widget(vmin_init, color="#ff6e40") # low-threshold handle
83+
whi = h.add_vline_widget(vmax_init, color="#ffffff") # high-threshold handle
84+
85+
86+
@wlo.on_release
87+
def _apply_low(event):
88+
"""Update image display_min when the low handle is released."""
89+
v.set_clim(vmin=event.x)
90+
91+
92+
@whi.on_release
93+
def _apply_high(event):
94+
"""Update image display_max when the high handle is released."""
95+
v.set_clim(vmax=event.x)
96+
6697

6798
fig
6899

69100
# %%
70-
# Adjust display range and colour map
71-
# ------------------------------------
72-
# :meth:`~anyplotlib.figure_plots.Plot2D.set_clim` clips the colour scale;
73-
# :meth:`~anyplotlib.figure_plots.Plot2D.set_colormap` switches the palette.
101+
# Adjust colour map
102+
# ------------------
103+
# :meth:`~anyplotlib.figure_plots.Plot2D.set_colormap` switches the palette;
104+
# :meth:`~anyplotlib.figure_plots.Plot2D.set_clim` adjusts the display range.
74105

75-
v.set_clim(vmin=0.0, vmax=0.8)
76106
v.set_colormap("viridis")
107+
v.set_clim(vmin=0.0, vmax=0.8)
77108

78109
fig

0 commit comments

Comments
 (0)