From 089179130a3e7b2576240342d48981a0997696ca Mon Sep 17 00:00:00 2001 From: koaning Date: Tue, 24 Mar 2026 15:24:43 +0100 Subject: [PATCH 1/2] Add HoverZoom widget for image magnification on hover Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 3 + README.md | 3 + agents.md | 1 + demos/hoverzoom.py | 86 ++++++++++++++++++ mkdocs/index.md | 5 ++ mkdocs/llms.txt | 1 + wigglystuff/__init__.py | 2 + wigglystuff/hover_zoom.py | 80 +++++++++++++++++ wigglystuff/static/hover-zoom.css | 55 ++++++++++++ wigglystuff/static/hover-zoom.js | 142 ++++++++++++++++++++++++++++++ 10 files changed, 378 insertions(+) create mode 100644 demos/hoverzoom.py create mode 100644 wigglystuff/hover_zoom.py create mode 100644 wigglystuff/static/hover-zoom.css create mode 100644 wigglystuff/static/hover-zoom.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 18f48dd9..a9c9cb64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- New `HoverZoom` widget — hover over an image to see a magnified side panel, like product zoom on e-commerce sites. + ## [0.2.40] - 2026-03-24 ### Added diff --git a/README.md b/README.md index fe8c939e..3475c7d8 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ uv pip install wigglystuff ColorPicker

molab · API · MD +HoverZoom

molab · API · MD + + GamepadWidget

molab · API · MD KeystrokeWidget

molab · API · MD SpeechToText

molab · API · MD diff --git a/agents.md b/agents.md index 99d65e5c..f7ea9d46 100644 --- a/agents.md +++ b/agents.md @@ -35,6 +35,7 @@ syncs back to Python. | ThreeWidget | `wigglystuff.three_widget.ThreeWidget` | `data`, `width`, `height`, `show_grid`, `show_axes`, `dark_mode`, `axis_labels`, `animate_updates`, `animation_duration_ms` | 3D scatter plot for point clouds | | WebcamCapture | `wigglystuff.webcam_capture.WebcamCapture` | `image_base64`, `capturing`, `interval_ms`, `facing_mode` | Webcam preview with snapshot capture | | GamepadWidget | `wigglystuff.gamepad.GamepadWidget` | `axes`, `current_button_press`, `dpad_*`, `current_timestamp` | Streams browser Gamepad API events | +| HoverZoom | `wigglystuff.hover_zoom.HoverZoom` | `image`, `zoom_factor`, `width`, `height` | Image hover zoom with magnified side panel | | KeystrokeWidget | `wigglystuff.keystroke.KeystrokeWidget` | `last_key` | Captures the latest keypress w/ modifiers | | WebkitSpeechToTextWidget | `wigglystuff.talk.WebkitSpeechToTextWidget` | `transcript`, `listening`, `trigger_listen` | WebKit speech recognition bridge | | DriverTour | `wigglystuff.driver_tour.DriverTour` | `steps`, `auto_start`, `show_progress`, `active`, `current_step` | Guided product tours via Driver.js | diff --git a/demos/hoverzoom.py b/demos/hoverzoom.py new file mode 100644 index 00000000..e297708f --- /dev/null +++ b/demos/hoverzoom.py @@ -0,0 +1,86 @@ +# /// script +# requires-python = ">=3.14" +# dependencies = [ +# "marimo>=0.19.7", +# "wigglystuff==0.2.37", +# "Pillow", +# "matplotlib", +# "numpy", +# ] +# /// + +import marimo + +__generated_with = "0.21.1" +app = marimo.App(width="medium") + + +@app.cell +def _(): + import marimo as mo + from wigglystuff import HoverZoom + + return HoverZoom, mo + + +@app.cell +def _(): + from PIL import Image, ImageDraw + + img = Image.new("RGB", (800, 600), (30, 80, 120)) + draw = ImageDraw.Draw(img) + for i in range(0, 800, 40): + for j in range(0, 600, 40): + color = ((i * 3) % 256, (j * 4) % 256, ((i + j) * 2) % 256) + draw.ellipse([i, j, i + 30, j + 30], fill=color) + return (img,) + + +@app.cell +def _(HoverZoom, img, mo): + widget = mo.ui.anywidget(HoverZoom(img, zoom_factor=3.0, width=450)) + return (widget,) + + +@app.cell +def _(widget): + widget + return + + +@app.cell +def _(): + import numpy as np + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + rng = np.random.default_rng(42) + n = 200 + x = rng.normal(0, 1, n) + y = rng.normal(0, 1, n) + labels = [f"p{i}" for i in range(n)] + + fig, ax = plt.subplots(figsize=(8, 6), dpi=200) + ax.scatter(x, y, s=12, alpha=0.6) + for xi, yi, label in zip(x, y, labels): + ax.annotate(label, (xi, yi), fontsize=4, alpha=0.7, ha="center", va="bottom") + ax.set_title("200 labeled points — hover to read the labels") + fig.tight_layout() + return (fig,) + + +@app.cell +def _(HoverZoom, fig, mo): + chart_widget = mo.ui.anywidget(HoverZoom(fig, zoom_factor=4.0, width=500)) + return (chart_widget,) + + +@app.cell +def _(chart_widget): + chart_widget + return + + +if __name__ == "__main__": + app.run() diff --git a/mkdocs/index.md b/mkdocs/index.md index 0b00f1c8..64511078 100644 --- a/mkdocs/index.md +++ b/mkdocs/index.md @@ -66,6 +66,11 @@ The documentation for wigglystuff is designed for humans (via hosted marimo note +