Skip to content

Commit a33566d

Browse files
authored
Merge pull request #40 from ksunden/artists
Introduce artist classes, starting with Line
2 parents a38c182 + e5b5455 commit a33566d

File tree

14 files changed

+889
-168
lines changed

14 files changed

+889
-168
lines changed

data_prototype/artist.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
from bisect import insort
2+
from typing import Sequence
3+
4+
import numpy as np
5+
6+
from .containers import DataContainer, ArrayContainer, DataUnion
7+
from .description import Desc, desc_like
8+
from .conversion_edge import Edge, Graph, TransformEdge
9+
10+
11+
class Artist:
12+
required_keys: dict[str, Desc]
13+
14+
# defaults?
15+
def __init__(
16+
self, container: DataContainer, edges: Sequence[Edge] | None = None, **kwargs
17+
):
18+
kwargs_cont = ArrayContainer(**kwargs)
19+
self._container = DataUnion(container, kwargs_cont)
20+
21+
edges = edges or []
22+
self._visible = True
23+
self._graph = Graph(edges)
24+
self._clip_box: DataContainer = ArrayContainer(
25+
{"x": "parent", "y": "parent"},
26+
**{"x": np.asarray([0, 1]), "y": np.asarray([0, 1])}
27+
)
28+
29+
def draw(self, renderer, graph: Graph) -> None:
30+
return
31+
32+
def set_clip_box(self, container: DataContainer) -> None:
33+
self._clip_box = container
34+
35+
def get_clip_box(self, container: DataContainer) -> DataContainer:
36+
return self._clip_box
37+
38+
def get_visible(self):
39+
return self._visible
40+
41+
def set_visible(self, visible):
42+
self._visible = visible
43+
44+
45+
class CompatibilityArtist:
46+
"""A compatibility shim to ducktype as a classic Matplotlib Artist.
47+
48+
At this time features are implemented on an "as needed" basis, and many
49+
are only implemented insofar as they do not fail, not necessarily providing
50+
full functionality of a full MPL Artist.
51+
52+
The idea is to keep the new Artist class as minimal as possible.
53+
As features are added this may shrink.
54+
55+
The main thing we are trying to avoid is the reliance on the axes/figure
56+
57+
Ultimately for useability, whatever remains shimmed out here may be rolled in as
58+
some form of gaurded option to ``Artist`` itself, but a firm dividing line is
59+
useful for avoiding accidental dependency.
60+
"""
61+
62+
def __init__(self, artist: Artist):
63+
self._artist = artist
64+
65+
self._axes = None
66+
self.figure = None
67+
self._clippath = None
68+
self._visible = True
69+
self.zorder = 2
70+
self._graph = Graph([])
71+
72+
@property
73+
def axes(self):
74+
return self._axes
75+
76+
@axes.setter
77+
def axes(self, ax):
78+
self._axes = ax
79+
80+
if self._axes is None:
81+
self._graph = Graph([])
82+
return
83+
84+
desc: Desc = Desc(("N",), coordinates="data")
85+
xy: dict[str, Desc] = {"x": desc, "y": desc}
86+
self._graph = Graph(
87+
[
88+
TransformEdge(
89+
"data",
90+
xy,
91+
desc_like(xy, coordinates="axes"),
92+
transform=self._axes.transData - self._axes.transAxes,
93+
),
94+
TransformEdge(
95+
"axes",
96+
desc_like(xy, coordinates="axes"),
97+
desc_like(xy, coordinates="display"),
98+
transform=self._axes.transAxes,
99+
),
100+
],
101+
aliases=(("parent", "axes"),),
102+
)
103+
104+
def set_figure(self, fig):
105+
self.figure = fig
106+
107+
def is_transform_set(self):
108+
return True
109+
110+
def get_mouseover(self):
111+
return False
112+
113+
def get_clip_path(self):
114+
self._clippath
115+
116+
def set_clip_path(self, path):
117+
self._clippath = path
118+
119+
def get_animated(self):
120+
return False
121+
122+
def get_visible(self):
123+
return self._visible
124+
125+
def set_visible(self, visible):
126+
self._visible = visible
127+
128+
def draw(self, renderer, graph=None):
129+
if not self.get_visible():
130+
return
131+
132+
if graph is None:
133+
graph = Graph([])
134+
self._artist.draw(renderer, graph + self._graph)
135+
136+
137+
class CompatibilityAxes:
138+
"""A compatibility shim to add to traditional matplotlib axes.
139+
140+
At this time features are implemented on an "as needed" basis, and many
141+
are only implemented insofar as they do not fail, not necessarily providing
142+
full functionality of a full MPL Artist.
143+
144+
The idea is to keep the new Artist class as minimal as possible.
145+
As features are added this may shrink.
146+
147+
The main thing we are trying to avoid is the reliance on the axes/figure
148+
149+
Ultimately for useability, whatever remains shimmed out here may be rolled in as
150+
some form of gaurded option to ``Artist`` itself, but a firm dividing line is
151+
useful for avoiding accidental dependency.
152+
"""
153+
154+
def __init__(self, axes):
155+
self._axes = axes
156+
self.figure = None
157+
self._clippath = None
158+
self._visible = True
159+
self.zorder = 2
160+
self._children: list[tuple[float, Artist]] = []
161+
162+
@property
163+
def axes(self):
164+
return self._axes
165+
166+
@axes.setter
167+
def axes(self, ax):
168+
self._axes = ax
169+
170+
if self._axes is None:
171+
self._graph = Graph([])
172+
return
173+
174+
desc: Desc = Desc(("N",), coordinates="data")
175+
xy: dict[str, Desc] = {"x": desc, "y": desc}
176+
self._graph = Graph(
177+
[
178+
TransformEdge(
179+
"data",
180+
xy,
181+
desc_like(xy, coordinates="axes"),
182+
transform=self._axes.transData - self._axes.transAxes,
183+
),
184+
TransformEdge(
185+
"axes",
186+
desc_like(xy, coordinates="axes"),
187+
desc_like(xy, coordinates="display"),
188+
transform=self._axes.transAxes,
189+
),
190+
],
191+
aliases=(("parent", "axes"),),
192+
)
193+
194+
def set_figure(self, fig):
195+
self.figure = fig
196+
197+
def is_transform_set(self):
198+
return True
199+
200+
def get_mouseover(self):
201+
return False
202+
203+
def get_clip_path(self):
204+
self._clippath
205+
206+
def set_clip_path(self, path):
207+
self._clippath = path
208+
209+
def get_animated(self):
210+
return False
211+
212+
def draw(self, renderer, graph=None):
213+
if not self.visible:
214+
return
215+
if graph is None:
216+
graph = Graph([])
217+
218+
graph = graph + self._graph
219+
220+
for _, c in self._children:
221+
c.draw(renderer, graph)
222+
223+
def add_artist(self, artist, zorder=1):
224+
insort(self._children, (zorder, artist), key=lambda x: x[0])
225+
226+
def set_xlim(self, min_=None, max_=None):
227+
self.axes.set_xlim(min_, max_)
228+
229+
def set_ylim(self, min_=None, max_=None):
230+
self.axes.set_ylim(min_, max_)
231+
232+
def get_visible(self):
233+
return self._visible
234+
235+
def set_visible(self, visible):
236+
self._visible = visible

