anyplotlib 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anyplotlib/__init__.py +53 -0
- anyplotlib/_base_plot.py +229 -0
- anyplotlib/_electron.py +74 -0
- anyplotlib/_repr_utils.py +345 -0
- anyplotlib/_utils.py +194 -0
- anyplotlib/axes/__init__.py +6 -0
- anyplotlib/axes/_axes.py +510 -0
- anyplotlib/axes/_inset_axes.py +126 -0
- anyplotlib/callbacks.py +328 -0
- anyplotlib/embed.py +194 -0
- anyplotlib/figure/__init__.py +7 -0
- anyplotlib/figure/_figure.py +633 -0
- anyplotlib/figure/_gridspec.py +99 -0
- anyplotlib/figure/_subplots.py +100 -0
- anyplotlib/figure_esm.js +6011 -0
- anyplotlib/markers.py +704 -0
- anyplotlib/plot1d/__init__.py +6 -0
- anyplotlib/plot1d/_plot1d.py +1376 -0
- anyplotlib/plot1d/_plotbar.py +436 -0
- anyplotlib/plot2d/__init__.py +5 -0
- anyplotlib/plot2d/_plot2d.py +726 -0
- anyplotlib/plot2d/_plotmesh.py +116 -0
- anyplotlib/plot3d/__init__.py +4 -0
- anyplotlib/plot3d/_plot3d.py +524 -0
- anyplotlib/plotxy/__init__.py +4 -0
- anyplotlib/plotxy/_plotxy.py +273 -0
- anyplotlib/sphinx_anywidget/__init__.py +177 -0
- anyplotlib/sphinx_anywidget/_directive.py +245 -0
- anyplotlib/sphinx_anywidget/_repr_utils.py +298 -0
- anyplotlib/sphinx_anywidget/_scraper.py +390 -0
- anyplotlib/sphinx_anywidget/_wheel_builder.py +84 -0
- anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +1099 -0
- anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +100 -0
- anyplotlib/widgets/__init__.py +18 -0
- anyplotlib/widgets/_base.py +218 -0
- anyplotlib/widgets/_widgets1d.py +109 -0
- anyplotlib/widgets/_widgets2d.py +141 -0
- anyplotlib/widgets/_widgets3d.py +50 -0
- anyplotlib-0.1.0.dist-info/METADATA +160 -0
- anyplotlib-0.1.0.dist-info/RECORD +42 -0
- anyplotlib-0.1.0.dist-info/WHEEL +4 -0
- anyplotlib-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* anywidget_overlay.css
|
|
3
|
+
* =====================
|
|
4
|
+
* Styles for the per-figure interaction badges injected by AnywidgetScraper
|
|
5
|
+
* and the .. anywidget-figure:: RST directive.
|
|
6
|
+
*
|
|
7
|
+
* Badge structure (rendered by _scraper._iframe_html):
|
|
8
|
+
*
|
|
9
|
+
* <div class="awi-fig-wrap" data-awi-fig="FIGID">
|
|
10
|
+
* <iframe data-awi-fig="FIGID" ...></iframe>
|
|
11
|
+
* <div class="awi-badge" data-awi-badge="FIGID">
|
|
12
|
+
* <button class="awi-badge-icon awi-activate-btn">⚡</button> <!-- interactive only -->
|
|
13
|
+
* </div>
|
|
14
|
+
* </div>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* ── figure wrapper ──────────────────────────────────────────────────────── */
|
|
18
|
+
.awi-fig-wrap {
|
|
19
|
+
position: relative; /* Badge positions relative to this */
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ── badge container ─────────────────────────────────────────────────────── */
|
|
23
|
+
.awi-badge {
|
|
24
|
+
position: absolute;
|
|
25
|
+
top: 8px;
|
|
26
|
+
right: 8px;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 8px;
|
|
30
|
+
z-index: 10;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ── after activation: no change needed — stays top-right ───────────────── */
|
|
35
|
+
|
|
36
|
+
/* ── shared icon style ───────────────────────────────────────────────────── */
|
|
37
|
+
.awi-badge-icon {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
width: 38px;
|
|
42
|
+
height: 38px;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
font-size: 18px;
|
|
45
|
+
line-height: 1;
|
|
46
|
+
background: rgba(30, 30, 46, 0.82);
|
|
47
|
+
backdrop-filter: blur(6px);
|
|
48
|
+
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
49
|
+
color: #fff;
|
|
50
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.5);
|
|
51
|
+
transition: opacity 0.25s, background 0.2s, transform 0.15s;
|
|
52
|
+
opacity: 0.90;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── ⚡ activation button ────────────────────────────────────────────────── */
|
|
56
|
+
.awi-activate-btn {
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
pointer-events: auto;
|
|
59
|
+
padding: 0;
|
|
60
|
+
font-family: inherit;
|
|
61
|
+
border-color: rgba(98, 114, 164, 0.7);
|
|
62
|
+
background: rgba(30, 30, 46, 0.80);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.awi-activate-btn:hover {
|
|
66
|
+
background: rgba(98, 114, 164, 0.85);
|
|
67
|
+
opacity: 1;
|
|
68
|
+
transform: scale(1.12);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.awi-activate-btn:focus-visible {
|
|
72
|
+
outline: 2px solid #8be9fd;
|
|
73
|
+
outline-offset: 2px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ── loading state (⏳) ──────────────────────────────────────────────────── */
|
|
77
|
+
.awi-activate-btn[data-state="loading"] {
|
|
78
|
+
cursor: default;
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
background: rgba(189, 147, 249, 0.85);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ── active / live state (✓) ─────────────────────────────────────────────── */
|
|
84
|
+
.awi-activate-btn[data-state="active"] {
|
|
85
|
+
background: rgba(80, 250, 123, 0.85);
|
|
86
|
+
color: #1e1e2e;
|
|
87
|
+
cursor: default;
|
|
88
|
+
pointer-events: none;
|
|
89
|
+
font-weight: 700;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ── error state (❌) ────────────────────────────────────────────────────── */
|
|
93
|
+
.awi-activate-btn[data-state="error"] {
|
|
94
|
+
background: rgba(255, 85, 85, 0.85);
|
|
95
|
+
cursor: default;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""anyplotlib.widgets — interactive overlay widget classes."""
|
|
2
|
+
from anyplotlib.widgets._base import Widget
|
|
3
|
+
from anyplotlib.widgets._widgets2d import (
|
|
4
|
+
RectangleWidget, CircleWidget, AnnularWidget,
|
|
5
|
+
CrosshairWidget, PolygonWidget, LabelWidget,
|
|
6
|
+
)
|
|
7
|
+
from anyplotlib.widgets._widgets1d import (
|
|
8
|
+
VLineWidget, HLineWidget, RangeWidget, PointWidget,
|
|
9
|
+
)
|
|
10
|
+
from anyplotlib.widgets._widgets3d import PlaneWidget
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Widget",
|
|
14
|
+
"RectangleWidget", "CircleWidget", "AnnularWidget",
|
|
15
|
+
"CrosshairWidget", "PolygonWidget", "LabelWidget",
|
|
16
|
+
"VLineWidget", "HLineWidget", "RangeWidget", "PointWidget",
|
|
17
|
+
"PlaneWidget",
|
|
18
|
+
]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
widgets/_base.py
|
|
3
|
+
================
|
|
4
|
+
Base Widget class shared by all interactive overlay widgets.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
import uuid as _uuid
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
from anyplotlib.callbacks import CallbackRegistry, Event, _EventMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Widget(_EventMixin):
|
|
14
|
+
"""Base class for all overlay widgets.
|
|
15
|
+
|
|
16
|
+
Provides attribute-based state access, callbacks for interaction events,
|
|
17
|
+
and automatic synchronization with the JavaScript renderer.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
wtype : str
|
|
22
|
+
Widget type (e.g., 'rectangle', 'circle', 'crosshair').
|
|
23
|
+
push_fn : Callable
|
|
24
|
+
Zero-arg callback to send position updates to the JavaScript renderer.
|
|
25
|
+
**kwargs : dict
|
|
26
|
+
Initial widget state (position, size, color, etc.).
|
|
27
|
+
|
|
28
|
+
Attributes
|
|
29
|
+
----------
|
|
30
|
+
callbacks : CallbackRegistry
|
|
31
|
+
Event callback registry. Register handlers via
|
|
32
|
+
``widget.add_event_handler(fn, "pointer_move")`` or as a decorator:
|
|
33
|
+
``@widget.add_event_handler("pointer_move")``.
|
|
34
|
+
|
|
35
|
+
Common event types:
|
|
36
|
+
|
|
37
|
+
- ``"pointer_move"`` — fires on every drag frame
|
|
38
|
+
- ``"pointer_up"`` — fires once when drag settles
|
|
39
|
+
- ``"pointer_down"`` — fires on click/press event
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, wtype: str, push_fn: Callable, **kwargs):
|
|
43
|
+
self._id: str = str(_uuid.uuid4())[:8]
|
|
44
|
+
self._type: str = wtype
|
|
45
|
+
self._data: dict = dict(kwargs)
|
|
46
|
+
self._data["id"] = self._id
|
|
47
|
+
self._data["type"] = wtype
|
|
48
|
+
self._push_fn: Callable = push_fn
|
|
49
|
+
self.callbacks: CallbackRegistry = CallbackRegistry()
|
|
50
|
+
|
|
51
|
+
# ── attribute read ────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def __getattr__(self, key: str):
|
|
54
|
+
"""Access widget properties as attributes (read-only)."""
|
|
55
|
+
if key.startswith("_"):
|
|
56
|
+
raise AttributeError(key)
|
|
57
|
+
try:
|
|
58
|
+
return self._data[key]
|
|
59
|
+
except KeyError:
|
|
60
|
+
raise AttributeError(
|
|
61
|
+
f"{type(self).__name__} has no attribute {key!r}. "
|
|
62
|
+
f"Available: {list(self._data)}"
|
|
63
|
+
) from None
|
|
64
|
+
|
|
65
|
+
# ── attribute write — routes public assignments through set() ────
|
|
66
|
+
|
|
67
|
+
def __setattr__(self, key: str, value) -> None:
|
|
68
|
+
"""Update widget properties via attribute assignment."""
|
|
69
|
+
# Private attrs and 'callbacks' bypass set()
|
|
70
|
+
if key.startswith("_") or key == "callbacks":
|
|
71
|
+
super().__setattr__(key, value)
|
|
72
|
+
return
|
|
73
|
+
# During __init__ _data may not exist yet
|
|
74
|
+
try:
|
|
75
|
+
object.__getattribute__(self, "_data")
|
|
76
|
+
except AttributeError:
|
|
77
|
+
super().__setattr__(key, value)
|
|
78
|
+
return
|
|
79
|
+
self.set(**{key: value})
|
|
80
|
+
|
|
81
|
+
# ── set / get ─────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
def set(self, _push: bool = True, **kwargs) -> None:
|
|
84
|
+
"""Update properties and send targeted update to JavaScript.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
_push : bool, optional
|
|
89
|
+
Whether to push update to renderer. Default True.
|
|
90
|
+
Set to False internally to avoid echo loops.
|
|
91
|
+
**kwargs : dict
|
|
92
|
+
Properties to update (e.g., x=100, y=50, radius=20).
|
|
93
|
+
|
|
94
|
+
Notes
|
|
95
|
+
-----
|
|
96
|
+
Updates are sent as targeted widget updates, not full panel re-renders.
|
|
97
|
+
This is more efficient for frequent updates during dragging.
|
|
98
|
+
"""
|
|
99
|
+
self._data.update(kwargs)
|
|
100
|
+
if _push:
|
|
101
|
+
self._push_fn()
|
|
102
|
+
self.callbacks.fire(Event("pointer_move", source=self))
|
|
103
|
+
|
|
104
|
+
def get(self, key: str, default=None):
|
|
105
|
+
"""Get a widget property by name.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
key : str
|
|
110
|
+
Property name.
|
|
111
|
+
default : optional
|
|
112
|
+
Default value if property not found.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
object
|
|
117
|
+
The property value.
|
|
118
|
+
"""
|
|
119
|
+
return self._data.get(key, default)
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict:
|
|
122
|
+
"""Return a dict copy of the widget state.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
dict
|
|
127
|
+
All widget properties including id and type.
|
|
128
|
+
"""
|
|
129
|
+
return dict(self._data)
|
|
130
|
+
|
|
131
|
+
# ── visibility ────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def visible(self) -> bool:
|
|
135
|
+
"""``True`` if the widget is rendered; ``False`` if hidden."""
|
|
136
|
+
return self._data.get("visible", True)
|
|
137
|
+
|
|
138
|
+
@visible.setter
|
|
139
|
+
def visible(self, value: bool) -> None:
|
|
140
|
+
self.show() if value else self.hide()
|
|
141
|
+
|
|
142
|
+
def show(self) -> None:
|
|
143
|
+
"""Show the widget. Does not fire ``pointer_move`` callbacks."""
|
|
144
|
+
self._data["visible"] = True
|
|
145
|
+
self._push_fn()
|
|
146
|
+
|
|
147
|
+
def hide(self) -> None:
|
|
148
|
+
"""Hide the widget without removing it or its callbacks.
|
|
149
|
+
|
|
150
|
+
Call :meth:`show` to make it visible again.
|
|
151
|
+
Does not fire ``pointer_move`` callbacks.
|
|
152
|
+
"""
|
|
153
|
+
self._data["visible"] = False
|
|
154
|
+
self._push_fn()
|
|
155
|
+
|
|
156
|
+
# ── JS → Python sync ──────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
def _update_from_js(self, msg: dict, event_type: str = "pointer_move") -> bool:
|
|
159
|
+
"""Apply incoming JS state without pushing back (avoids echo).
|
|
160
|
+
|
|
161
|
+
Updates widget ``_data`` with widget-specific state fields from msg,
|
|
162
|
+
then fires widget callbacks with a flat Event.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
msg : dict
|
|
167
|
+
Full raw event message from JS.
|
|
168
|
+
event_type : str
|
|
169
|
+
One of the pointer event types (``pointer_move``, ``pointer_up``,
|
|
170
|
+
``pointer_down``).
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
bool
|
|
175
|
+
True if any widget state changed.
|
|
176
|
+
"""
|
|
177
|
+
_envelope = {
|
|
178
|
+
"source", "panel_id", "event_type", "widget_id",
|
|
179
|
+
"time_stamp", "modifiers", "button", "buttons",
|
|
180
|
+
}
|
|
181
|
+
changed = False
|
|
182
|
+
for k, v in msg.items():
|
|
183
|
+
if k in ("id", "type") or k in _envelope:
|
|
184
|
+
continue
|
|
185
|
+
if self._data.get(k) != v:
|
|
186
|
+
self._data[k] = v
|
|
187
|
+
changed = True
|
|
188
|
+
|
|
189
|
+
if changed or event_type in ("pointer_up", "pointer_down"):
|
|
190
|
+
event = Event(
|
|
191
|
+
event_type=event_type,
|
|
192
|
+
source=self,
|
|
193
|
+
time_stamp=msg.get("time_stamp", 0.0),
|
|
194
|
+
modifiers=msg.get("modifiers", []),
|
|
195
|
+
x=msg.get("x"),
|
|
196
|
+
y=msg.get("y"),
|
|
197
|
+
button=msg.get("button"),
|
|
198
|
+
buttons=msg.get("buttons", 0),
|
|
199
|
+
xdata=msg.get("xdata"),
|
|
200
|
+
ydata=msg.get("ydata"),
|
|
201
|
+
)
|
|
202
|
+
self.callbacks.fire(event)
|
|
203
|
+
return changed
|
|
204
|
+
|
|
205
|
+
# ── repr ──────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
def __repr__(self) -> str:
|
|
208
|
+
props = ", ".join(
|
|
209
|
+
f"{k}={v:.4g}" if isinstance(v, float) else f"{k}={v!r}"
|
|
210
|
+
for k, v in self._data.items()
|
|
211
|
+
if k not in ("id", "type", "color")
|
|
212
|
+
)
|
|
213
|
+
return f"{type(self).__name__}({props})"
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def id(self) -> str:
|
|
217
|
+
"""Return the widget's unique identifier."""
|
|
218
|
+
return self._id
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
widgets/_widgets1d.py
|
|
3
|
+
=====================
|
|
4
|
+
Interactive overlay widgets for 1-D line panels (Plot1D).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from anyplotlib.widgets._base import Widget
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VLineWidget(Widget):
|
|
12
|
+
"""Draggable vertical line overlay widget for 1-D plots.
|
|
13
|
+
|
|
14
|
+
Allows interactive selection of a single x-axis value. The line can be
|
|
15
|
+
dragged left/right to change the selected position.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
push_fn : Callable
|
|
20
|
+
Update callback.
|
|
21
|
+
x : float
|
|
22
|
+
Initial x-position in data coordinates.
|
|
23
|
+
color : str, optional
|
|
24
|
+
CSS colour for the line. Default ``"#00e5ff"``.
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, push_fn, *, x, color="#00e5ff"):
|
|
27
|
+
super().__init__("vline", push_fn, x=float(x), color=color)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HLineWidget(Widget):
|
|
31
|
+
"""Draggable horizontal line overlay widget for bar charts.
|
|
32
|
+
|
|
33
|
+
Allows interactive selection of a single y-axis value. The line can be
|
|
34
|
+
dragged up/down to change the selected value.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
push_fn : Callable
|
|
39
|
+
Update callback.
|
|
40
|
+
y : float
|
|
41
|
+
Initial y-position in data coordinates.
|
|
42
|
+
color : str, optional
|
|
43
|
+
CSS colour for the line. Default ``"#00e5ff"``.
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, push_fn, *, y, color="#00e5ff"):
|
|
46
|
+
super().__init__("hline", push_fn, y=float(y), color=color)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RangeWidget(Widget):
|
|
50
|
+
"""Draggable range selection widget.
|
|
51
|
+
|
|
52
|
+
Two display styles are available:
|
|
53
|
+
|
|
54
|
+
``style='band'`` (default)
|
|
55
|
+
Two connected vertical lines with a translucent fill band. Either
|
|
56
|
+
line can be dragged independently; the whole band can be dragged by
|
|
57
|
+
clicking inside it.
|
|
58
|
+
|
|
59
|
+
``style='fwhm'``
|
|
60
|
+
Two circular handles joined by a dashed horizontal line drawn at
|
|
61
|
+
height *y* (the half-maximum level). Only the x-positions of the
|
|
62
|
+
handles are draggable. Use this to show/edit a FWHM interval on a
|
|
63
|
+
peak.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
push_fn : Callable
|
|
68
|
+
Update callback.
|
|
69
|
+
x0, x1 : float
|
|
70
|
+
Initial left and right positions in data coordinates.
|
|
71
|
+
color : str, optional
|
|
72
|
+
CSS colour. Default ``"#00e5ff"``.
|
|
73
|
+
style : {'band', 'fwhm'}, optional
|
|
74
|
+
Visual style. Default ``"band"``.
|
|
75
|
+
y : float, optional
|
|
76
|
+
Y-position (data coordinates) for the connecting line when
|
|
77
|
+
``style='fwhm'``. Ignored for ``style='band'``. Default ``0.0``.
|
|
78
|
+
"""
|
|
79
|
+
def __init__(self, push_fn, *, x0, x1, color="#00e5ff",
|
|
80
|
+
style: str = "band", y: float = 0.0):
|
|
81
|
+
super().__init__("range", push_fn,
|
|
82
|
+
x0=float(x0), x1=float(x1), color=color,
|
|
83
|
+
style=str(style), y=float(y))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PointWidget(Widget):
|
|
87
|
+
"""Draggable point (control point) overlay widget for 1-D plots.
|
|
88
|
+
|
|
89
|
+
A free-moving handle that can be dragged to any position within the
|
|
90
|
+
plot area. Reports its data-space ``x`` and ``y`` coordinates back
|
|
91
|
+
to Python via the standard callback hooks.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
push_fn : Callable
|
|
96
|
+
Update callback.
|
|
97
|
+
x : float
|
|
98
|
+
Initial x position in data coordinates.
|
|
99
|
+
y : float
|
|
100
|
+
Initial y position in data coordinates (value axis).
|
|
101
|
+
color : str, optional
|
|
102
|
+
CSS colour for the handle. Default ``"#00e5ff"``.
|
|
103
|
+
show_crosshair : bool, optional
|
|
104
|
+
If ``True`` (default), draw dashed crosshair guide lines through the
|
|
105
|
+
handle. Set to ``False`` for a bare draggable dot with no guides.
|
|
106
|
+
"""
|
|
107
|
+
def __init__(self, push_fn, *, x, y, color="#00e5ff", show_crosshair=True):
|
|
108
|
+
super().__init__("point", push_fn, x=float(x), y=float(y), color=color,
|
|
109
|
+
show_crosshair=bool(show_crosshair))
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
widgets/_widgets2d.py
|
|
3
|
+
=====================
|
|
4
|
+
Interactive overlay widgets for 2-D image panels (Plot2D / InsetAxes).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from anyplotlib.widgets._base import Widget
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RectangleWidget(Widget):
|
|
12
|
+
"""Draggable rectangle overlay widget for 2-D plots.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
push_fn : Callable
|
|
17
|
+
Update callback.
|
|
18
|
+
x, y : float
|
|
19
|
+
Top-left corner position in pixel/data coordinates.
|
|
20
|
+
w, h : float
|
|
21
|
+
Width and height in pixel/data coordinates.
|
|
22
|
+
color : str, optional
|
|
23
|
+
CSS colour for the rectangle outline. Default ``"#00e5ff"``.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self, push_fn, *, x, y, w, h, color="#00e5ff"):
|
|
26
|
+
super().__init__("rectangle", push_fn,
|
|
27
|
+
x=float(x), y=float(y),
|
|
28
|
+
w=float(w), h=float(h), color=color)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CircleWidget(Widget):
|
|
32
|
+
"""Draggable circle overlay widget for 2-D plots.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
push_fn : Callable
|
|
37
|
+
Update callback.
|
|
38
|
+
cx, cy : float
|
|
39
|
+
Center position in pixel/data coordinates.
|
|
40
|
+
r : float
|
|
41
|
+
Radius in pixel/data coordinates.
|
|
42
|
+
color : str, optional
|
|
43
|
+
CSS colour for the circle outline. Default ``"#00e5ff"``.
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, push_fn, *, cx, cy, r, color="#00e5ff"):
|
|
46
|
+
super().__init__("circle", push_fn,
|
|
47
|
+
cx=float(cx), cy=float(cy), r=float(r), color=color)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AnnularWidget(Widget):
|
|
51
|
+
"""Draggable annular (ring) overlay widget for 2-D plots.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
push_fn : Callable
|
|
56
|
+
Update callback.
|
|
57
|
+
cx, cy : float
|
|
58
|
+
Center position in pixel/data coordinates.
|
|
59
|
+
r_outer, r_inner : float
|
|
60
|
+
Outer and inner radii in pixel/data coordinates.
|
|
61
|
+
Inner radius must be less than outer radius.
|
|
62
|
+
color : str, optional
|
|
63
|
+
CSS colour for the ring outline. Default ``"#00e5ff"``.
|
|
64
|
+
|
|
65
|
+
Raises
|
|
66
|
+
------
|
|
67
|
+
ValueError
|
|
68
|
+
If r_inner >= r_outer.
|
|
69
|
+
"""
|
|
70
|
+
def __init__(self, push_fn, *, cx, cy, r_outer, r_inner, color="#00e5ff"):
|
|
71
|
+
if r_inner >= r_outer:
|
|
72
|
+
raise ValueError("r_inner must be < r_outer")
|
|
73
|
+
super().__init__("annular", push_fn,
|
|
74
|
+
cx=float(cx), cy=float(cy),
|
|
75
|
+
r_outer=float(r_outer), r_inner=float(r_inner),
|
|
76
|
+
color=color)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CrosshairWidget(Widget):
|
|
80
|
+
"""Draggable crosshair overlay widget for 2-D plots.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
push_fn : Callable
|
|
85
|
+
Update callback.
|
|
86
|
+
cx, cy : float
|
|
87
|
+
Center position in pixel/data coordinates.
|
|
88
|
+
color : str, optional
|
|
89
|
+
CSS colour for the crosshair. Default ``"#00e5ff"``.
|
|
90
|
+
"""
|
|
91
|
+
def __init__(self, push_fn, *, cx, cy, color="#00e5ff"):
|
|
92
|
+
super().__init__("crosshair", push_fn,
|
|
93
|
+
cx=float(cx), cy=float(cy), color=color)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class PolygonWidget(Widget):
|
|
97
|
+
"""Draggable polygon overlay widget for 2-D plots.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
push_fn : Callable
|
|
102
|
+
Update callback.
|
|
103
|
+
vertices : list of tuple
|
|
104
|
+
Polygon vertices ``[(x0, y0), (x1, y1), ...]`` in pixel/data coordinates.
|
|
105
|
+
Must have at least 3 vertices.
|
|
106
|
+
color : str, optional
|
|
107
|
+
CSS colour for the polygon outline. Default ``"#00e5ff"``.
|
|
108
|
+
|
|
109
|
+
Raises
|
|
110
|
+
------
|
|
111
|
+
ValueError
|
|
112
|
+
If fewer than 3 vertices provided.
|
|
113
|
+
"""
|
|
114
|
+
def __init__(self, push_fn, *, vertices, color="#00e5ff"):
|
|
115
|
+
verts = [[float(x), float(y)] for x, y in vertices]
|
|
116
|
+
if len(verts) < 3:
|
|
117
|
+
raise ValueError("polygon needs >= 3 vertices")
|
|
118
|
+
super().__init__("polygon", push_fn, vertices=verts, color=color)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class LabelWidget(Widget):
|
|
122
|
+
"""Text label overlay widget for 2-D plots.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
push_fn : Callable
|
|
127
|
+
Update callback.
|
|
128
|
+
x, y : float
|
|
129
|
+
Label position in pixel/data coordinates.
|
|
130
|
+
text : str, optional
|
|
131
|
+
Label text. Default ``"Label"``.
|
|
132
|
+
fontsize : int, optional
|
|
133
|
+
Font size in points. Default 14.
|
|
134
|
+
color : str, optional
|
|
135
|
+
CSS colour for the text. Default ``"#00e5ff"``.
|
|
136
|
+
"""
|
|
137
|
+
def __init__(self, push_fn, *, x, y, text="Label", fontsize=14,
|
|
138
|
+
color="#00e5ff"):
|
|
139
|
+
super().__init__("label", push_fn,
|
|
140
|
+
x=float(x), y=float(y),
|
|
141
|
+
text=str(text), fontsize=int(fontsize), color=color)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
widgets/_widgets3d.py
|
|
3
|
+
=====================
|
|
4
|
+
Interactive overlay widgets for 3-D panels.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
from anyplotlib.widgets._base import Widget
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PlaneWidget(Widget):
|
|
15
|
+
"""A draggable axis-aligned plane in a 3-D panel.
|
|
16
|
+
|
|
17
|
+
Rendered as a translucent quad spanning the panel's bounds,
|
|
18
|
+
perpendicular to *axis* at *position*. Drag it in the browser to slide
|
|
19
|
+
it along its normal — ideal as a slice selector for voxel volumes.
|
|
20
|
+
Voxels lying on a plane are rendered more opaque (see
|
|
21
|
+
:meth:`~anyplotlib.Axes.voxels`).
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
axis : ``"x"`` | ``"y"`` | ``"z"``
|
|
26
|
+
The plane's normal axis.
|
|
27
|
+
position : float
|
|
28
|
+
Position along *axis* in data coordinates.
|
|
29
|
+
color : str, optional
|
|
30
|
+
CSS colour of the plane fill and border.
|
|
31
|
+
alpha : float, optional
|
|
32
|
+
Fill opacity (0–1). Default 0.12.
|
|
33
|
+
|
|
34
|
+
Examples
|
|
35
|
+
--------
|
|
36
|
+
>>> pw = vol.add_widget("plane", axis="z", position=24)
|
|
37
|
+
>>> @pw.add_event_handler("pointer_move")
|
|
38
|
+
... def on_drag(event):
|
|
39
|
+
... print("slice now at", pw.position)
|
|
40
|
+
>>> pw.set(position=10) # move it from Python
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, push_fn: Callable, axis: str = "z",
|
|
44
|
+
position: float = 0.0, color: str = "#00e5ff",
|
|
45
|
+
alpha: float = 0.12):
|
|
46
|
+
if axis not in ("x", "y", "z"):
|
|
47
|
+
raise ValueError(f"axis must be 'x', 'y', or 'z', got {axis!r}")
|
|
48
|
+
super().__init__("plane", push_fn,
|
|
49
|
+
axis=axis, position=float(position),
|
|
50
|
+
color=color, alpha=float(alpha))
|