Skip to content

Commit 2cd647f

Browse files
authored
Merge pull request #9 from ksunden/scatter
Early PathCollection support
2 parents 339bc85 + 799d188 commit 2cd647f

File tree

3 files changed

+179
-2
lines changed

3 files changed

+179
-2
lines changed

data_prototype/wrappers.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from matplotlib.image import AxesImage as _AxesImage
1212
from matplotlib.patches import StepPatch as _StepPatch
1313
from matplotlib.text import Text as _Text
14-
from matplotlib.collections import LineCollection as _LineCollection
14+
import matplotlib.transforms as mtransforms
15+
from matplotlib.collections import LineCollection as _LineCollection, PathCollection as _PathCollection
1516
from matplotlib.artist import Artist as _Artist
1617

1718
from data_prototype.containers import DataContainer, _MatplotlibTransform
@@ -206,7 +207,7 @@ class LineWrapper(ProxyWrapper):
206207

207208
def __init__(self, data: DataContainer, nus=None, /, **kwargs):
208209
super().__init__(data, nus)
209-
self._wrapped_instance = self._wrapped_class([], [], **kwargs)
210+
self._wrapped_instance = self._wrapped_class(np.array([]), np.array([]), **kwargs)
210211

211212
@_stale_wrapper
212213
def draw(self, renderer):
@@ -221,6 +222,42 @@ def _update_wrapped(self, data):
221222
getattr(self._wrapped_instance, f"set_{k}")(v)
222223

223224

225+
class PathCollectionWrapper(ProxyWrapper):
226+
_wrapped_class = _PathCollection
227+
required_keys = {"x", "y", "paths", "facecolors", "edgecolors", "sizes"}
228+
_privtized_methods = (
229+
"set_facecolors",
230+
"set_edgecolors",
231+
"set_offsets",
232+
"set_sizes",
233+
"set_paths",
234+
"get_facecolors",
235+
"get_edgecolors",
236+
"get_offsets",
237+
"get_sizes",
238+
"get_paths",
239+
)
240+
241+
def __init__(self, data: DataContainer, nus=None, /, **kwargs):
242+
super().__init__(data, nus)
243+
self._wrapped_instance = self._wrapped_class([], **kwargs)
244+
self._wrapped_instance.set_transform(mtransforms.IdentityTransform())
245+
246+
@_stale_wrapper
247+
def draw(self, renderer):
248+
self._update_wrapped(
249+
self._query_and_transform(renderer, xunits=["x"], yunits=["y"]),
250+
)
251+
return self._wrapped_instance.draw(renderer)
252+
253+
def _update_wrapped(self, data):
254+
self._wrapped_instance.set_offsets(np.array([data["x"], data["y"]]).T)
255+
self._wrapped_instance.set_paths(data["paths"])
256+
self._wrapped_instance.set_facecolors(data["facecolors"])
257+
self._wrapped_instance.set_edgecolors(data["edgecolors"])
258+
self._wrapped_instance.set_sizes(data["sizes"])
259+
260+
224261
class ImageWrapper(ProxyWrapper):
225262
_wrapped_class = _AxesImage
226263
required_keys = {"xextent", "yextent", "image"}

