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,1376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
plot1d/_plot1d.py
|
|
3
|
+
=================
|
|
4
|
+
1-D line panel (Plot1D) and its line helper (Line1D).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import uuid as _uuid
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
from anyplotlib._base_plot import _BasePlot, _PanelMixin, _MarkerMixin
|
|
15
|
+
from anyplotlib.markers import MarkerRegistry
|
|
16
|
+
from anyplotlib.callbacks import CallbackRegistry
|
|
17
|
+
from anyplotlib.widgets import (
|
|
18
|
+
Widget,
|
|
19
|
+
VLineWidget as _VLineWidget,
|
|
20
|
+
HLineWidget as _HLineWidget,
|
|
21
|
+
RangeWidget as _RangeWidget,
|
|
22
|
+
PointWidget as _PointWidget,
|
|
23
|
+
)
|
|
24
|
+
from anyplotlib._utils import _norm_linestyle, _arr_to_b64, _to_rgba_u8
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Line1D — per-line handle
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
class Line1D:
|
|
32
|
+
"""Handle to a single line on a :class:`Plot1D` panel.
|
|
33
|
+
|
|
34
|
+
Returned by :meth:`Plot1D.add_line`. Use it to update the line data,
|
|
35
|
+
register event handlers scoped to just that line, or to remove it later.
|
|
36
|
+
|
|
37
|
+
Attributes
|
|
38
|
+
----------
|
|
39
|
+
id : str | None
|
|
40
|
+
``None`` for the primary line; an 8-character UUID string for
|
|
41
|
+
overlay lines added with :meth:`Plot1D.add_line`.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, plot: "Plot1D", lid: str | None):
|
|
45
|
+
self._plot = plot
|
|
46
|
+
self._lid = lid
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def id(self) -> str | None:
|
|
50
|
+
return self._lid
|
|
51
|
+
|
|
52
|
+
def __str__(self) -> str:
|
|
53
|
+
return "" if self._lid is None else self._lid
|
|
54
|
+
|
|
55
|
+
def __repr__(self) -> str:
|
|
56
|
+
return f"Line1D(id={self._lid!r})"
|
|
57
|
+
|
|
58
|
+
def __eq__(self, other) -> bool:
|
|
59
|
+
if isinstance(other, Line1D):
|
|
60
|
+
return self._lid == other._lid
|
|
61
|
+
if isinstance(other, str):
|
|
62
|
+
return self._lid == other
|
|
63
|
+
return NotImplemented
|
|
64
|
+
|
|
65
|
+
def __hash__(self) -> int:
|
|
66
|
+
return hash(self._lid)
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
def add_event_handler(self, fn_or_type, *args, **kwargs):
|
|
70
|
+
"""Register a handler scoped to this line only.
|
|
71
|
+
|
|
72
|
+
Wraps the plot-level pointer_move / pointer_down handler
|
|
73
|
+
with a line_id filter. Only pointer_move and pointer_down
|
|
74
|
+
are meaningful on a line handle.
|
|
75
|
+
"""
|
|
76
|
+
target_lid = self._lid
|
|
77
|
+
|
|
78
|
+
if callable(fn_or_type):
|
|
79
|
+
fn = fn_or_type
|
|
80
|
+
types = args
|
|
81
|
+
return self._wrap_and_register(fn, types, target_lid, **kwargs)
|
|
82
|
+
else:
|
|
83
|
+
all_types = (fn_or_type,) + args
|
|
84
|
+
def _decorator(fn):
|
|
85
|
+
return self._wrap_and_register(fn, all_types, target_lid, **kwargs)
|
|
86
|
+
return _decorator
|
|
87
|
+
|
|
88
|
+
def _wrap_and_register(self, fn, types, target_lid, **kwargs):
|
|
89
|
+
from functools import wraps
|
|
90
|
+
@wraps(fn)
|
|
91
|
+
def _filtered(event):
|
|
92
|
+
if event.line_id == target_lid:
|
|
93
|
+
fn(event)
|
|
94
|
+
_filtered.__wrapped__ = fn
|
|
95
|
+
return self._plot.add_event_handler(_filtered, *types, **kwargs)
|
|
96
|
+
|
|
97
|
+
def remove_handler(self, cid_or_fn, *types):
|
|
98
|
+
"""Remove a handler registered via this line handle."""
|
|
99
|
+
self._plot.remove_handler(cid_or_fn, *types)
|
|
100
|
+
|
|
101
|
+
def set_data(self, y: "np.ndarray", x_axis=None) -> None:
|
|
102
|
+
"""Update the y-data (and optionally x-axis) of this overlay line.
|
|
103
|
+
|
|
104
|
+
The y-axis range is recomputed and the panel re-renders immediately.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
y : array-like, shape (N,)
|
|
109
|
+
New y values. Must be 1-D.
|
|
110
|
+
x_axis : array-like, shape (N,), optional
|
|
111
|
+
New x coordinates. If omitted the existing x-axis is kept.
|
|
112
|
+
|
|
113
|
+
Raises
|
|
114
|
+
------
|
|
115
|
+
ValueError
|
|
116
|
+
If called on the primary line (use :meth:`Plot1D.set_data`
|
|
117
|
+
instead), or if *y* is not 1-D.
|
|
118
|
+
KeyError
|
|
119
|
+
If this line has already been removed.
|
|
120
|
+
"""
|
|
121
|
+
if self._lid is None:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
"Cannot call set_data() on the primary line; "
|
|
124
|
+
"use plot.set_data() instead."
|
|
125
|
+
)
|
|
126
|
+
y = np.asarray(y, dtype=float)
|
|
127
|
+
if y.ndim != 1:
|
|
128
|
+
raise ValueError("y must be 1-D")
|
|
129
|
+
for entry in self._plot._state["extra_lines"]:
|
|
130
|
+
if entry["id"] == self._lid:
|
|
131
|
+
entry["data"] = y
|
|
132
|
+
if x_axis is not None:
|
|
133
|
+
entry["x_axis"] = np.asarray(x_axis, dtype=float)
|
|
134
|
+
break
|
|
135
|
+
else:
|
|
136
|
+
raise KeyError(self._lid)
|
|
137
|
+
self._plot._recompute_data_range()
|
|
138
|
+
self._plot._push()
|
|
139
|
+
|
|
140
|
+
def remove(self) -> None:
|
|
141
|
+
"""Remove this overlay line from its parent plot."""
|
|
142
|
+
if self._lid is None:
|
|
143
|
+
raise ValueError("Cannot remove the primary line via Line1D.remove().")
|
|
144
|
+
self._plot.remove_line(self._lid)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# Plot1D
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
class Plot1D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
152
|
+
"""1-D line plot panel returned by :meth:`Axes.plot`.
|
|
153
|
+
|
|
154
|
+
All display state is stored in a plain ``_state`` dict. Every mutation
|
|
155
|
+
ends with :meth:`_push`, which serialises the state to the parent
|
|
156
|
+
``Figure`` trait so the JS renderer picks up the change immediately.
|
|
157
|
+
|
|
158
|
+
Supported line properties
|
|
159
|
+
-------------------------
|
|
160
|
+
Set at construction time via :meth:`Axes.plot` or updated afterwards
|
|
161
|
+
with the corresponding setter:
|
|
162
|
+
|
|
163
|
+
.. list-table::
|
|
164
|
+
:header-rows: 1
|
|
165
|
+
:widths: 18 18 64
|
|
166
|
+
|
|
167
|
+
* - Parameter
|
|
168
|
+
- Default
|
|
169
|
+
- Description
|
|
170
|
+
* - ``color``
|
|
171
|
+
- ``"#4fc3f7"``
|
|
172
|
+
- CSS colour string for the primary line.
|
|
173
|
+
* - ``linewidth``
|
|
174
|
+
- ``1.5``
|
|
175
|
+
- Stroke width in pixels.
|
|
176
|
+
* - ``linestyle`` (``ls``)
|
|
177
|
+
- ``"solid"``
|
|
178
|
+
- Dash pattern: ``"solid"``, ``"dashed"``, ``"dotted"``,
|
|
179
|
+
``"dashdot"``. Shorthands ``"-"``, ``"--"``, ``":"``,
|
|
180
|
+
``"-."`` also accepted.
|
|
181
|
+
* - ``alpha``
|
|
182
|
+
- ``1.0``
|
|
183
|
+
- Line opacity (0 = transparent, 1 = fully opaque).
|
|
184
|
+
* - ``marker``
|
|
185
|
+
- ``"none"``
|
|
186
|
+
- Per-point symbol: ``"o"`` (circle), ``"s"`` (square),
|
|
187
|
+
``"^"``/``"v"`` (triangles), ``"D"`` (diamond),
|
|
188
|
+
``"+"``/``"x"`` (stroke-only), or ``"none"``.
|
|
189
|
+
* - ``markersize``
|
|
190
|
+
- ``4.0``
|
|
191
|
+
- Marker radius / half-side in pixels.
|
|
192
|
+
* - ``label``
|
|
193
|
+
- ``""``
|
|
194
|
+
- Legend label (empty string = no legend entry).
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
Public API summary
|
|
198
|
+
------------------
|
|
199
|
+
|
|
200
|
+
**Data**
|
|
201
|
+
:meth:`update` — replace y-data (and optionally the x-axis /
|
|
202
|
+
units) without recreating the panel.
|
|
203
|
+
|
|
204
|
+
**Overlay lines**
|
|
205
|
+
:meth:`add_line` / :meth:`remove_line` / :meth:`clear_lines` —
|
|
206
|
+
overlay additional curves on the same axes.
|
|
207
|
+
|
|
208
|
+
**Shaded spans**
|
|
209
|
+
:meth:`add_span` / :meth:`remove_span` / :meth:`clear_spans` —
|
|
210
|
+
highlight a region along the x- or y-axis.
|
|
211
|
+
|
|
212
|
+
**View control**
|
|
213
|
+
:meth:`set_view` / :meth:`reset_view` — programmatic pan/zoom
|
|
214
|
+
(users can also pan/zoom interactively with the mouse; press **R**
|
|
215
|
+
to reset).
|
|
216
|
+
|
|
217
|
+
**Interactive widgets**
|
|
218
|
+
:meth:`add_vline_widget` / :meth:`add_hline_widget` /
|
|
219
|
+
:meth:`add_range_widget` — draggable overlays that report their
|
|
220
|
+
position back to Python via callbacks. Manage them with
|
|
221
|
+
:meth:`get_widget`, :meth:`remove_widget`, :meth:`list_widgets`,
|
|
222
|
+
and :meth:`clear_widgets`.
|
|
223
|
+
|
|
224
|
+
**Static marker collections**
|
|
225
|
+
:meth:`add_points` / :meth:`add_circles` / :meth:`add_vlines` /
|
|
226
|
+
:meth:`add_hlines` / :meth:`add_arrows` / :meth:`add_ellipses` /
|
|
227
|
+
:meth:`add_lines` / :meth:`add_rectangles` / :meth:`add_squares` /
|
|
228
|
+
:meth:`add_polygons` / :meth:`add_texts` — fixed overlays
|
|
229
|
+
positioned at explicit data coordinates. Access them via
|
|
230
|
+
``plot.markers[type][name]`` and manage with :meth:`remove_marker`,
|
|
231
|
+
:meth:`clear_markers`, and :meth:`list_markers`.
|
|
232
|
+
|
|
233
|
+
**Callbacks**
|
|
234
|
+
:meth:`on_changed` / :meth:`on_release` / :meth:`on_click` /
|
|
235
|
+
:meth:`on_key` — react to pan/zoom frames, mouse clicks, and
|
|
236
|
+
key-presses. Remove a handler with :meth:`disconnect`.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
def __init__(self, data: np.ndarray,
|
|
240
|
+
x_axis=None,
|
|
241
|
+
units: str = "px",
|
|
242
|
+
y_units: str = "",
|
|
243
|
+
color: str = "#4fc3f7",
|
|
244
|
+
linewidth: float = 1.5,
|
|
245
|
+
linestyle: str = "solid",
|
|
246
|
+
alpha: float = 1.0,
|
|
247
|
+
marker: str = "none",
|
|
248
|
+
markersize: float = 4.0,
|
|
249
|
+
label: str = "",
|
|
250
|
+
yscale: str = "linear"):
|
|
251
|
+
self._id: str = ""
|
|
252
|
+
self._fig: object = None
|
|
253
|
+
|
|
254
|
+
if yscale not in ("linear", "log"):
|
|
255
|
+
raise ValueError("yscale must be 'linear' or 'log'")
|
|
256
|
+
|
|
257
|
+
data = np.asarray(data, dtype=float)
|
|
258
|
+
if data.ndim != 1:
|
|
259
|
+
raise ValueError(f"data must be 1-D, got {data.shape}")
|
|
260
|
+
n = len(data)
|
|
261
|
+
if x_axis is None:
|
|
262
|
+
x_axis = np.arange(n, dtype=float)
|
|
263
|
+
x_axis = np.asarray(x_axis, dtype=float)
|
|
264
|
+
if len(x_axis) != n:
|
|
265
|
+
raise ValueError("x_axis length must match data length")
|
|
266
|
+
|
|
267
|
+
dmin = float(np.nanmin(data))
|
|
268
|
+
dmax = float(np.nanmax(data))
|
|
269
|
+
pad = (dmax - dmin) * 0.05 if dmax > dmin else 0.5
|
|
270
|
+
dmin -= pad; dmax += pad
|
|
271
|
+
|
|
272
|
+
self._state: dict = {
|
|
273
|
+
"kind": "1d",
|
|
274
|
+
"data": data, # numpy float64 — encoded in to_state_dict()
|
|
275
|
+
"x_axis": x_axis, # numpy float64 — encoded in to_state_dict()
|
|
276
|
+
"units": units,
|
|
277
|
+
"y_units": y_units,
|
|
278
|
+
"data_min": dmin,
|
|
279
|
+
"data_max": dmax,
|
|
280
|
+
"view_x0": 0.0,
|
|
281
|
+
"view_x1": 1.0,
|
|
282
|
+
"line_color": color,
|
|
283
|
+
"line_linewidth": float(linewidth),
|
|
284
|
+
"line_linestyle": _norm_linestyle(linestyle),
|
|
285
|
+
"line_alpha": float(alpha),
|
|
286
|
+
"line_marker": marker if marker is not None else "none",
|
|
287
|
+
"line_markersize": float(markersize),
|
|
288
|
+
"line_label": label,
|
|
289
|
+
"extra_lines": [],
|
|
290
|
+
"spans": [],
|
|
291
|
+
"overlay_widgets": [],
|
|
292
|
+
"markers": [],
|
|
293
|
+
"pointer_settled_ms": 0,
|
|
294
|
+
"pointer_settled_delta": 4,
|
|
295
|
+
"yscale": yscale,
|
|
296
|
+
# Annotation labels
|
|
297
|
+
"title": "",
|
|
298
|
+
# Explicit y-range override: [ymin, ymax] or None (auto)
|
|
299
|
+
"y_range": None,
|
|
300
|
+
# Visibility toggles
|
|
301
|
+
"axis_visible": True,
|
|
302
|
+
"x_ticks_visible": True,
|
|
303
|
+
"y_ticks_visible": True,
|
|
304
|
+
"_view_from_python": False,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
self.markers = MarkerRegistry(self._push_markers,
|
|
308
|
+
allowed=MarkerRegistry._KNOWN_1D)
|
|
309
|
+
self.callbacks = CallbackRegistry()
|
|
310
|
+
self._widgets: dict[str, Widget] = {}
|
|
311
|
+
|
|
312
|
+
def to_state_dict(self) -> dict:
|
|
313
|
+
d = dict(self._state)
|
|
314
|
+
# Replace numpy arrays with b64-encoded strings for the wire format.
|
|
315
|
+
data_arr = d.pop("data")
|
|
316
|
+
x_arr = d.pop("x_axis")
|
|
317
|
+
d["data_b64"] = _arr_to_b64(data_arr, np.float64)
|
|
318
|
+
d["x_axis_b64"] = _arr_to_b64(x_arr, np.float64)
|
|
319
|
+
# Encode extra-line arrays too
|
|
320
|
+
new_extra = []
|
|
321
|
+
for ex in d["extra_lines"]:
|
|
322
|
+
ex2 = dict(ex)
|
|
323
|
+
ex2["data_b64"] = _arr_to_b64(ex2.pop("data"), np.float64)
|
|
324
|
+
ex2["x_axis_b64"] = _arr_to_b64(
|
|
325
|
+
np.asarray(ex2.pop("x_axis"), dtype=np.float64), np.float64)
|
|
326
|
+
new_extra.append(ex2)
|
|
327
|
+
d["extra_lines"] = new_extra
|
|
328
|
+
d["overlay_widgets"] = [w.to_dict() for w in self._widgets.values()]
|
|
329
|
+
markers = self.markers.to_wire_list()
|
|
330
|
+
# Hoist any raster image bytes into the deduped geometry channel so a
|
|
331
|
+
# view-only push never re-serialises the (potentially large) RGBA blob.
|
|
332
|
+
# The lightweight marker dict keeps only id + extent/clip_path/transform;
|
|
333
|
+
# JS re-joins them via st.raster_geom[id] (see _applyGeom / draw1d).
|
|
334
|
+
raster_geom: dict = {}
|
|
335
|
+
for ms in markers:
|
|
336
|
+
if ms.get("type") == "raster" and "image_b64" in ms:
|
|
337
|
+
raster_geom[ms["id"]] = {
|
|
338
|
+
"image_b64": ms.pop("image_b64"),
|
|
339
|
+
"image_width": ms["image_width"],
|
|
340
|
+
"image_height": ms["image_height"],
|
|
341
|
+
}
|
|
342
|
+
if raster_geom:
|
|
343
|
+
d["raster_geom"] = raster_geom
|
|
344
|
+
d["markers"] = markers
|
|
345
|
+
return d
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def line(self) -> "Line1D":
|
|
349
|
+
"""Handle for the primary line, enabling per-line callbacks.
|
|
350
|
+
|
|
351
|
+
Returns a :class:`Line1D` with ``id=None`` so you can register
|
|
352
|
+
hover / click handlers scoped to just the primary line::
|
|
353
|
+
|
|
354
|
+
@plot.line.on_click
|
|
355
|
+
def on_primary_click(event):
|
|
356
|
+
print(f"primary line clicked at x={event.x:.3f}")
|
|
357
|
+
"""
|
|
358
|
+
return Line1D(self, None)
|
|
359
|
+
|
|
360
|
+
# ------------------------------------------------------------------
|
|
361
|
+
# Data
|
|
362
|
+
# ------------------------------------------------------------------
|
|
363
|
+
@property
|
|
364
|
+
def data(self) -> np.ndarray:
|
|
365
|
+
"""The primary line's y-data (read-only).
|
|
366
|
+
|
|
367
|
+
Returns a float64 copy with ``writeable=False``. To replace the
|
|
368
|
+
data call :meth:`set_data`.
|
|
369
|
+
"""
|
|
370
|
+
arr = self._state["data"].copy()
|
|
371
|
+
arr.flags.writeable = False
|
|
372
|
+
return arr
|
|
373
|
+
|
|
374
|
+
def set_data(self, data: np.ndarray, x_axis=None,
|
|
375
|
+
units: str | None = None, y_units: str | None = None) -> None:
|
|
376
|
+
"""Replace the primary line's y-data and optionally its x-axis / units.
|
|
377
|
+
|
|
378
|
+
The y-axis range (``data_min`` / ``data_max``) is recomputed
|
|
379
|
+
automatically. The viewport is **not** reset — call
|
|
380
|
+
:meth:`reset_view` explicitly if needed.
|
|
381
|
+
|
|
382
|
+
Parameters
|
|
383
|
+
----------
|
|
384
|
+
data : array-like, shape (N,)
|
|
385
|
+
New y values. Must be 1-D.
|
|
386
|
+
x_axis : array-like, shape (N,), optional
|
|
387
|
+
New x coordinates. If omitted and the length of *data* matches
|
|
388
|
+
the current x-axis, the existing x-axis is reused; otherwise it
|
|
389
|
+
is reset to ``0, 1, …, N-1``.
|
|
390
|
+
units : str, optional
|
|
391
|
+
New x-axis label. Unchanged if not supplied.
|
|
392
|
+
y_units : str, optional
|
|
393
|
+
New y-axis label. Unchanged if not supplied.
|
|
394
|
+
"""
|
|
395
|
+
data = np.asarray(data, dtype=float)
|
|
396
|
+
if data.ndim != 1:
|
|
397
|
+
raise ValueError(f"data must be 1-D, got {data.shape}")
|
|
398
|
+
n = len(data)
|
|
399
|
+
if x_axis is None:
|
|
400
|
+
prev = self._state["x_axis"] # already a numpy array
|
|
401
|
+
x_axis = prev if len(prev) == n else np.arange(n, dtype=float)
|
|
402
|
+
x_axis = np.asarray(x_axis, dtype=float)
|
|
403
|
+
|
|
404
|
+
dmin = float(np.nanmin(data))
|
|
405
|
+
dmax = float(np.nanmax(data))
|
|
406
|
+
pad = (dmax - dmin) * 0.05 if dmax > dmin else 0.5
|
|
407
|
+
|
|
408
|
+
self._state["data"] = data
|
|
409
|
+
self._state["x_axis"] = x_axis
|
|
410
|
+
self._state["data_min"] = dmin - pad
|
|
411
|
+
self._state["data_max"] = dmax + pad
|
|
412
|
+
if units is not None: self._state["units"] = units
|
|
413
|
+
if y_units is not None: self._state["y_units"] = y_units
|
|
414
|
+
self._push()
|
|
415
|
+
|
|
416
|
+
def _recompute_data_range(self) -> None:
|
|
417
|
+
"""Recompute data_min/data_max across the primary line and all overlays.
|
|
418
|
+
|
|
419
|
+
Called automatically whenever the set of lines changes so that every
|
|
420
|
+
curve stays fully visible.
|
|
421
|
+
"""
|
|
422
|
+
all_vals = [self._state["data"]] # already a numpy float64 array
|
|
423
|
+
for ex in self._state["extra_lines"]:
|
|
424
|
+
d = ex.get("data")
|
|
425
|
+
if d is not None and len(d):
|
|
426
|
+
all_vals.append(d)
|
|
427
|
+
combined = np.concatenate(all_vals)
|
|
428
|
+
dmin = float(np.nanmin(combined))
|
|
429
|
+
dmax = float(np.nanmax(combined))
|
|
430
|
+
pad = (dmax - dmin) * 0.05 if dmax > dmin else 0.5
|
|
431
|
+
self._state["data_min"] = dmin - pad
|
|
432
|
+
self._state["data_max"] = dmax + pad
|
|
433
|
+
|
|
434
|
+
# ------------------------------------------------------------------
|
|
435
|
+
# Extra lines
|
|
436
|
+
# ------------------------------------------------------------------
|
|
437
|
+
def add_line(self, data: np.ndarray, x_axis=None,
|
|
438
|
+
color: str = "#4fc3f7", linewidth: float = 1.5,
|
|
439
|
+
linestyle: str = "solid", ls: str | None = None,
|
|
440
|
+
alpha: float = 1.0,
|
|
441
|
+
marker: str = "none", markersize: float = 4.0,
|
|
442
|
+
label: str = "") -> "Line1D":
|
|
443
|
+
"""Overlay an additional curve on this panel.
|
|
444
|
+
|
|
445
|
+
The y-axis range is automatically expanded to include the new data so
|
|
446
|
+
all lines remain fully visible.
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
data : array-like, shape (N,)
|
|
451
|
+
Y values for the new line. Must be 1-D.
|
|
452
|
+
x_axis : array-like, shape (N,), optional
|
|
453
|
+
X coordinates. Defaults to the primary line's x-axis.
|
|
454
|
+
color : str, optional
|
|
455
|
+
CSS colour string. Default ``"#4fc3f7"``.
|
|
456
|
+
linewidth : float, optional
|
|
457
|
+
Stroke width in pixels. Default ``1.5``.
|
|
458
|
+
linestyle : str, optional
|
|
459
|
+
Dash pattern: ``"solid"``, ``"dashed"``, ``"dotted"``,
|
|
460
|
+
``"dashdot"`` (or shorthands). Default ``"solid"``.
|
|
461
|
+
ls : str, optional
|
|
462
|
+
Short alias for *linestyle*.
|
|
463
|
+
alpha : float, optional
|
|
464
|
+
Line opacity (0–1). Default ``1.0``.
|
|
465
|
+
marker : str, optional
|
|
466
|
+
Per-point marker symbol (see :class:`Plot1D`). Default
|
|
467
|
+
``"none"``.
|
|
468
|
+
markersize : float, optional
|
|
469
|
+
Marker radius / half-side in pixels. Default ``4.0``.
|
|
470
|
+
label : str, optional
|
|
471
|
+
Legend label. Default ``""`` (no legend entry).
|
|
472
|
+
|
|
473
|
+
Returns
|
|
474
|
+
-------
|
|
475
|
+
Line1D
|
|
476
|
+
A handle to the new overlay line. Use it to register
|
|
477
|
+
per-line hover/click callbacks or to remove the line later::
|
|
478
|
+
|
|
479
|
+
line = v.add_line(fit, color="#ffcc00", label="fit")
|
|
480
|
+
line.remove() # remove it
|
|
481
|
+
@line.on_click # per-line click handler
|
|
482
|
+
def clicked(event): ...
|
|
483
|
+
"""
|
|
484
|
+
data = np.asarray(data, dtype=float)
|
|
485
|
+
if data.ndim != 1:
|
|
486
|
+
raise ValueError("data must be 1-D")
|
|
487
|
+
xa = (np.asarray(x_axis, dtype=float) if x_axis is not None
|
|
488
|
+
else self._state["x_axis"])
|
|
489
|
+
lid = str(_uuid.uuid4())[:8]
|
|
490
|
+
self._state["extra_lines"].append({
|
|
491
|
+
"id": lid,
|
|
492
|
+
"data": data,
|
|
493
|
+
"x_axis": xa,
|
|
494
|
+
"color": color,
|
|
495
|
+
"linewidth": float(linewidth),
|
|
496
|
+
"linestyle": _norm_linestyle(ls if ls is not None else linestyle),
|
|
497
|
+
"alpha": float(alpha),
|
|
498
|
+
"marker": marker if marker is not None else "none",
|
|
499
|
+
"markersize": float(markersize),
|
|
500
|
+
"label": label,
|
|
501
|
+
})
|
|
502
|
+
self._recompute_data_range()
|
|
503
|
+
self._push()
|
|
504
|
+
return Line1D(self, lid)
|
|
505
|
+
|
|
506
|
+
def remove_line(self, lid: "str | Line1D") -> None:
|
|
507
|
+
"""Remove an overlay line by its ID or :class:`Line1D` handle.
|
|
508
|
+
|
|
509
|
+
The y-axis range is recomputed after removal.
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
lid : str or Line1D
|
|
514
|
+
The value returned by :meth:`add_line`.
|
|
515
|
+
|
|
516
|
+
Raises
|
|
517
|
+
------
|
|
518
|
+
KeyError
|
|
519
|
+
If *lid* does not match any overlay line.
|
|
520
|
+
"""
|
|
521
|
+
if isinstance(lid, Line1D):
|
|
522
|
+
lid = lid._lid
|
|
523
|
+
before = len(self._state["extra_lines"])
|
|
524
|
+
self._state["extra_lines"] = [
|
|
525
|
+
e for e in self._state["extra_lines"] if e["id"] != lid]
|
|
526
|
+
if len(self._state["extra_lines"]) == before:
|
|
527
|
+
raise KeyError(lid)
|
|
528
|
+
self._recompute_data_range()
|
|
529
|
+
self._push()
|
|
530
|
+
|
|
531
|
+
def clear_lines(self) -> None:
|
|
532
|
+
"""Remove all overlay lines, leaving the primary line intact.
|
|
533
|
+
|
|
534
|
+
The y-axis range is recomputed after clearing.
|
|
535
|
+
"""
|
|
536
|
+
self._state["extra_lines"] = []
|
|
537
|
+
self._recompute_data_range()
|
|
538
|
+
self._push()
|
|
539
|
+
|
|
540
|
+
# ------------------------------------------------------------------
|
|
541
|
+
# Spans
|
|
542
|
+
# ------------------------------------------------------------------
|
|
543
|
+
def add_span(self, v0: float, v1: float,
|
|
544
|
+
axis: str = "x", color: str | None = None) -> str:
|
|
545
|
+
"""Add a shaded span along the x- or y-axis.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
v0, v1 : float
|
|
550
|
+
Start and end of the span in data coordinates.
|
|
551
|
+
axis : ``"x"`` | ``"y"``, optional
|
|
552
|
+
Which axis the span runs along. Default ``"x"``.
|
|
553
|
+
color : str, optional
|
|
554
|
+
CSS colour string (supports alpha, e.g.
|
|
555
|
+
``"rgba(255,200,0,0.2)"``). Defaults to a theme-appropriate
|
|
556
|
+
yellow tint.
|
|
557
|
+
|
|
558
|
+
Returns
|
|
559
|
+
-------
|
|
560
|
+
str
|
|
561
|
+
Span ID for use with :meth:`remove_span`.
|
|
562
|
+
"""
|
|
563
|
+
sid = str(_uuid.uuid4())[:8]
|
|
564
|
+
self._state["spans"].append({
|
|
565
|
+
"id": sid, "v0": float(v0), "v1": float(v1),
|
|
566
|
+
"axis": axis, "color": color,
|
|
567
|
+
})
|
|
568
|
+
self._push()
|
|
569
|
+
return sid
|
|
570
|
+
|
|
571
|
+
def remove_span(self, sid: str) -> None:
|
|
572
|
+
"""Remove a shaded span by its ID.
|
|
573
|
+
|
|
574
|
+
Parameters
|
|
575
|
+
----------
|
|
576
|
+
sid : str
|
|
577
|
+
The ID returned by :meth:`add_span`.
|
|
578
|
+
|
|
579
|
+
Raises
|
|
580
|
+
------
|
|
581
|
+
KeyError
|
|
582
|
+
If *sid* does not match any span.
|
|
583
|
+
"""
|
|
584
|
+
before = len(self._state["spans"])
|
|
585
|
+
self._state["spans"] = [
|
|
586
|
+
s for s in self._state["spans"] if s["id"] != sid]
|
|
587
|
+
if len(self._state["spans"]) == before:
|
|
588
|
+
raise KeyError(sid)
|
|
589
|
+
self._push()
|
|
590
|
+
|
|
591
|
+
def clear_spans(self) -> None:
|
|
592
|
+
"""Remove all shaded spans."""
|
|
593
|
+
self._state["spans"] = []
|
|
594
|
+
self._push()
|
|
595
|
+
|
|
596
|
+
# ------------------------------------------------------------------
|
|
597
|
+
# Overlay Widgets
|
|
598
|
+
# ------------------------------------------------------------------
|
|
599
|
+
def add_vline_widget(self, x: float, color: str = "#00e5ff") -> _VLineWidget:
|
|
600
|
+
"""Add a draggable vertical-line overlay.
|
|
601
|
+
|
|
602
|
+
Parameters
|
|
603
|
+
----------
|
|
604
|
+
x : float
|
|
605
|
+
Initial x position in data coordinates.
|
|
606
|
+
color : str, optional
|
|
607
|
+
CSS colour string. Default ``"#00e5ff"``.
|
|
608
|
+
|
|
609
|
+
Returns
|
|
610
|
+
-------
|
|
611
|
+
VLineWidget
|
|
612
|
+
Widget object. Register position callbacks with
|
|
613
|
+
:meth:`on_changed` / :meth:`on_release`.
|
|
614
|
+
"""
|
|
615
|
+
widget = _VLineWidget(lambda: None, x=float(x), color=color)
|
|
616
|
+
widget._push_fn = self._make_widget_push_fn(widget)
|
|
617
|
+
self._widgets[widget.id] = widget
|
|
618
|
+
self._push()
|
|
619
|
+
return widget
|
|
620
|
+
|
|
621
|
+
def add_hline_widget(self, y: float, color: str = "#00e5ff") -> _HLineWidget:
|
|
622
|
+
"""Add a draggable horizontal-line overlay.
|
|
623
|
+
|
|
624
|
+
Parameters
|
|
625
|
+
----------
|
|
626
|
+
y : float
|
|
627
|
+
Initial y position in data coordinates.
|
|
628
|
+
color : str, optional
|
|
629
|
+
CSS colour string. Default ``"#00e5ff"``.
|
|
630
|
+
|
|
631
|
+
Returns
|
|
632
|
+
-------
|
|
633
|
+
HLineWidget
|
|
634
|
+
Widget object. Register position callbacks with
|
|
635
|
+
:meth:`on_changed` / :meth:`on_release`.
|
|
636
|
+
"""
|
|
637
|
+
widget = _HLineWidget(lambda: None, y=float(y), color=color)
|
|
638
|
+
widget._push_fn = self._make_widget_push_fn(widget)
|
|
639
|
+
self._widgets[widget.id] = widget
|
|
640
|
+
self._push()
|
|
641
|
+
return widget
|
|
642
|
+
|
|
643
|
+
def add_range_widget(self, x0: float, x1: float,
|
|
644
|
+
color: str = "#00e5ff",
|
|
645
|
+
style: str = "band",
|
|
646
|
+
y: float = 0.0,
|
|
647
|
+
_push: bool = True) -> _RangeWidget:
|
|
648
|
+
"""Add a draggable range overlay to this panel.
|
|
649
|
+
|
|
650
|
+
Parameters
|
|
651
|
+
----------
|
|
652
|
+
x0, x1 : float
|
|
653
|
+
Initial left and right edges in data coordinates.
|
|
654
|
+
color : str, optional
|
|
655
|
+
CSS colour string. Default ``"#00e5ff"``.
|
|
656
|
+
style : {'band', 'fwhm'}, optional
|
|
657
|
+
Visual style. ``'band'`` (default) draws two vertical lines with
|
|
658
|
+
a translucent fill. ``'fwhm'`` draws two draggable circles
|
|
659
|
+
connected by a dashed horizontal line at *y* (the half-maximum
|
|
660
|
+
level), giving an ``o-------o`` FWHM indicator.
|
|
661
|
+
y : float, optional
|
|
662
|
+
Y-coordinate (data space) for the connecting line when
|
|
663
|
+
``style='fwhm'``. Ignored when ``style='band'``. Default 0.
|
|
664
|
+
_push : bool, optional
|
|
665
|
+
Push state to JS immediately. Set to ``False`` when adding
|
|
666
|
+
several widgets at once; call :meth:`_push` manually afterward.
|
|
667
|
+
|
|
668
|
+
Returns
|
|
669
|
+
-------
|
|
670
|
+
RangeWidget
|
|
671
|
+
Widget object. Register position callbacks with
|
|
672
|
+
:meth:`on_changed` / :meth:`on_release`.
|
|
673
|
+
"""
|
|
674
|
+
widget = _RangeWidget(lambda: None, x0=float(x0), x1=float(x1),
|
|
675
|
+
color=color, style=style, y=float(y))
|
|
676
|
+
widget._push_fn = self._make_widget_push_fn(widget)
|
|
677
|
+
self._widgets[widget.id] = widget
|
|
678
|
+
if _push:
|
|
679
|
+
self._push()
|
|
680
|
+
return widget
|
|
681
|
+
|
|
682
|
+
def add_point_widget(self, x: float, y: float,
|
|
683
|
+
color: str = "#00e5ff",
|
|
684
|
+
show_crosshair: bool = True,
|
|
685
|
+
_push: bool = True) -> _PointWidget:
|
|
686
|
+
"""Add a freely-draggable control point to this panel.
|
|
687
|
+
|
|
688
|
+
Parameters
|
|
689
|
+
----------
|
|
690
|
+
x : float
|
|
691
|
+
Initial x position in data coordinates.
|
|
692
|
+
y : float
|
|
693
|
+
Initial y position in data coordinates (value axis).
|
|
694
|
+
color : str, optional
|
|
695
|
+
CSS colour string. Default ``"#00e5ff"``.
|
|
696
|
+
show_crosshair : bool, optional
|
|
697
|
+
Draw dashed guide lines through the handle. Default ``True``.
|
|
698
|
+
Pass ``False`` for a plain dot with no guide lines.
|
|
699
|
+
_push : bool, optional
|
|
700
|
+
Push state to JS immediately. Set to ``False`` when adding
|
|
701
|
+
several widgets at once; call :meth:`_push` manually afterward.
|
|
702
|
+
|
|
703
|
+
Returns
|
|
704
|
+
-------
|
|
705
|
+
PointWidget
|
|
706
|
+
"""
|
|
707
|
+
widget = _PointWidget(lambda: None, x=float(x), y=float(y), color=color,
|
|
708
|
+
show_crosshair=show_crosshair)
|
|
709
|
+
widget._push_fn = self._make_widget_push_fn(widget)
|
|
710
|
+
self._widgets[widget.id] = widget
|
|
711
|
+
if _push:
|
|
712
|
+
self._push()
|
|
713
|
+
return widget
|
|
714
|
+
|
|
715
|
+
# ------------------------------------------------------------------
|
|
716
|
+
# View control
|
|
717
|
+
# ------------------------------------------------------------------
|
|
718
|
+
def set_view(self, x0: float | None = None, x1: float | None = None) -> None:
|
|
719
|
+
"""Programmatically set the visible x range.
|
|
720
|
+
|
|
721
|
+
Parameters
|
|
722
|
+
----------
|
|
723
|
+
x0 : float, optional
|
|
724
|
+
Left edge of the view in data coordinates. ``None`` keeps the
|
|
725
|
+
current left edge.
|
|
726
|
+
x1 : float, optional
|
|
727
|
+
Right edge of the view in data coordinates. ``None`` keeps the
|
|
728
|
+
current right edge.
|
|
729
|
+
"""
|
|
730
|
+
xarr = np.asarray(self._state["x_axis"])
|
|
731
|
+
if len(xarr) < 2:
|
|
732
|
+
return
|
|
733
|
+
xmin, xmax = float(xarr[0]), float(xarr[-1])
|
|
734
|
+
span = xmax - xmin or 1.0
|
|
735
|
+
f0 = 0.0 if x0 is None else max(0.0, min(1.0, (float(x0)-xmin)/span))
|
|
736
|
+
f1 = 1.0 if x1 is None else max(0.0, min(1.0, (float(x1)-xmin)/span))
|
|
737
|
+
with self._python_view_push():
|
|
738
|
+
self._state["view_x0"] = f0
|
|
739
|
+
self._state["view_x1"] = f1
|
|
740
|
+
|
|
741
|
+
def reset_view(self) -> None:
|
|
742
|
+
"""Reset the view to show the full x range of the primary line."""
|
|
743
|
+
with self._python_view_push():
|
|
744
|
+
self._state["view_x0"] = 0.0
|
|
745
|
+
self._state["view_x1"] = 1.0
|
|
746
|
+
|
|
747
|
+
# ------------------------------------------------------------------
|
|
748
|
+
# Primary-line property setters
|
|
749
|
+
# ------------------------------------------------------------------
|
|
750
|
+
|
|
751
|
+
def set_color(self, color: str) -> None:
|
|
752
|
+
"""Set the primary line colour.
|
|
753
|
+
|
|
754
|
+
Parameters
|
|
755
|
+
----------
|
|
756
|
+
color : str
|
|
757
|
+
Any CSS colour string (hex, ``rgb()``, named colour, etc.).
|
|
758
|
+
"""
|
|
759
|
+
self._state["line_color"] = color
|
|
760
|
+
self._push()
|
|
761
|
+
|
|
762
|
+
def set_linewidth(self, linewidth: float) -> None:
|
|
763
|
+
"""Set the primary line stroke width.
|
|
764
|
+
|
|
765
|
+
Parameters
|
|
766
|
+
----------
|
|
767
|
+
linewidth : float
|
|
768
|
+
Stroke width in pixels.
|
|
769
|
+
"""
|
|
770
|
+
self._state["line_linewidth"] = float(linewidth)
|
|
771
|
+
self._push()
|
|
772
|
+
|
|
773
|
+
def set_linestyle(self, linestyle: str) -> None:
|
|
774
|
+
"""Set the primary line dash pattern.
|
|
775
|
+
|
|
776
|
+
Parameters
|
|
777
|
+
----------
|
|
778
|
+
linestyle : str
|
|
779
|
+
``"solid"`` (``"-"``), ``"dashed"`` (``"--"``),
|
|
780
|
+
``"dotted"`` (``":"``), or ``"dashdot"`` (``"-."``)
|
|
781
|
+
"""
|
|
782
|
+
self._state["line_linestyle"] = _norm_linestyle(linestyle)
|
|
783
|
+
self._push()
|
|
784
|
+
|
|
785
|
+
def set_alpha(self, alpha: float) -> None:
|
|
786
|
+
"""Set the primary line opacity.
|
|
787
|
+
|
|
788
|
+
Parameters
|
|
789
|
+
----------
|
|
790
|
+
alpha : float
|
|
791
|
+
Opacity in the range 0 (transparent) to 1 (fully opaque).
|
|
792
|
+
"""
|
|
793
|
+
self._state["line_alpha"] = float(alpha)
|
|
794
|
+
self._push()
|
|
795
|
+
|
|
796
|
+
def set_marker(self, marker: str, markersize: float | None = None) -> None:
|
|
797
|
+
"""Set the primary line per-point marker symbol.
|
|
798
|
+
|
|
799
|
+
Parameters
|
|
800
|
+
----------
|
|
801
|
+
marker : str
|
|
802
|
+
``"o"``, ``"s"``, ``"^"``, ``"v"``, ``"D"``, ``"+"``,
|
|
803
|
+
``"x"``, or ``"none"``.
|
|
804
|
+
markersize : float, optional
|
|
805
|
+
Marker radius / half-side in pixels. Unchanged if not supplied.
|
|
806
|
+
"""
|
|
807
|
+
self._state["line_marker"] = marker if marker is not None else "none"
|
|
808
|
+
if markersize is not None:
|
|
809
|
+
self._state["line_markersize"] = float(markersize)
|
|
810
|
+
self._push()
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def color(self) -> str:
|
|
814
|
+
return self._state["line_color"]
|
|
815
|
+
|
|
816
|
+
@property
|
|
817
|
+
def x(self) -> np.ndarray:
|
|
818
|
+
return np.asarray(self._state["x_axis"])
|
|
819
|
+
|
|
820
|
+
@property
|
|
821
|
+
def y(self) -> np.ndarray:
|
|
822
|
+
return np.asarray(self._state["data"])
|
|
823
|
+
|
|
824
|
+
def set_xlabel(self, label: str, fontsize: float | None = None) -> None:
|
|
825
|
+
"""Set the x-axis label.
|
|
826
|
+
|
|
827
|
+
Parameters
|
|
828
|
+
----------
|
|
829
|
+
label : str
|
|
830
|
+
Label text. Supports the mini-TeX subset for scientific
|
|
831
|
+
notation, e.g. ``r"Energy ($10^{-3}$ eV)"`` or ``r"$\\Delta t$ (s)"``
|
|
832
|
+
— see :class:`~anyplotlib._base_plot._BasePlot` notes.
|
|
833
|
+
fontsize : float, optional
|
|
834
|
+
Font size in CSS pixels. Default 9. ``None`` keeps the
|
|
835
|
+
current size.
|
|
836
|
+
"""
|
|
837
|
+
self._set_label("units", label, "x_label_size", fontsize)
|
|
838
|
+
|
|
839
|
+
def set_ylabel(self, label: str, fontsize: float | None = None) -> None:
|
|
840
|
+
"""Set the y-axis label. Same semantics as :meth:`set_xlabel`."""
|
|
841
|
+
self._set_label("y_units", label, "y_label_size", fontsize)
|
|
842
|
+
|
|
843
|
+
def set_yscale(self, scale: str) -> None:
|
|
844
|
+
"""Set the y-axis scale: ``'linear'`` or ``'log'``."""
|
|
845
|
+
if scale not in ("linear", "log"):
|
|
846
|
+
raise ValueError("scale must be 'linear' or 'log'")
|
|
847
|
+
self._state["yscale"] = scale
|
|
848
|
+
self._push()
|
|
849
|
+
|
|
850
|
+
def set_xlim(self, xmin: float, xmax: float) -> None:
|
|
851
|
+
self.set_view(x0=xmin, x1=xmax)
|
|
852
|
+
|
|
853
|
+
def set_ylim(self, ymin: float, ymax: float) -> None:
|
|
854
|
+
self._state["y_range"] = [float(ymin), float(ymax)]
|
|
855
|
+
self._push()
|
|
856
|
+
|
|
857
|
+
def get_ylim(self) -> tuple:
|
|
858
|
+
yr = self._state.get("y_range")
|
|
859
|
+
if yr is not None:
|
|
860
|
+
return (float(yr[0]), float(yr[1]))
|
|
861
|
+
return (float(self._state["data_min"]), float(self._state["data_max"]))
|
|
862
|
+
|
|
863
|
+
def get_xlim(self) -> tuple:
|
|
864
|
+
xarr = np.asarray(self._state["x_axis"])
|
|
865
|
+
if len(xarr) < 2:
|
|
866
|
+
return (0.0, 1.0)
|
|
867
|
+
xmin, xmax = float(xarr[0]), float(xarr[-1])
|
|
868
|
+
span = xmax - xmin or 1.0
|
|
869
|
+
x0 = xmin + self._state["view_x0"] * span
|
|
870
|
+
x1 = xmin + self._state["view_x1"] * span
|
|
871
|
+
return (x0, x1)
|
|
872
|
+
|
|
873
|
+
def get_xbound(self) -> tuple:
|
|
874
|
+
xarr = np.asarray(self._state["x_axis"])
|
|
875
|
+
return (float(xarr.min()), float(xarr.max()))
|
|
876
|
+
|
|
877
|
+
# ------------------------------------------------------------------
|
|
878
|
+
# Marker API (matplotlib-style kwargs → MarkerRegistry)
|
|
879
|
+
# ------------------------------------------------------------------
|
|
880
|
+
def add_circles(self, offsets, name=None, *, radius=5,
|
|
881
|
+
facecolors=None, edgecolors="#ff0000",
|
|
882
|
+
linewidths=1.5, alpha=0.3,
|
|
883
|
+
hover_edgecolors=None, hover_facecolors=None,
|
|
884
|
+
labels=None, label=None,
|
|
885
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
886
|
+
"""Add circle markers at explicit (x, y) positions.
|
|
887
|
+
|
|
888
|
+
On 1-D panels circles are rendered as filled/stroked discs; *radius*
|
|
889
|
+
is in canvas pixels (not data units).
|
|
890
|
+
|
|
891
|
+
Parameters
|
|
892
|
+
----------
|
|
893
|
+
offsets : array-like, shape (N, 2)
|
|
894
|
+
Marker positions as ``[[x0, y0], [x1, y1], …]`` in data
|
|
895
|
+
coordinates.
|
|
896
|
+
name : str, optional
|
|
897
|
+
Registry key. Auto-generated if omitted.
|
|
898
|
+
radius : float or array-like, optional
|
|
899
|
+
Radius in pixels. Scalar or per-marker array. Default ``5``.
|
|
900
|
+
facecolors : str or None, optional
|
|
901
|
+
Fill colour. ``None`` = no fill.
|
|
902
|
+
edgecolors : str, optional
|
|
903
|
+
Stroke colour. Default ``"#ff0000"``.
|
|
904
|
+
linewidths : float, optional
|
|
905
|
+
Stroke width in pixels. Default ``1.5``.
|
|
906
|
+
alpha : float, optional
|
|
907
|
+
Fill opacity (0–1). Default ``0.3``.
|
|
908
|
+
hover_edgecolors, hover_facecolors : str, optional
|
|
909
|
+
Colour overrides applied on mouse-hover.
|
|
910
|
+
labels : list of str, optional
|
|
911
|
+
Per-marker tooltip labels.
|
|
912
|
+
label : str, optional
|
|
913
|
+
Collection-level tooltip label.
|
|
914
|
+
|
|
915
|
+
Returns
|
|
916
|
+
-------
|
|
917
|
+
MarkerGroup
|
|
918
|
+
Live group object. Call ``.set(**kwargs)`` to update in place.
|
|
919
|
+
"""
|
|
920
|
+
# On 1-D panels the native type is "points" (radius maps to sizes).
|
|
921
|
+
return self._add_marker("points", name, offsets=offsets, sizes=radius,
|
|
922
|
+
facecolors=facecolors, edgecolors=edgecolors,
|
|
923
|
+
linewidths=linewidths, alpha=alpha,
|
|
924
|
+
hover_edgecolors=hover_edgecolors,
|
|
925
|
+
hover_facecolors=hover_facecolors,
|
|
926
|
+
labels=labels, label=label,
|
|
927
|
+
transform=transform)
|
|
928
|
+
|
|
929
|
+
def add_points(self, offsets, name=None, *, sizes=5,
|
|
930
|
+
color="#ff0000", facecolors=None,
|
|
931
|
+
linewidths=1.5, alpha=0.3,
|
|
932
|
+
hover_edgecolors=None, hover_facecolors=None,
|
|
933
|
+
labels=None, label=None,
|
|
934
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
935
|
+
"""Add point markers at (x, y) positions in data coordinates.
|
|
936
|
+
|
|
937
|
+
Parameters
|
|
938
|
+
----------
|
|
939
|
+
offsets : array-like, shape (N, 2)
|
|
940
|
+
Marker positions as ``[[x0, y0], [x1, y1], …]``.
|
|
941
|
+
name : str, optional
|
|
942
|
+
Registry key. Auto-generated if omitted.
|
|
943
|
+
sizes : float or array-like, optional
|
|
944
|
+
Radius in pixels. Scalar or per-marker array. Default ``5``.
|
|
945
|
+
color : str, optional
|
|
946
|
+
Stroke colour. Default ``"#ff0000"``.
|
|
947
|
+
facecolors : str or None, optional
|
|
948
|
+
Fill colour. ``None`` = no fill.
|
|
949
|
+
linewidths : float, optional
|
|
950
|
+
Stroke width in pixels. Default ``1.5``.
|
|
951
|
+
alpha : float, optional
|
|
952
|
+
Fill opacity (0–1). Default ``0.3``.
|
|
953
|
+
hover_edgecolors, hover_facecolors : str, optional
|
|
954
|
+
Colour overrides applied on mouse-hover.
|
|
955
|
+
labels : list of str, optional
|
|
956
|
+
Per-marker tooltip labels.
|
|
957
|
+
label : str, optional
|
|
958
|
+
Collection-level tooltip label.
|
|
959
|
+
|
|
960
|
+
Returns
|
|
961
|
+
-------
|
|
962
|
+
MarkerGroup
|
|
963
|
+
"""
|
|
964
|
+
return self._add_marker("points", name, offsets=offsets, sizes=sizes,
|
|
965
|
+
edgecolors=color, facecolors=facecolors,
|
|
966
|
+
linewidths=linewidths, alpha=alpha,
|
|
967
|
+
hover_edgecolors=hover_edgecolors,
|
|
968
|
+
hover_facecolors=hover_facecolors,
|
|
969
|
+
labels=labels, label=label,
|
|
970
|
+
transform=transform)
|
|
971
|
+
|
|
972
|
+
def add_hlines(self, y_values, name=None, *,
|
|
973
|
+
color="#ff0000", linewidths=1.5,
|
|
974
|
+
hover_edgecolors=None,
|
|
975
|
+
labels=None, label=None,
|
|
976
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
977
|
+
"""Add static horizontal lines spanning the full x range.
|
|
978
|
+
|
|
979
|
+
Parameters
|
|
980
|
+
----------
|
|
981
|
+
y_values : array-like, shape (N,)
|
|
982
|
+
Y positions of each line in data coordinates.
|
|
983
|
+
name : str, optional
|
|
984
|
+
Registry key. Auto-generated if omitted.
|
|
985
|
+
color : str, optional
|
|
986
|
+
Line colour. Default ``"#ff0000"``.
|
|
987
|
+
linewidths : float, optional
|
|
988
|
+
Stroke width in pixels. Default ``1.5``.
|
|
989
|
+
hover_edgecolors : str, optional
|
|
990
|
+
Colour override applied on mouse-hover.
|
|
991
|
+
labels : list of str, optional
|
|
992
|
+
Per-line tooltip labels.
|
|
993
|
+
label : str, optional
|
|
994
|
+
Collection-level tooltip label.
|
|
995
|
+
|
|
996
|
+
Returns
|
|
997
|
+
-------
|
|
998
|
+
MarkerGroup
|
|
999
|
+
"""
|
|
1000
|
+
return self._add_marker("hlines", name, offsets=y_values,
|
|
1001
|
+
color=color, linewidths=linewidths,
|
|
1002
|
+
hover_edgecolors=hover_edgecolors,
|
|
1003
|
+
labels=labels, label=label,
|
|
1004
|
+
transform=transform)
|
|
1005
|
+
|
|
1006
|
+
def add_vlines(self, x_values, name=None, *,
|
|
1007
|
+
color="#ff0000", linewidths=1.5,
|
|
1008
|
+
hover_edgecolors=None,
|
|
1009
|
+
labels=None, label=None,
|
|
1010
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1011
|
+
"""Add static vertical lines spanning the full y range.
|
|
1012
|
+
|
|
1013
|
+
Parameters
|
|
1014
|
+
----------
|
|
1015
|
+
x_values : array-like, shape (N,)
|
|
1016
|
+
X positions of each line in data coordinates.
|
|
1017
|
+
name : str, optional
|
|
1018
|
+
Registry key. Auto-generated if omitted.
|
|
1019
|
+
color : str, optional
|
|
1020
|
+
Line colour. Default ``"#ff0000"``.
|
|
1021
|
+
linewidths : float, optional
|
|
1022
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1023
|
+
hover_edgecolors : str, optional
|
|
1024
|
+
Colour override applied on mouse-hover.
|
|
1025
|
+
labels : list of str, optional
|
|
1026
|
+
Per-line tooltip labels.
|
|
1027
|
+
label : str, optional
|
|
1028
|
+
Collection-level tooltip label.
|
|
1029
|
+
|
|
1030
|
+
Returns
|
|
1031
|
+
-------
|
|
1032
|
+
MarkerGroup
|
|
1033
|
+
"""
|
|
1034
|
+
return self._add_marker("vlines", name, offsets=x_values,
|
|
1035
|
+
color=color, linewidths=linewidths,
|
|
1036
|
+
hover_edgecolors=hover_edgecolors,
|
|
1037
|
+
labels=labels, label=label,
|
|
1038
|
+
transform=transform)
|
|
1039
|
+
|
|
1040
|
+
def add_arrows(self, offsets, U, V, name=None, *,
|
|
1041
|
+
edgecolors="#ff0000", linewidths=1.5,
|
|
1042
|
+
hover_edgecolors=None,
|
|
1043
|
+
labels=None, label=None,
|
|
1044
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1045
|
+
"""Add arrow markers at explicit (x, y) positions.
|
|
1046
|
+
|
|
1047
|
+
Parameters
|
|
1048
|
+
----------
|
|
1049
|
+
offsets : array-like, shape (N, 2)
|
|
1050
|
+
Arrow tail positions as ``[[x0, y0], …]`` in data coordinates.
|
|
1051
|
+
U, V : array-like, shape (N,)
|
|
1052
|
+
X and Y components of each arrow vector (in data units).
|
|
1053
|
+
name : str, optional
|
|
1054
|
+
Registry key. Auto-generated if omitted.
|
|
1055
|
+
edgecolors : str, optional
|
|
1056
|
+
Arrow colour. Default ``"#ff0000"``.
|
|
1057
|
+
linewidths : float, optional
|
|
1058
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1059
|
+
hover_edgecolors : str, optional
|
|
1060
|
+
Colour override applied on mouse-hover.
|
|
1061
|
+
labels : list of str, optional
|
|
1062
|
+
Per-arrow tooltip labels.
|
|
1063
|
+
label : str, optional
|
|
1064
|
+
Collection-level tooltip label.
|
|
1065
|
+
|
|
1066
|
+
Returns
|
|
1067
|
+
-------
|
|
1068
|
+
MarkerGroup
|
|
1069
|
+
"""
|
|
1070
|
+
return self._add_marker("arrows", name, offsets=offsets, U=U, V=V,
|
|
1071
|
+
edgecolors=edgecolors, linewidths=linewidths,
|
|
1072
|
+
hover_edgecolors=hover_edgecolors,
|
|
1073
|
+
labels=labels, label=label,
|
|
1074
|
+
transform=transform)
|
|
1075
|
+
|
|
1076
|
+
def add_ellipses(self, offsets, widths, heights, name=None, *,
|
|
1077
|
+
angles=0, facecolors=None, edgecolors="#ff0000",
|
|
1078
|
+
linewidths=1.5, alpha=0.3,
|
|
1079
|
+
hover_edgecolors=None, hover_facecolors=None,
|
|
1080
|
+
labels=None, label=None,
|
|
1081
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1082
|
+
"""Add ellipse markers at explicit (x, y) positions.
|
|
1083
|
+
|
|
1084
|
+
Parameters
|
|
1085
|
+
----------
|
|
1086
|
+
offsets : array-like, shape (N, 2)
|
|
1087
|
+
Centre positions in data coordinates.
|
|
1088
|
+
widths, heights : float or array-like
|
|
1089
|
+
Full width and height of each ellipse in canvas pixels.
|
|
1090
|
+
name : str, optional
|
|
1091
|
+
Registry key. Auto-generated if omitted.
|
|
1092
|
+
angles : float or array-like, optional
|
|
1093
|
+
Rotation angle(s) in degrees. Default ``0``.
|
|
1094
|
+
facecolors : str or None, optional
|
|
1095
|
+
Fill colour. ``None`` = no fill.
|
|
1096
|
+
edgecolors : str, optional
|
|
1097
|
+
Stroke colour. Default ``"#ff0000"``.
|
|
1098
|
+
linewidths : float, optional
|
|
1099
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1100
|
+
alpha : float, optional
|
|
1101
|
+
Fill opacity (0–1). Default ``0.3``.
|
|
1102
|
+
hover_edgecolors, hover_facecolors : str, optional
|
|
1103
|
+
Colour overrides applied on mouse-hover.
|
|
1104
|
+
labels : list of str, optional
|
|
1105
|
+
Per-marker tooltip labels.
|
|
1106
|
+
label : str, optional
|
|
1107
|
+
Collection-level tooltip label.
|
|
1108
|
+
|
|
1109
|
+
Returns
|
|
1110
|
+
-------
|
|
1111
|
+
MarkerGroup
|
|
1112
|
+
"""
|
|
1113
|
+
return self._add_marker("ellipses", name, offsets=offsets,
|
|
1114
|
+
widths=widths, heights=heights, angles=angles,
|
|
1115
|
+
facecolors=facecolors, edgecolors=edgecolors,
|
|
1116
|
+
linewidths=linewidths, alpha=alpha,
|
|
1117
|
+
hover_edgecolors=hover_edgecolors,
|
|
1118
|
+
hover_facecolors=hover_facecolors,
|
|
1119
|
+
labels=labels, label=label,
|
|
1120
|
+
transform=transform)
|
|
1121
|
+
|
|
1122
|
+
def add_lines(self, segments, name=None, *,
|
|
1123
|
+
edgecolors="#ff0000", linewidths=1.5,
|
|
1124
|
+
hover_edgecolors=None,
|
|
1125
|
+
labels=None, label=None,
|
|
1126
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1127
|
+
"""Add line-segment markers (static, not draggable).
|
|
1128
|
+
|
|
1129
|
+
Parameters
|
|
1130
|
+
----------
|
|
1131
|
+
segments : array-like, shape (N, 2, 2)
|
|
1132
|
+
Each segment is ``[[x0, y0], [x1, y1]]`` in data coordinates.
|
|
1133
|
+
name : str, optional
|
|
1134
|
+
Registry key. Auto-generated if omitted.
|
|
1135
|
+
edgecolors : str, optional
|
|
1136
|
+
Line colour. Default ``"#ff0000"``.
|
|
1137
|
+
linewidths : float, optional
|
|
1138
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1139
|
+
hover_edgecolors : str, optional
|
|
1140
|
+
Colour override applied on mouse-hover.
|
|
1141
|
+
labels : list of str, optional
|
|
1142
|
+
Per-segment tooltip labels.
|
|
1143
|
+
label : str, optional
|
|
1144
|
+
Collection-level tooltip label.
|
|
1145
|
+
|
|
1146
|
+
Returns
|
|
1147
|
+
-------
|
|
1148
|
+
MarkerGroup
|
|
1149
|
+
"""
|
|
1150
|
+
return self._add_marker("lines", name, segments=segments,
|
|
1151
|
+
edgecolors=edgecolors, linewidths=linewidths,
|
|
1152
|
+
hover_edgecolors=hover_edgecolors,
|
|
1153
|
+
labels=labels, label=label,
|
|
1154
|
+
transform=transform)
|
|
1155
|
+
|
|
1156
|
+
def add_rectangles(self, offsets, widths, heights, name=None, *,
|
|
1157
|
+
angles=0, facecolors=None, edgecolors="#ff0000",
|
|
1158
|
+
linewidths=1.5, alpha=0.3,
|
|
1159
|
+
hover_edgecolors=None, hover_facecolors=None,
|
|
1160
|
+
labels=None, label=None,
|
|
1161
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1162
|
+
"""Add rectangle markers at explicit (x, y) positions.
|
|
1163
|
+
|
|
1164
|
+
Parameters
|
|
1165
|
+
----------
|
|
1166
|
+
offsets : array-like, shape (N, 2)
|
|
1167
|
+
Centre positions in data coordinates.
|
|
1168
|
+
widths, heights : float or array-like
|
|
1169
|
+
Full width and height of each rectangle in canvas pixels.
|
|
1170
|
+
name : str, optional
|
|
1171
|
+
Registry key. Auto-generated if omitted.
|
|
1172
|
+
angles : float or array-like, optional
|
|
1173
|
+
Rotation angle(s) in degrees. Default ``0``.
|
|
1174
|
+
facecolors : str or None, optional
|
|
1175
|
+
Fill colour. ``None`` = no fill.
|
|
1176
|
+
edgecolors : str, optional
|
|
1177
|
+
Stroke colour. Default ``"#ff0000"``.
|
|
1178
|
+
linewidths : float, optional
|
|
1179
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1180
|
+
alpha : float, optional
|
|
1181
|
+
Fill opacity (0–1). Default ``0.3``.
|
|
1182
|
+
hover_edgecolors, hover_facecolors : str, optional
|
|
1183
|
+
Colour overrides applied on mouse-hover.
|
|
1184
|
+
labels : list of str, optional
|
|
1185
|
+
Per-marker tooltip labels.
|
|
1186
|
+
label : str, optional
|
|
1187
|
+
Collection-level tooltip label.
|
|
1188
|
+
|
|
1189
|
+
Returns
|
|
1190
|
+
-------
|
|
1191
|
+
MarkerGroup
|
|
1192
|
+
"""
|
|
1193
|
+
return self._add_marker("rectangles", name, offsets=offsets,
|
|
1194
|
+
widths=widths, heights=heights, angles=angles,
|
|
1195
|
+
facecolors=facecolors, edgecolors=edgecolors,
|
|
1196
|
+
linewidths=linewidths, alpha=alpha,
|
|
1197
|
+
hover_edgecolors=hover_edgecolors,
|
|
1198
|
+
hover_facecolors=hover_facecolors,
|
|
1199
|
+
labels=labels, label=label,
|
|
1200
|
+
transform=transform)
|
|
1201
|
+
|
|
1202
|
+
def add_squares(self, offsets, widths, name=None, *,
|
|
1203
|
+
angles=0, facecolors=None, edgecolors="#ff0000",
|
|
1204
|
+
linewidths=1.5, alpha=0.3,
|
|
1205
|
+
hover_edgecolors=None, hover_facecolors=None,
|
|
1206
|
+
labels=None, label=None,
|
|
1207
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1208
|
+
"""Add square markers at explicit (x, y) positions.
|
|
1209
|
+
|
|
1210
|
+
Parameters
|
|
1211
|
+
----------
|
|
1212
|
+
offsets : array-like, shape (N, 2)
|
|
1213
|
+
Centre positions in data coordinates.
|
|
1214
|
+
widths : float or array-like
|
|
1215
|
+
Side length of each square in canvas pixels.
|
|
1216
|
+
name : str, optional
|
|
1217
|
+
Registry key. Auto-generated if omitted.
|
|
1218
|
+
angles : float or array-like, optional
|
|
1219
|
+
Rotation angle(s) in degrees. Default ``0``.
|
|
1220
|
+
facecolors : str or None, optional
|
|
1221
|
+
Fill colour. ``None`` = no fill.
|
|
1222
|
+
edgecolors : str, optional
|
|
1223
|
+
Stroke colour. Default ``"#ff0000"``.
|
|
1224
|
+
linewidths : float, optional
|
|
1225
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1226
|
+
alpha : float, optional
|
|
1227
|
+
Fill opacity (0–1). Default ``0.3``.
|
|
1228
|
+
hover_edgecolors, hover_facecolors : str, optional
|
|
1229
|
+
Colour overrides applied on mouse-hover.
|
|
1230
|
+
labels : list of str, optional
|
|
1231
|
+
Per-marker tooltip labels.
|
|
1232
|
+
label : str, optional
|
|
1233
|
+
Collection-level tooltip label.
|
|
1234
|
+
|
|
1235
|
+
Returns
|
|
1236
|
+
-------
|
|
1237
|
+
MarkerGroup
|
|
1238
|
+
"""
|
|
1239
|
+
return self._add_marker("squares", name, offsets=offsets,
|
|
1240
|
+
widths=widths, angles=angles,
|
|
1241
|
+
facecolors=facecolors, edgecolors=edgecolors,
|
|
1242
|
+
linewidths=linewidths, alpha=alpha,
|
|
1243
|
+
hover_edgecolors=hover_edgecolors,
|
|
1244
|
+
hover_facecolors=hover_facecolors,
|
|
1245
|
+
labels=labels, label=label,
|
|
1246
|
+
transform=transform)
|
|
1247
|
+
|
|
1248
|
+
def add_polygons(self, vertices_list, name=None, *,
|
|
1249
|
+
facecolors=None, edgecolors="#ff0000",
|
|
1250
|
+
linewidths=1.5, alpha=0.3,
|
|
1251
|
+
hover_edgecolors=None, hover_facecolors=None,
|
|
1252
|
+
labels=None, label=None, clip_path=None,
|
|
1253
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1254
|
+
"""Add polygon markers defined by explicit vertex lists.
|
|
1255
|
+
|
|
1256
|
+
Parameters
|
|
1257
|
+
----------
|
|
1258
|
+
vertices_list : list of array-like, each shape (K, 2)
|
|
1259
|
+
One polygon per element; each is a list of ``[x, y]`` vertices
|
|
1260
|
+
in data coordinates.
|
|
1261
|
+
name : str, optional
|
|
1262
|
+
Registry key. Auto-generated if omitted.
|
|
1263
|
+
facecolors : str or None, optional
|
|
1264
|
+
Fill colour. ``None`` = no fill.
|
|
1265
|
+
edgecolors : str, optional
|
|
1266
|
+
Stroke colour. Default ``"#ff0000"``.
|
|
1267
|
+
linewidths : float, optional
|
|
1268
|
+
Stroke width in pixels. Default ``1.5``.
|
|
1269
|
+
alpha : float, optional
|
|
1270
|
+
Fill opacity (0–1). Default ``0.3``.
|
|
1271
|
+
hover_edgecolors, hover_facecolors : str, optional
|
|
1272
|
+
Colour overrides applied on mouse-hover.
|
|
1273
|
+
labels : list of str, optional
|
|
1274
|
+
Per-polygon tooltip labels.
|
|
1275
|
+
label : str, optional
|
|
1276
|
+
Collection-level tooltip label.
|
|
1277
|
+
clip_path : array-like, shape (K, 2), optional
|
|
1278
|
+
Data-coordinate polygon the group is clipped to (matplotlib
|
|
1279
|
+
``set_clip_path``) — e.g. a curved sector boundary so a mesh's edge
|
|
1280
|
+
cells don't overflow it. ``None`` = no clip.
|
|
1281
|
+
|
|
1282
|
+
Returns
|
|
1283
|
+
-------
|
|
1284
|
+
MarkerGroup
|
|
1285
|
+
"""
|
|
1286
|
+
return self._add_marker("polygons", name, vertices_list=vertices_list,
|
|
1287
|
+
facecolors=facecolors, edgecolors=edgecolors,
|
|
1288
|
+
linewidths=linewidths, alpha=alpha,
|
|
1289
|
+
hover_edgecolors=hover_edgecolors,
|
|
1290
|
+
hover_facecolors=hover_facecolors,
|
|
1291
|
+
labels=labels, label=label, clip_path=clip_path,
|
|
1292
|
+
transform=transform)
|
|
1293
|
+
|
|
1294
|
+
def add_raster(self, rgba, *, extent, name=None, clip_path=None,
|
|
1295
|
+
smooth: bool = False,
|
|
1296
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1297
|
+
"""Add an RGBA image drawn between data-coordinate ``extent`` corners.
|
|
1298
|
+
|
|
1299
|
+
A single-``drawImage`` raster — the fast path for a dense regular grid
|
|
1300
|
+
(e.g. an orientation-density heatmap) that would otherwise be thousands
|
|
1301
|
+
of individual polygons. The image bytes travel on the deduped geometry
|
|
1302
|
+
channel, so re-aiming the view never re-transmits them.
|
|
1303
|
+
|
|
1304
|
+
Parameters
|
|
1305
|
+
----------
|
|
1306
|
+
rgba : array-like, shape (H, W, 3|4)
|
|
1307
|
+
Image data. Coerced to uint8 RGBA via the usual rules (floats in
|
|
1308
|
+
0–1 scaled ×255, missing alpha → 255). Alpha 0 = transparent cell.
|
|
1309
|
+
extent : (x0, x1, y0, y1)
|
|
1310
|
+
Data-coordinate bounding box the image is stretched across.
|
|
1311
|
+
name : str, optional
|
|
1312
|
+
Registry key. Auto-generated if omitted.
|
|
1313
|
+
clip_path : array-like, shape (K, 2), optional
|
|
1314
|
+
Data-coordinate polygon the image is clipped to (matplotlib
|
|
1315
|
+
``set_clip_path``) — e.g. a curved fundamental-sector boundary.
|
|
1316
|
+
``None`` = no clip.
|
|
1317
|
+
smooth : bool, optional
|
|
1318
|
+
If ``True``, the image is bilinearly interpolated when stretched
|
|
1319
|
+
(a smooth heat field). Default ``False`` keeps crisp
|
|
1320
|
+
nearest-neighbour cells (matplotlib ``interpolation="nearest"``).
|
|
1321
|
+
|
|
1322
|
+
Returns
|
|
1323
|
+
-------
|
|
1324
|
+
MarkerGroup
|
|
1325
|
+
"""
|
|
1326
|
+
import base64
|
|
1327
|
+
rgba_u8 = _to_rgba_u8(np.asarray(rgba))
|
|
1328
|
+
h, w = rgba_u8.shape[:2]
|
|
1329
|
+
image_b64 = base64.b64encode(
|
|
1330
|
+
np.ascontiguousarray(rgba_u8).tobytes()).decode("ascii")
|
|
1331
|
+
return self._add_marker("raster", name, image_b64=image_b64,
|
|
1332
|
+
image_width=int(w), image_height=int(h),
|
|
1333
|
+
extent=tuple(float(v) for v in extent),
|
|
1334
|
+
clip_path=clip_path, smooth=bool(smooth),
|
|
1335
|
+
transform=transform)
|
|
1336
|
+
|
|
1337
|
+
def add_texts(self, offsets, texts, name=None, *,
|
|
1338
|
+
color="#ff0000", fontsize=12,
|
|
1339
|
+
hover_edgecolors=None,
|
|
1340
|
+
labels=None, label=None,
|
|
1341
|
+
transform: str = "data") -> "MarkerGroup": # noqa: F821
|
|
1342
|
+
"""Add text annotations at explicit (x, y) positions.
|
|
1343
|
+
|
|
1344
|
+
Parameters
|
|
1345
|
+
----------
|
|
1346
|
+
offsets : array-like, shape (N, 2)
|
|
1347
|
+
Anchor positions in data coordinates.
|
|
1348
|
+
texts : list of str
|
|
1349
|
+
One string per position.
|
|
1350
|
+
name : str, optional
|
|
1351
|
+
Registry key. Auto-generated if omitted.
|
|
1352
|
+
color : str, optional
|
|
1353
|
+
Text colour. Default ``"#ff0000"``.
|
|
1354
|
+
fontsize : int, optional
|
|
1355
|
+
Font size in pixels. Default ``12``.
|
|
1356
|
+
hover_edgecolors : str, optional
|
|
1357
|
+
Colour override applied on mouse-hover.
|
|
1358
|
+
labels : list of str, optional
|
|
1359
|
+
Per-annotation tooltip labels.
|
|
1360
|
+
label : str, optional
|
|
1361
|
+
Collection-level tooltip label.
|
|
1362
|
+
|
|
1363
|
+
Returns
|
|
1364
|
+
-------
|
|
1365
|
+
MarkerGroup
|
|
1366
|
+
"""
|
|
1367
|
+
return self._add_marker("texts", name, offsets=offsets, texts=texts,
|
|
1368
|
+
color=color, fontsize=fontsize,
|
|
1369
|
+
hover_edgecolors=hover_edgecolors,
|
|
1370
|
+
labels=labels, label=label,
|
|
1371
|
+
transform=transform)
|
|
1372
|
+
|
|
1373
|
+
def __repr__(self) -> str:
|
|
1374
|
+
n = len(self._state.get("data", []))
|
|
1375
|
+
color = self._state.get("line_color", "?")
|
|
1376
|
+
return f"Plot1D(n={n}, color={color!r})"
|