Skip to content

Commit 8b1d326

Browse files
authored
Merge pull request #45 from ksunden/caching_patches
Consolidate caching logic, implement patch subclasses
2 parents 7021e10 + 5581ada commit 8b1d326

File tree

4 files changed

+284
-53
lines changed

4 files changed

+284
-53
lines changed

data_prototype/artist.py

+28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from bisect import insort
2+
from collections import OrderedDict
23
from typing import Sequence
34
from contextlib import contextmanager
45

@@ -33,6 +34,8 @@ def __init__(
3334
**{"x": np.asarray([0, 1]), "y": np.asarray([0, 1])}
3435
)
3536

37+
self._caches = {}
38+
3639
def draw(self, renderer, graph: Graph) -> None:
3740
return
3841

@@ -119,6 +122,31 @@ def pick(self, mouseevent, graph: Graph | None = None):
119122
# which do not have an axes property but children might
120123
a.pick(mouseevent, graph)
121124

125+
def _get_dynamic_graph(self, query, description, graph, cacheset):
126+
return Graph([])
127+
128+
def _query_and_eval(self, container, requires, graph, cacheset=None):
129+
g = graph + self._graph
130+
query, q_cache_key = container.query(g)
131+
g = g + self._get_dynamic_graph(query, container.describe(), graph, cacheset)
132+
g_cache_key = g.cache_key()
133+
cache_key = (g_cache_key, q_cache_key)
134+
135+
cache = None
136+
if cacheset is not None:
137+
cache = self._caches.setdefault(cacheset, OrderedDict())
138+
if cache_key in cache:
139+
return cache[cache_key]
140+
141+
conv = g.evaluator(container.describe(), requires)
142+
ret = conv.evaluate(query)
143+
144+
# TODO: actually add to cache and prune
145+
# if cache is not None:
146+
# cache[cache_key] = ret
147+
148+
return ret
149+
122150