examples/lissajous.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
==========================
3+
An animated lissajous ball
4+
==========================
5+
6+
Inspired by https://twitter.com/_brohrer_/status/1584681864648065027
7+
8+
9+
"""
10+
import time
11+
from typing import Dict, Tuple, Any, Union
12+
from functools import partial
13+
14+
import numpy as np
15+
16+
import matplotlib.pyplot as plt
17+
import matplotlib.markers as mmarkers
18+
from matplotlib.animation import FuncAnimation
19+
20+
from data_prototype.containers import _MatplotlibTransform, Desc
21+
22+
from data_prototype.wrappers import PathCollectionWrapper, FormatedText
23+
24+
25+
class Lissajous:
26+
N = 1024
27+
# cycles per minutes
28+
scale = 2
29+
30+
def describe(self):
31+
return {
32+
"x": Desc([self.N], float),
33+
"y": Desc([self.N], float),
34+
"phase": Desc([], float),
35+
"time": Desc([], float),
36+
"sizes": Desc([], float),
37+
"paths": Desc([], float),
38+
"edgecolors": Desc([], str),
39+
"facecolors": Desc([self.N], str),
40+
}
41+
42+
def query(
43+
self,
44+
transform: _MatplotlibTransform,
45+
size: Tuple[int, int],
46+
) -> Tuple[Dict[str, Any], Union[str, int]]:
47+
def next_time():
48+
cur_time = time.time()
49+
cur_time = np.array([cur_time, cur_time - 0.1, cur_time - 0.2, cur_time - 0.3])
50+
51+
phase = 15 * np.pi * (self.scale * cur_time % 60) / 150
52+
marker_obj = mmarkers.MarkerStyle("o")
53+
return {
54+
"x": np.cos(5 * phase),
55+
"y": np.sin(3 * phase),
56+
"phase": phase[0],
57+
"sizes": np.array([256]),
58+
"paths": [marker_obj.get_path().transformed(marker_obj.get_transform())],
59+
"edgecolors": "k",
60+
"facecolors": ["#4682b4ff", "#82b446aa", "#46b48288", "#8246b433"],
61+
"time": cur_time[0],
62+
}, hash(cur_time[0])
63+
64+
return next_time()
65+
66+
67+
def update(frame, art):
68+
return art
69+
70+
71+
sot_c = Lissajous()
72+
73+
fc = FormatedText(
74+
sot_c,
75+
{"text": lambda phase: f"ϕ={phase:.2f} "},
76+
x=1,
77+
y=1,
78+
ha="right",
79+
)
80+
fig, ax = plt.subplots()
81+
ax.set_xlim(-1.1, 1.1)
82+
ax.set_ylim(-1.1, 1.1)
83+
lw = PathCollectionWrapper(sot_c, offset_transform=ax.transData)
84+
ax.add_artist(lw)
85+
ax.add_artist(fc)
86+
# ax.set_xticks([])
87+
# ax.set_yticks([])
88+
ax.set_aspect(1)
89+
ani = FuncAnimation(
90+
fig,
91+
partial(update, art=(lw, fc)),
92+
frames=60 * 15,
93+
interval=1000 / 100,
94+
# TODO: blitting does not work because wrappers do not inherent from Artist
95+
# blit=True,
96+
)
97+
plt.show()

examples/simple_scatter.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
==========================
3+
An animated lissajous ball
4+
==========================
5+
6+
Inspired by https://twitter.com/_brohrer_/status/1584681864648065027
7+
8+
9+
"""
10+
import numpy as np
11+
12+
import matplotlib.pyplot as plt
13+
import matplotlib.markers as mmarkers
14+
15+
from data_prototype.containers import ArrayContainer
16+
17+
from data_prototype.wrappers import PathCollectionWrapper
18+
19+
20+
def update(frame, art):
21+
return art
22+
23+
24+
marker_obj = mmarkers.MarkerStyle("o")
25+
26+
cont = ArrayContainer(
27+
x=np.array([0, 1, 2]),
28+
y=np.array([1, 4, 2]),
29+
paths=np.array([marker_obj.get_path()]),
30+
sizes=np.array([12]),
31+
edgecolors=np.array(["k"]),
32+
facecolors=np.array(["C3"]),
33+
)
34+
35+
fig, ax = plt.subplots()
36+
ax.set_xlim(-0.5, 2.5)
37+
ax.set_ylim(0, 5)
38+
lw = PathCollectionWrapper(cont, offset_transform=ax.transData)
39+
ax.add_artist(lw)
40+
# ax.set_xticks([])
41+
# ax.set_yticks([])
42+
# ax.set_aspect(1)
43+
plt.show()

0 commit comments

Comments
 (0)