Skip to content

Commit 48102db

Browse files
committed
Enhance interactive fitting: add figure-level help text and update example usage to include "help"
1 parent b190e55 commit 48102db

File tree

4 files changed

+129
-4
lines changed

4 files changed

+129
-4
lines changed

Examples/Interactive/plot_interactive_fitting.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ def gaussian(x, amp, mu, sigma):
5252

5353
# ── Figure ─────────────────────────────────────────────────────────────────
5454

55-
fig, ax = apl.subplots(1, 1, figsize=(720, 380))
55+
fig, ax = apl.subplots(1, 1, figsize=(720, 380),
56+
help="Click a coloured line → show/hide its widgets\n"
57+
"Drag circle handle → move peak center (μ) and amplitude (A)\n"
58+
"Drag range edge → widen / narrow the width (σ)\n"
59+
"press: f → run least-squares fit")
5660
plot = ax.plot(signal, axes=[x], color="#adb5bd", linewidth=1.5,
5761
alpha=0.6, label="data")
5862
#

anyplotlib/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@
77
VLineWidget, HLineWidget, RangeWidget,
88
)
99

10+
# ── Global help flag ──────────────────────────────────────────────────────
11+
# Set to False to suppress help badges on all figures in this session.
12+
# Default True: badges appear whenever a figure has help text set.
13+
show_help: bool = True
14+
1015
__all__ = [
1116
"Figure", "GridSpec", "SubplotSpec", "subplots",
1217
"Axes", "Plot1D", "Plot2D", "PlotMesh", "Plot3D", "PlotBar",
1318
"CallbackRegistry", "Event",
1419
"Widget", "RectangleWidget", "CircleWidget", "AnnularWidget",
1520
"CrosshairWidget", "PolygonWidget", "LabelWidget",
1621
"VLineWidget", "HLineWidget", "RangeWidget",
22+
"show_help",
1723
]

