Skip to content

Commit 5581ada

Browse files
committed
Lay groundwork for caching of patches
Currently does not do a lot of actual caching, but the hooks are present to be filled out when focusing on efficiency
1 parent c0321da commit 5581ada

File tree

3 files changed

+231
-39
lines changed

3 files changed

+231
-39
lines changed

data_prototype/artist.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,13 @@ def pick(self, mouseevent, graph: Graph | None = None):
122122
# which do not have an axes property but children might
123123
a.pick(mouseevent, graph)
124124

125-
def _get_dynamic_graph(self, query, description, graph):
125+
def _get_dynamic_graph(self, query, description, graph, cacheset):
126126
return Graph([])
127127

128128
def _query_and_eval(self, container, requires, graph, cacheset=None):
129129
g = graph + self._graph
130130
query, q_cache_key = container.query(g)
131-
g = g + self._get_dynamic_graph(query, container.describe(), graph)
131+
g = g + self._get_dynamic_graph(query, container.describe(), graph, cacheset)
132132
g_cache_key = g.cache_key()
133133
cache_key = (g_cache_key, q_cache_key)
134134

data_prototype/patches.py

+189-7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
from .wrappers import ProxyWrapper, _stale_wrapper
99

1010
from .artist import Artist, _renderer_group
11-
from .description import Desc
11+
from .description import Desc, desc_like
1212
from .containers import DataContainer
13-
from .conversion_edge import Graph, CoordinateEdge, DefaultEdge
13+
from .conversion_edge import Graph, CoordinateEdge, DefaultEdge, TransformEdge
1414

1515

1616
class Patch(Artist):
@@ -21,8 +21,14 @@ def __init__(self, container, edges=None, **kwargs):
2121
def_edges = [
2222
CoordinateEdge.from_coords("xycoords", {"x": "auto", "y": "auto"}, "data"),
2323
CoordinateEdge.from_coords("codes", {"codes": "auto"}, "display"),
24-
CoordinateEdge.from_coords("facecolor", {"color": Desc(())}, "display"),
25-
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+
),
2632
CoordinateEdge.from_coords("linewidth", {"linewidth": Desc(())}, "display"),
2733
CoordinateEdge.from_coords("hatch", {"hatch": Desc(())}, "display"),
2834
CoordinateEdge.from_coords("alpha", {"alpha": Desc(())}, "display"),
@@ -45,8 +51,8 @@ def draw(self, renderer, graph: Graph) -> None:
4551
"x": desc,
4652
"y": desc,
4753
"codes": desc,
48-
"facecolor": scalar,
49-
"edgecolor": scalar,
54+
"facecolor": Desc((), "display"),
55+
"edgecolor": Desc(("M",), "display"),
5056
"linewidth": scalar,
5157
"linestyle": scalar,
5258
"hatch": scalar,
@@ -69,7 +75,7 @@ def draw(self, renderer, graph: Graph) -> None:
6975
with _renderer_group(renderer, "patch", None):
7076
gc = renderer.new_gc()
7177

72-
gc.set_foreground(evald["facecolor"], isRGBA=False)
78+
gc.set_foreground(evald["edgecolor"], isRGBA=False)
7379
gc.set_clip_rectangle(
7480
mtransforms.Bbox.from_extents(clipx[0], clipy[0], clipx[1], clipy[1])
7581
)
@@ -112,6 +118,182 @@ class Rectangle(Patch):
112118
def __init__(self, container, edges=None, **kwargs):
113119
super().__init__(container, edges, **kwargs)
114120

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+
115297

116298
class PatchWrapper(ProxyWrapper):
117299
_wrapped_class = _Patch

examples/simple_patch.py

+40-30
Original file line numberDiff line numberDiff line change
@@ -12,53 +12,63 @@
1212
import numpy as np
1313

1414
import matplotlib.pyplot as plt
15+
import matplotlib.patches as mpatches
1516

1617
from data_prototype.containers import ArrayContainer
18+
from data_prototype.artist import CompatibilityAxes
1719

18-
from data_prototype.patches import RectangleWrapper
20+
from data_prototype.patches import Rectangle
1921

2022
cont1 = ArrayContainer(
21-
x=np.array([-3]),
22-
y=np.array([0]),
23-
width=np.array([2]),
24-
height=np.array([3]),
25-
angle=np.array([0]),
26-
rotation_point=np.array(["center"]),
23+
lower_left_x=np.array(-3),
24+
lower_left_y=np.array(0),
25+
upper_right_x=np.array(-1),
26+
upper_right_y=np.array(3),
2727
edgecolor=np.array([0, 0, 0]),
28-
facecolor=np.array([0.0, 0.7, 0, 0.5]),
29-
linewidth=np.array([3]),
30-
linestyle=np.array(["-"]),
28+
hatch_color=np.array([0, 0, 0]),
29+
facecolor="green",
30+
linewidth=3,
31+
linestyle="-",
3132
antialiased=np.array([True]),
32-
hatch=np.array(["*"]),
33+
hatch="*",
3334
fill=np.array([True]),
3435
capstyle=np.array(["round"]),
3536
joinstyle=np.array(["miter"]),
37+
alpha=np.array(0.5),
3638
)
3739

3840
cont2 = ArrayContainer(
39-
x=np.array([0]),
40-
y=np.array([1]),
41-
width=np.array([2]),
42-
height=np.array([3]),
43-
angle=np.array([30]),
44-
rotation_point=np.array(["center"]),
45-
edgecolor=np.array([0, 0, 0]),
46-
facecolor=np.array([0.7, 0, 0]),
47-
linewidth=np.array([6]),
48-
linestyle=np.array(["-"]),
41+
lower_left_x=0,
42+
lower_left_y=np.array(1),
43+
upper_right_x=np.array(2),
44+
upper_right_y=np.array(5),
45+
angle=30,
46+
rotation_point_x=np.array(1),
47+
rotation_point_y=np.array(3.5),
48+
edgecolor=np.array([0.5, 0.2, 0]),
49+
hatch_color=np.array([0, 0, 0]),
50+
facecolor="red",
51+
linewidth=6,
52+
linestyle="-",
4953
antialiased=np.array([True]),
50-
hatch=np.array([""]),
54+
hatch="",
5155
fill=np.array([True]),
52-
capstyle=np.array(["butt"]),
53-
joinstyle=np.array(["round"]),
56+
capstyle=np.array(["round"]),
57+
joinstyle=np.array(["miter"]),
5458
)
5559

56-
fig, ax = plt.subplots()
57-
ax.set_xlim(-5, 5)
58-
ax.set_ylim(0, 5)
59-
rect1 = RectangleWrapper(cont1, {})
60-
rect2 = RectangleWrapper(cont2, {})
60+
fig, nax = plt.subplots()
61+
ax = CompatibilityAxes(nax)
62+
nax.add_artist(ax)
63+
nax.set_xlim(-5, 5)
64+
nax.set_ylim(0, 5)
65+
66+
rect = mpatches.Rectangle((4, 1), 2, 3, linewidth=6, edgecolor="black", angle=30)
67+
nax.add_artist(rect)
68+
69+
rect1 = Rectangle(cont1, {})
70+
rect2 = Rectangle(cont2, {})
6171
ax.add_artist(rect1)
6272
ax.add_artist(rect2)
63-
ax.set_aspect(1)
73+
nax.set_aspect(1)
6474
plt.show()

0 commit comments

Comments
 (0)