plotlive 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.
plotlive/artists.py ADDED
@@ -0,0 +1,207 @@
1
+ from __future__ import annotations
2
+ import numpy as np
3
+ from .colors import to_rgba, get_cmap, DEFAULT_CYCLE
4
+
5
+
6
+ class Artist:
7
+ def __init__(self):
8
+ self.label: str = '_nolegend_'
9
+ self.visible: bool = True
10
+ self.alpha: float = 1.0
11
+ self.zorder: float = 1.0
12
+
13
+ def get_xdata(self) -> np.ndarray | None:
14
+ return None
15
+
16
+ def get_ydata(self) -> np.ndarray | None:
17
+ return None
18
+
19
+ def get_label(self) -> str:
20
+ return self.label
21
+
22
+
23
+ class Line2D(Artist):
24
+ def __init__(self, xdata: np.ndarray, ydata: np.ndarray, **kwargs):
25
+ super().__init__()
26
+ self.xdata = np.asarray(xdata, dtype=float)
27
+ self.ydata = np.asarray(ydata, dtype=float)
28
+ color = kwargs.get('color', kwargs.get('c', 'C0'))
29
+ self.color: tuple = to_rgba(color)
30
+ self.linewidth: float = float(kwargs.get('linewidth', kwargs.get('lw', 1.5)))
31
+ self.linestyle: str = kwargs.get('linestyle', kwargs.get('ls', '-'))
32
+ self.marker: str | None = kwargs.get('marker', None)
33
+ self.markersize: float = float(kwargs.get('markersize', kwargs.get('ms', 6.0)))
34
+ self.markerfacecolor = kwargs.get('markerfacecolor', None)
35
+ self.label = kwargs.get('label', '_nolegend_')
36
+ self.alpha = float(kwargs.get('alpha', 1.0))
37
+
38
+ def get_xdata(self) -> np.ndarray:
39
+ return self.xdata
40
+
41
+ def get_ydata(self) -> np.ndarray:
42
+ return self.ydata
43
+
44
+ def set_xdata(self, x) -> None:
45
+ self.xdata = np.asarray(x, dtype=float)
46
+
47
+ def set_ydata(self, y) -> None:
48
+ self.ydata = np.asarray(y, dtype=float)
49
+
50
+
51
+ class PathCollection(Artist):
52
+ """Stores data for a scatter plot."""
53
+
54
+ def __init__(self, xdata: np.ndarray, ydata: np.ndarray, **kwargs):
55
+ super().__init__()
56
+ self.xdata = np.asarray(xdata, dtype=float).ravel()
57
+ self.ydata = np.asarray(ydata, dtype=float).ravel()
58
+ s = kwargs.get('s', 20.0)
59
+ self.sizes = np.broadcast_to(
60
+ np.atleast_1d(np.asarray(s, dtype=float)), self.xdata.shape
61
+ ).copy()
62
+ self._c_raw = kwargs.get('c', kwargs.get('color', 'C0'))
63
+ self.cmap_name: str = kwargs.get('cmap', 'viridis')
64
+ self.vmin: float | None = kwargs.get('vmin', None)
65
+ self.vmax: float | None = kwargs.get('vmax', None)
66
+ self.marker: str = kwargs.get('marker', 'o')
67
+ self.linewidths: float = float(kwargs.get('linewidths', 0.0))
68
+ self.edgecolors = kwargs.get('edgecolors', 'none')
69
+ self.label = kwargs.get('label', '_nolegend_')
70
+ self.alpha = float(kwargs.get('alpha', 1.0))
71
+ self._colors_resolved: np.ndarray | None = None
72
+
73
+ def get_xdata(self) -> np.ndarray:
74
+ return self.xdata
75
+
76
+ def get_ydata(self) -> np.ndarray:
77
+ return self.ydata
78
+
79
+ def resolve_colors(self) -> np.ndarray:
80
+ """Return (N, 4) uint8 RGBA array."""
81
+ if self._colors_resolved is not None:
82
+ return self._colors_resolved
83
+
84
+ c = self._c_raw
85
+ n = len(self.xdata)
86
+ result = np.zeros((n, 4), dtype=np.uint8)
87
+
88
+ # Try to interpret c as an array of floats (for cmap mapping)
89
+ try:
90
+ c_arr = np.asarray(c, dtype=float)
91
+ if c_arr.ndim == 1 and c_arr.shape[0] == n and n > 1:
92
+ # It's a per-point scalar for colormap
93
+ vmin = self.vmin if self.vmin is not None else float(c_arr.min())
94
+ vmax = self.vmax if self.vmax is not None else float(c_arr.max())
95
+ cmap = get_cmap(self.cmap_name)
96
+ span = vmax - vmin if vmax != vmin else 1.0
97
+ t = (c_arr - vmin) / span
98
+ rgba = cmap(t)
99
+ result[:] = rgba[:, :4]
100
+ self._colors_resolved = result
101
+ return result
102
+ except (TypeError, ValueError):
103
+ pass
104
+
105
+ # Single color broadcast
106
+ try:
107
+ rgba = to_rgba(c, self.alpha)
108
+ except Exception:
109
+ rgba = to_rgba(DEFAULT_CYCLE[0], self.alpha)
110
+ result[:] = rgba
111
+ self._colors_resolved = result
112
+ return result
113
+
114
+
115
+ class Rectangle(Artist):
116
+ def __init__(self, x: float, y: float, width: float, height: float, **kwargs):
117
+ super().__init__()
118
+ self.x = float(x)
119
+ self.y = float(y)
120
+ self.width = float(width)
121
+ self.height = float(height)
122
+ color = kwargs.get('color', kwargs.get('facecolor', 'C0'))
123
+ self.facecolor: tuple = to_rgba(color, float(kwargs.get('alpha', 1.0)))
124
+ ec = kwargs.get('edgecolor', kwargs.get('ec', 'black'))
125
+ self.edgecolor: tuple = to_rgba(ec) if ec not in ('none', 'None', None) else (0, 0, 0, 0)
126
+ self.linewidth: float = float(kwargs.get('linewidth', kwargs.get('lw', 1.0)))
127
+ self.label = kwargs.get('label', '_nolegend_')
128
+ self.alpha = float(kwargs.get('alpha', 1.0))
129
+ self._horizontal: bool = kwargs.get('_horizontal', False)
130
+
131
+
132
+ class BarContainer:
133
+ def __init__(self, patches: list[Rectangle], label: str = ''):
134
+ self.patches = patches
135
+ self.label = label
136
+
137
+ def __iter__(self):
138
+ return iter(self.patches)
139
+
140
+ def __len__(self):
141
+ return len(self.patches)
142
+
143
+ def __getitem__(self, idx):
144
+ return self.patches[idx]
145
+
146
+
147
+ class Polygon(Artist):
148
+ """Filled polygon — used by fill_between, violinplot, pie, stackplot."""
149
+
150
+ def __init__(self, xy: np.ndarray, **kwargs):
151
+ super().__init__()
152
+ self.xy = np.asarray(xy, dtype=float) # (N, 2): columns [x, y]
153
+ color = kwargs.get('color', kwargs.get('facecolor', 'C0'))
154
+ alpha = float(kwargs.get('alpha', 0.5))
155
+ self.facecolor: tuple = to_rgba(color, alpha)
156
+ ec = kwargs.get('edgecolor', kwargs.get('ec', 'none'))
157
+ self.edgecolor: tuple = (to_rgba(ec)
158
+ if ec not in ('none', 'None', None)
159
+ else (0, 0, 0, 0))
160
+ self.linewidth: float = float(kwargs.get('linewidth', kwargs.get('lw', 1.0)))
161
+ self.label = kwargs.get('label', '_nolegend_')
162
+ self.alpha = alpha
163
+
164
+
165
+ class ErrorBar(Artist):
166
+ """Central line + whiskers — used by errorbar()."""
167
+
168
+ def __init__(self, x, y, yerr=None, xerr=None, **kwargs):
169
+ super().__init__()
170
+ self.xdata = np.asarray(x, dtype=float).ravel()
171
+ self.ydata = np.asarray(y, dtype=float).ravel()
172
+ n = len(self.xdata)
173
+
174
+ def _norm(err):
175
+ if err is None:
176
+ return None
177
+ e = np.asarray(err, float)
178
+ if e.ndim <= 1:
179
+ e = np.stack([e * np.ones(n), e * np.ones(n)])
180
+ return np.broadcast_to(e, (2, n)).copy()
181
+
182
+ self.yerr = _norm(yerr)
183
+ self.xerr = _norm(xerr)
184
+ color = kwargs.get('color', kwargs.get('c', 'C0'))
185
+ self.color: tuple = to_rgba(color)
186
+ self.linewidth: float = float(kwargs.get('linewidth', kwargs.get('lw', 1.5)))
187
+ self.capsize: float = float(kwargs.get('capsize', 4))
188
+ self.marker: str = kwargs.get('marker', 'o')
189
+ self.markersize: float = float(kwargs.get('markersize', kwargs.get('ms', 5.0)))
190
+ self.linestyle: str = kwargs.get('linestyle', kwargs.get('ls', '-'))
191
+ self.label = kwargs.get('label', '_nolegend_')
192
+ self.alpha: float = float(kwargs.get('alpha', 1.0))
193
+
194
+
195
+ class AxesImage(Artist):
196
+ def __init__(self, data: np.ndarray, **kwargs):
197
+ super().__init__()
198
+ self.data = np.asarray(data)
199
+ self.cmap_name: str = kwargs.get('cmap', 'viridis')
200
+ self.vmin: float | None = kwargs.get('vmin', None)
201
+ self.vmax: float | None = kwargs.get('vmax', None)
202
+ self.origin: str = kwargs.get('origin', 'upper')
203
+ self.aspect: str = kwargs.get('aspect', 'equal')
204
+ self.extent: tuple | None = kwargs.get('extent', None)
205
+ self.label = kwargs.get('label', '_nolegend_')
206
+ self._surface_cache = None
207
+ self._cache_key = None