anyplotlib/figure.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class Figure(anywidget.AnyWidget):
7474
event_json = traitlets.Unicode("{}").tag(sync=True)
7575
# When True the JS renderer shows a per-panel FPS / frame-time overlay.
7676
display_stats = traitlets.Bool(False).tag(sync=True)
77+
# Figure-level help text shown in a '?' badge overlay in JS.
78+
# Empty string means no badge. Gated by apl.show_help at the Python level.
79+
help_text = traitlets.Unicode("").tag(sync=True)
7780
_esm = _ESM_SOURCE
7881
# Static CSS injected by anywidget alongside _esm.
7982
# .apl-scale-wrap — outer container; width:100% means it always fills
@@ -111,7 +114,7 @@ class Figure(anywidget.AnyWidget):
111114
def __init__(self, nrows=1, ncols=1, figsize=(640, 480),
112115
width_ratios=None, height_ratios=None,
113116
sharex=False, sharey=False,
114-
display_stats=False, **kwargs):
117+
display_stats=False, help="", **kwargs):
115118
super().__init__(**kwargs)
116119
self._nrows = nrows
117120
self._ncols = ncols
@@ -125,8 +128,37 @@ def __init__(self, nrows=1, ncols=1, figsize=(640, 480),
125128
self.fig_width = figsize[0]
126129
self.fig_height = figsize[1]
127130
self.display_stats = display_stats
131+
self.help_text = self._resolve_help(help)
128132
self._push_layout()
129133

134+
@staticmethod
135+
def _resolve_help(text: str) -> str:
136+
"""Return *text* if ``apl.show_help`` is True (default), else ``""``."""
137+
try:
138+
import anyplotlib as _apl
139+
if not getattr(_apl, "show_help", True):
140+
return ""
141+
except ImportError:
142+
pass
143+
return text or ""
144+
145+
def set_help(self, text: str) -> None:
146+
"""Set (or clear) the figure-level help text shown in the **?** badge.
147+
148+
Parameters
149+
----------
150+
text : str
151+
Help string displayed when the user clicks the **?** badge.
152+
Pass an empty string (or ``""`` ) to remove the badge entirely.
153+
Newlines (``\\n``) are respected in the card.
154+
155+
Examples
156+
--------
157+
>>> fig.set_help("Drag peak: move μ/A\\nPress f: least-squares fit")
158+
>>> fig.set_help("") # hide the badge
159+
"""
160+
self.help_text = self._resolve_help(text)
161+
130162
# ── subplot creation ──────────────────────────────────────────────────────
131163
def add_subplot(self, spec) -> Axes:
132164
"""Add a subplot cell and return its :class:`Axes`.
@@ -347,7 +379,8 @@ def subplots(nrows=1, ncols=1, *,
347379
width_ratios=None,
348380
height_ratios=None,
349381
gridspec_kw=None,
350-
display_stats=False):
382+
display_stats=False,
383+
help=""):
351384
"""Create a :class:`Figure` and a grid of :class:`~anyplotlib.figure_plots.Axes`.
352385
353386
Mirrors :func:`matplotlib.pyplot.subplots`.
@@ -369,6 +402,13 @@ def subplots(nrows=1, ncols=1, *,
369402
gridspec_kw : dict, optional
370403
Extra keyword arguments forwarded to :class:`GridSpec`.
371404
Recognised keys: ``width_ratios``, ``height_ratios``.
405+
display_stats : bool, optional
406+
Show per-panel FPS / frame-time overlay. Default False.
407+
help : str, optional
408+
Help text shown when the user clicks the **?** badge on the figure.
409+
Newlines (``\\n``) create separate lines in the card. The badge is
410+
hidden when *help* is empty (default). Suppressed globally when
411+
``apl.show_help = False``.
372412
373413
Returns
374414
-------
@@ -398,6 +438,7 @@ def subplots(nrows=1, ncols=1, *,
398438
width_ratios=width_ratios, height_ratios=height_ratios,
399439
sharex=sharex, sharey=sharey,
400440
display_stats=display_stats,
441+
help=help,
401442
)
402443
# Build the GridSpec from the Figure's own stored ratios so there is
403444
# exactly one source of truth.

anyplotlib/figure_esm.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,81 @@ function render({ model, el }) {
189189
'color:white;font-size:11px;border-radius:4px;display:none;pointer-events:none;z-index:21;';
190190
outerDiv.appendChild(sizeLabel);
191191

192-
// Tooltip (shared across all panels)
192+
// ── Help badge (figure-level) ─────────────────────────────────────────────
193+
// A small '?' button in the top-right corner of the figure.
194+
// • Hidden until the mouse enters outerDiv (plot "active").
195+
// • Stays visible while the help card is open, even after mouse-leave.
196+
// • Rounded square, tucked into the right padding band so it never
197+
// overlaps plot content.
198+
// • Clicking toggles the help card; click again (or mouse-leave with
199+
// card closed) hides the button again.
200+
const _BTN_BG = 'rgba(100,100,120,0.72)';
201+
const _BTN_BG_ACTIVE = 'rgba(75,120,210,0.92)';
202+
203+
const helpBtn = document.createElement('div');
204+
helpBtn.style.cssText =
205+
'position:absolute;top:9px;right:6px;width:20px;height:20px;' +
206+
'border-radius:4px;background:' + _BTN_BG + ';color:#fff;' +
207+
'font-size:12px;font-weight:bold;font-family:sans-serif;' +
208+
'display:none;align-items:center;justify-content:center;' +
209+
'cursor:pointer;z-index:50;user-select:none;line-height:1;' +
210+
'box-shadow:0 1px 4px rgba(0,0,0,0.35);';
211+
helpBtn.textContent = '?';
212+
helpBtn.title = 'Show help';
213+
outerDiv.appendChild(helpBtn);
214+
215+
const helpCard = document.createElement('div');
216+
helpCard.style.cssText =
217+
'position:absolute;top:33px;right:6px;padding:10px 14px;' +
218+
'background:rgba(28,28,38,0.95);color:#e0e0e8;font-size:12px;' +
219+
'font-family:sans-serif;border-radius:6px;line-height:1.7;' +
220+
'white-space:pre-wrap;max-width:300px;display:none;z-index:51;' +
221+
'box-shadow:0 4px 14px rgba(0,0,0,0.55);pointer-events:none;' +
222+
'border:1px solid rgba(120,120,160,0.3);';
223+
outerDiv.appendChild(helpCard);
224+
225+
let _helpExists = false; // true when help_text is non-empty
226+
let _helpHovered = false; // true while mouse is inside outerDiv
227+
let _helpOpen = false; // true while the card is shown
228+
229+
function _updateHelp() {
230+
const txt = model.get('help_text') || '';
231+
_helpExists = !!txt;
232+
helpCard.textContent = txt;
233+
if (!txt) {
234+
// Help removed — hide everything immediately.
235+
helpBtn.style.display = 'none';
236+
helpCard.style.display = 'none';
237+
helpBtn.style.background = _BTN_BG;
238+
_helpOpen = false;
239+
} else if (_helpHovered || _helpOpen) {
240+
// Already hovered or card open — make badge visible.
241+
helpBtn.style.display = 'flex';
242+
}
243+
}
244+
_updateHelp();
245+
246+
outerDiv.addEventListener('mouseenter', () => {
247+
_helpHovered = true;
248+
if (_helpExists) helpBtn.style.display = 'flex';
249+
});
250+
251+
outerDiv.addEventListener('mouseleave', () => {
252+
_helpHovered = false;
253+
// Only hide the button if the card is also closed.
254+
if (!_helpOpen) helpBtn.style.display = 'none';
255+
});
256+
257+
helpBtn.addEventListener('click', (e) => {
258+
e.stopPropagation();
259+
_helpOpen = !_helpOpen;
260+
helpCard.style.display = _helpOpen ? 'block' : 'none';
261+
helpBtn.style.background = _helpOpen ? _BTN_BG_ACTIVE : _BTN_BG;
262+
// If closing the card while the mouse has already left, hide the button too.
263+
if (!_helpOpen && !_helpHovered) helpBtn.style.display = 'none';
264+
});
265+
266+
model.on('change:help_text', _updateHelp);
193267
const tooltip = document.createElement('div');
194268
tooltip.style.cssText =
195269
'position:fixed;padding:5px 9px;font-size:12px;font-family:sans-serif;' +

0 commit comments

Comments
 (0)