123151
class CompatibilityArtist:
124152
"""A compatibility shim to ducktype as a classic Matplotlib Artist.

data_prototype/conversion_edge.py

+9
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,12 @@ def __add__(self, other: Graph) -> Graph:
418418
aother = {k: v for k, v in other._aliases}
419419
aliases = tuple((aself | aother).items())
420420
return Graph(self._edges + other._edges, aliases)
421+
422+
def cache_key(self):
423+
"""A cache key representing the graph.
424+
425+
Current implementation is a new UUID, that is to say uncachable.
426+
"""
427+
import uuid
428+
429+
return str(uuid.uuid4())

data_prototype/patches.py

+207-23
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,28 @@
77

88
from .wrappers import ProxyWrapper, _stale_wrapper
99

10-
from .containers import DataContainer
11-
1210
from .artist import Artist, _renderer_group
13-
from .description import Desc
14-
from .conversion_edge import Graph, CoordinateEdge, DefaultEdge
11+
from .description import Desc, desc_like
12+
from .containers import DataContainer
13+
from .conversion_edge import Graph, CoordinateEdge, DefaultEdge, TransformEdge
1514

1615

1716
class Patch(Artist):
1817
def __init__(self, container, edges=None, **kwargs):
1918
super().__init__(container, edges, **kwargs)
2019

2120
scalar = Desc((), "display") # ... this needs thinking...
22-
edges = [
21+
def_edges = [
2322
CoordinateEdge.from_coords("xycoords", {"x": "auto", "y": "auto"}, "data"),
2423
CoordinateEdge.from_coords("codes", {"codes": "auto"}, "display"),
25-
CoordinateEdge.from_coords("facecolor", {"color": Desc(())}, "display"),
26-
CoordinateEdge.from_coords("edgecolor", {"color": Desc(())}, "display"),
24+
CoordinateEdge.from_coords("facecolor", {"facecolor": Desc(())}, "display"),
25+
CoordinateEdge.from_coords("edgecolor", {"edgecolor": Desc(())}, "display"),
26+
CoordinateEdge.from_coords(
27+
"facecolor_rgba", {"facecolor": Desc(("M",))}, "display"
28+
),
29+
CoordinateEdge.from_coords(
30+
"edgecolor_rgba", {"edgecolor": Desc(("M",))}, "display"
31+
),
2732
CoordinateEdge.from_coords("linewidth", {"linewidth": Desc(())}, "display"),
2833
CoordinateEdge.from_coords("hatch", {"hatch": Desc(())}, "display"),
2934
CoordinateEdge.from_coords("alpha", {"alpha": Desc(())}, "display"),
@@ -34,39 +39,34 @@ def __init__(self, container, edges=None, **kwargs):
3439
DefaultEdge.from_default_value("alpha_def", "alpha", scalar, 1),
3540
DefaultEdge.from_default_value("hatch_def", "hatch", scalar, None),
3641
]
37-
self._graph = self._graph + Graph(edges)
42+
self._graph = self._graph + Graph(def_edges)
3843

3944
def draw(self, renderer, graph: Graph) -> None:
4045
if not self.get_visible():
4146
return
42-
g = graph + self._graph
4347
desc = Desc(("N",), "display")
4448
scalar = Desc((), "display") # ... this needs thinking...
4549

4650
require = {
4751
"x": desc,
4852
"y": desc,
4953
"codes": desc,
50-
"facecolor": scalar,
51-
"edgecolor": scalar,
54+
"facecolor": Desc((), "display"),
55+
"edgecolor": Desc(("M",), "display"),
5256
"linewidth": scalar,
5357
"linestyle": scalar,
5458
"hatch": scalar,
5559
"alpha": scalar,
5660
}
5761

58-
# copy from line
59-
conv = g.evaluator(self._container.describe(), require)
60-
query, _ = self._container.query(g)
61-
evald = conv.evaluate(query)
62-
63-
clip_conv = g.evaluator(
64-
self._clip_box.describe(),
65-
{"x": Desc(("N",), "display"), "y": Desc(("N",), "display")},
62+
evald = self._query_and_eval(
63+
self._container, require, graph, cacheset="default"
6664
)
67-
clip_query, _ = self._clip_box.query(g)
68-
clipx, clipy = clip_conv.evaluate(clip_query).values()
69-
# copy from line
65+
66+
clip_req = {"x": Desc(("N",), "display"), "y": Desc(("N",), "display")}
67+
clipx, clipy = self._query_and_eval(
68+
self._clip_box, clip_req, graph, cacheset="clip"
69+
).values()
7070

7171
path = mpath.Path._fast_from_codes_and_verts(
7272
verts=np.vstack([evald["x"], evald["y"]]).T, codes=evald["codes"]
@@ -75,7 +75,7 @@ def draw(self, renderer, graph: Graph) -> None:
7575
with _renderer_group(renderer, "patch", None):
7676
gc = renderer.new_gc()
7777

78-
gc.set_foreground(evald["facecolor"], isRGBA=False)
78+
gc.set_foreground(evald["edgecolor"], isRGBA=False)
7979
gc.set_clip_rectangle(
8080
mtransforms.Bbox.from_extents(clipx[0], clipy[0], clipx[1], clipy[1])
8181
)
@@ -111,6 +111,190 @@ def draw(self, renderer, graph: Graph) -> None:
111111
gc.restore()
112112

113113

114+
class RectangleContainer(DataContainer): ...
115+
116+
117+
class Rectangle(Patch):
118+
def __init__(self, container, edges=None, **kwargs):
119+
super().__init__(container, edges, **kwargs)
120+
121+
rect = mpath.Path.unit_rectangle()
122+
123+
desc = Desc((4,), "abstract_path")
124+
scalar = Desc((), "data")
125+
scalar_auto = Desc(())
126+
def_edges = [
127+
CoordinateEdge.from_coords(
128+
"llxycoords",
129+
{"lower_left_x": scalar_auto, "lower_left_y": scalar_auto},
130+
"data",
131+
),
132+
CoordinateEdge.from_coords(
133+
"urxycoords",
134+
{"upper_right_x": scalar_auto, "upper_right_y": scalar_auto},
135+
"data",
136+
),
137+
CoordinateEdge.from_coords(
138+
"rpxycoords",
139+
{"rotation_point_x": scalar_auto, "rotation_point_y": scalar_auto},
140+
"data",
141+
),
142+
CoordinateEdge.from_coords("anglecoords", {"angle": scalar_auto}, "data"),
143+
DefaultEdge.from_default_value(
144+
"x_def", "x", desc, rect.vertices.T[0], weight=0.1
145+
),
146+
DefaultEdge.from_default_value(
147+
"y_def", "y", desc, rect.vertices.T[1], weight=0.1
148+
),
149+
DefaultEdge.from_default_value(
150+
"codes_def",
151+
"codes",
152+
desc_like(desc, coordinates="display"),
153+
rect.codes,
154+
weight=0.1,
155+
),
156+
DefaultEdge.from_default_value("angle_def", "angle", scalar, 0),
157+
DefaultEdge.from_default_value(
158+
"rotation_point_x_def", "rotation_point_x", scalar, 0
159+
),
160+
DefaultEdge.from_default_value(
161+
"rotation_point_y_def", "rotation_point_y", scalar, 0
162+
),
163+
]
164+
165+
self._graph = self._graph + Graph(def_edges)
166+
167+
def _get_dynamic_graph(self, query, description, graph, cacheset):
168+
if cacheset == "clip":
169+
return Graph([])
170+
171+
desc = Desc((), "data")
172+
173+
requires = {
174+
"upper_right_x": desc,
175+
"upper_right_y": desc,
176+
"lower_left_x": desc,
177+
"lower_left_y": desc,
178+
"angle": desc,
179+
"rotation_point_x": desc,
180+
"rotation_point_y": desc,
181+
}
182+
183+
g = graph + self._graph
184+
185+
conv = g.evaluator(description, requires)
186+
evald = conv.evaluate(query)
187+
188+
bbox = mtransforms.Bbox.from_extents(
189+
evald["lower_left_x"],
190+
evald["lower_left_y"],
191+
evald["upper_right_x"],
192+
evald["upper_right_y"],
193+
)
194+
rotation_point = (evald["rotation_point_x"], evald["rotation_point_y"])
195+
196+
scale = mtransforms.BboxTransformTo(bbox)
197+
rotate = (
198+
mtransforms.Affine2D()
199+
.translate(-rotation_point[0], -rotation_point[1])
200+
.rotate_deg(evald["angle"])
201+
.translate(*rotation_point)
202+
)
203+
204+
descn: Desc = Desc(("N",), coordinates="data")
205+
xy: dict[str, Desc] = {"x": descn, "y": descn}
206+
edges = [
207+
TransformEdge(
208+
"scale_and_rotate",
209+
desc_like(xy, coordinates="abstract_path"),
210+
xy,
211+
transform=scale + rotate,
212+
)
213+
]
214+
215+
return Graph(edges)
216+
217+
218+
class RegularPolygon(Patch):
219+
def __init__(self, container, edges=None, **kwargs):
220+
super().__init__(container, edges, **kwargs)
221+
222+
scalar = Desc((), "data")
223+
scalar_auto = Desc(())
224+
def_edges = [
225+
CoordinateEdge.from_coords(
226+
"centercoords",
227+
{"center_x": scalar_auto, "center_y": scalar_auto},
228+
"data",
229+
),
230+
CoordinateEdge.from_coords(
231+
"orientationcoords", {"orientation": scalar_auto}, "data"
232+
),
233+
CoordinateEdge.from_coords("radiuscoords", {"radius": scalar_auto}, "data"),
234+
CoordinateEdge.from_coords(
235+
"num_vertices_coords", {"num_vertices": scalar_auto}, "data"
236+
),
237+
DefaultEdge.from_default_value("orientation_def", "orientation", scalar, 0),
238+
DefaultEdge.from_default_value("radius_def", "radius", scalar, 5),
239+
]
240+
241+
self._graph = self._graph + Graph(def_edges)
242+
243+
def _get_dynamic_graph(self, query, description, graph, cacheset):
244+
if cacheset == "clip":
245+
return Graph([])
246+
247+
desc = Desc((), "data")
248+
desc_abs = Desc(("N",), "abstract_path")
249+
250+
requires = {
251+
"center_x": desc,
252+
"center_y": desc,
253+
"radius": desc,
254+
"orientation": desc,
255+
"num_vertices": desc,
256+
}
257+
258+
g = graph + self._graph
259+
260+
conv = g.evaluator(description, requires)
261+
evald = conv.evaluate(query)
262+
263+
circ = mpath.Path.unit_regular_polygon(evald["num_vertices"])
264+
265+
scale = mtransforms.Affine2D().scale(evald["radius"])
266+
rotate = mtransforms.Affine2D().rotate(evald["orientation"])
267+
translate = mtransforms.Affine2D().translate(
268+
evald["center_x"], evald["center_y"]
269+
)
270+
271+
descn: Desc = Desc(("N",), coordinates="data")
272+
xy: dict[str, Desc] = {"x": descn, "y": descn}
273+
edges = [
274+
TransformEdge(
275+
"scale_and_rotate",
276+
desc_like(xy, coordinates="abstract_path"),
277+
xy,
278+
transform=scale + rotate + translate,
279+
),
280+
DefaultEdge.from_default_value(
281+
"x_def", "x", desc_abs, circ.vertices.T[0], weight=0.1
282+
),
283+
DefaultEdge.from_default_value(
284+
"y_def", "y", desc_abs, circ.vertices.T[1], weight=0.1
285+
),
286+
DefaultEdge.from_default_value(
287+
"codes_def",
288+
"codes",
289+
desc_like(desc_abs, coordinates="display"),
290+
circ.codes,
291+
weight=0.1,
292+
),
293+
]
294+
295+
return Graph(edges)
296+
297+
114298
class PatchWrapper(ProxyWrapper):
115299
_wrapped_class = _Patch
116300
_privtized_methods = (

0 commit comments

Comments
 (0)