data_prototype/containers.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,15 @@ class NoNewKeys(ValueError): ...
8282

8383

8484
class ArrayContainer:
85-
def __init__(self, **data):
85+
def __init__(self, coordinates: dict[str, str] | None = None, /, **data):
86+
coordinates = coordinates or {}
8687
self._data = data
8788
self._cache_key = str(uuid.uuid4())
8889
self._desc = {
8990
k: (
90-
Desc(v.shape, v.dtype)
91+
Desc(v.shape, coordinates.get(k, "auto"))
9192
if isinstance(v, np.ndarray)
92-
else Desc((), type(v))
93+
else Desc(())
9394
)
9495
for k, v in data.items()
9596
}
@@ -117,7 +118,7 @@ def update(self, **data):
117118

118119
class RandomContainer:
119120
def __init__(self, **shapes):
120-
self._desc = {k: Desc(s, np.dtype(float)) for k, s in shapes.items()}
121+
self._desc = {k: Desc(s) for k, s in shapes.items()}
121122

122123
def query(
123124
self,
@@ -171,7 +172,7 @@ def __init__(
171172
def _split(input_dict):
172173
out = {}
173174
for k, (shape, func) in input_dict.items():
174-
self._desc[k] = Desc(shape, np.dtype(float))
175+
self._desc[k] = Desc(shape)
175176
out[k] = func
176177
return out
177178

@@ -196,7 +197,7 @@ def query(
196197
# if hash_key in self._cache:
197198
# return self._cache[hash_key], hash_key
198199

199-
desc = Desc(("N",), np.dtype("f8"))
200+
desc = Desc(("N",))
200201
xy = {"x": desc, "y": desc}
201202
data_lim = graph.evaluator(
202203
desc_like(xy, coordinates="data"),
@@ -243,8 +244,8 @@ def __init__(self, raw_data, num_bins: int):
243244
self._raw_data = raw_data
244245
self._num_bins = num_bins
245246
self._desc = {
246-
"edges": Desc((num_bins + 1 + 2,), np.dtype(float)),
247-
"density": Desc((num_bins + 2,), np.dtype(float)),
247+
"edges": Desc((num_bins + 1 + 2,)),
248+
"density": Desc((num_bins + 2,)),
248249
}
249250
self._full_range = (raw_data.min(), raw_data.max())
250251
self._cache: MutableMapping[Union[str, int], Any] = LFUCache(64)
@@ -256,7 +257,7 @@ def query(
256257
) -> Tuple[Dict[str, Any], Union[str, int]]:
257258
dmin, dmax = self._full_range
258259

259-
desc = Desc(("N",), np.dtype("f8"))
260+
desc = Desc(("N",))
260261
xy = {"x": desc, "y": desc}
261262
data_lim = graph.evaluator(
262263
desc_like(xy, coordinates="data"),
@@ -302,8 +303,8 @@ def __init__(self, series: pd.Series, *, index_name: str, col_name: str):
302303
self._index_name = index_name
303304
self._col_name = col_name
304305
self._desc = {
305-
index_name: Desc((len(series),), series.index.dtype),
306-
col_name: Desc((len(series),), series.dtype),
306+
index_name: Desc((len(series),)),
307+
col_name: Desc((len(series),)),
307308
}
308309
self._hash_key = str(uuid.uuid4())
309310

@@ -343,9 +344,9 @@ def __init__(
343344

344345
self._desc: Dict[str, Desc] = {}
345346
if self._index_name is not None:
346-
self._desc[self._index_name] = Desc((len(df),), df.index.dtype)
347+
self._desc[self._index_name] = Desc((len(df),))
347348
for col, out in self._col_name_dict.items():
348-
self._desc[out] = Desc((len(df),), df[col].dtype)
349+
self._desc[out] = Desc((len(df),))
349350

350351
self._hash_key = str(uuid.uuid4())
351352

0 commit comments

Comments
 (0)