Skip to content

Commit 7021e10

Browse files
authored
Merge pull request #43 from tacaswell/enh_patch
ENH: add a basic Patch artist
2 parents f68736b + f7d140d commit 7021e10

File tree

4 files changed

+152
-1
lines changed

4 files changed

+152
-1
lines changed

data_prototype/artist.py

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from bisect import insort
22
from typing import Sequence
3+
from contextlib import contextmanager
34

45
import numpy as np
56

@@ -316,3 +317,12 @@ def set_xlim(self, min_=None, max_=None):
316317

317318
def set_ylim(self, min_=None, max_=None):
318319
self.axes.set_ylim(min_, max_)
320+
321+
322+
@contextmanager
323+
def _renderer_group(renderer, group, gid):
324+
renderer.open_group(group, gid)
325+
try:
326+
yield
327+
finally:
328+
renderer.close_group(group)

data_prototype/patches.py

+106-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,114 @@
1+
from matplotlib.patches import Patch as _Patch, Rectangle as _Rectangle
2+
import matplotlib.path as mpath
3+
import matplotlib.transforms as mtransforms
4+
import matplotlib.colors as mcolors
15
import numpy as np
26

7+
38
from .wrappers import ProxyWrapper, _stale_wrapper
9+
410
from .containers import DataContainer
511

6-
from matplotlib.patches import Patch as _Patch, Rectangle as _Rectangle
12+
from .artist import Artist, _renderer_group
13+
from .description import Desc
14+
from .conversion_edge import Graph, CoordinateEdge, DefaultEdge
15+
16+
17+
class Patch(Artist):
18+
def __init__(self, container, edges=None, **kwargs):
19+
super().__init__(container, edges, **kwargs)
20+
21+
scalar = Desc((), "display") # ... this needs thinking...
22+
edges = [
23+
CoordinateEdge.from_coords("xycoords", {"x": "auto", "y": "auto"}, "data"),
24+
CoordinateEdge.from_coords("codes", {"codes": "auto"}, "display"),
25+
CoordinateEdge.from_coords("facecolor", {"color": Desc(())}, "display"),
26+
CoordinateEdge.from_coords("edgecolor", {"color": Desc(())}, "display"),
27+
CoordinateEdge.from_coords("linewidth", {"linewidth": Desc(())}, "display"),
28+
CoordinateEdge.from_coords("hatch", {"hatch": Desc(())}, "display"),
29+
CoordinateEdge.from_coords("alpha", {"alpha": Desc(())}, "display"),
30+
DefaultEdge.from_default_value("facecolor_def", "facecolor", scalar, "C0"),
31+
DefaultEdge.from_default_value("edgecolor_def", "edgecolor", scalar, "C0"),
32+
DefaultEdge.from_default_value("linewidth_def", "linewidth", scalar, 1),
33+
DefaultEdge.from_default_value("linestyle_def", "linestyle", scalar, "-"),
34+
DefaultEdge.from_default_value("alpha_def", "alpha", scalar, 1),
35+
DefaultEdge.from_default_value("hatch_def", "hatch", scalar, None),
36+
]
37+
self._graph = self._graph + Graph(edges)
38+
39+
def draw(self, renderer, graph: Graph) -> None:
40+
if not self.get_visible():
41+
return
42+
g = graph + self._graph
43+
desc = Desc(("N",), "display")
44+
scalar = Desc((), "display") # ... this needs thinking...
45+
46+
require = {
47+
"x": desc,
48+
"y": desc,
49+
"codes": desc,
50+
"facecolor": scalar,
51+
"edgecolor": scalar,
52+
"linewidth": scalar,
53+
"linestyle": scalar,
54+
"hatch": scalar,
55+
"alpha": scalar,
56+
}
57+
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")},
66+
)
67+
clip_query, _ = self._clip_box.query(g)
68+
clipx, clipy = clip_conv.evaluate(clip_query).values()
69+
# copy from line
70+
71+
path = mpath.Path._fast_from_codes_and_verts(
72+
verts=np.vstack([evald["x"], evald["y"]]).T, codes=evald["codes"]
73+
)
74+
75+
with _renderer_group(renderer, "patch", None):
76+
gc = renderer.new_gc()
77+
78+
gc.set_foreground(evald["facecolor"], isRGBA=False)
79+
gc.set_clip_rectangle(
80+
mtransforms.Bbox.from_extents(clipx[0], clipy[0], clipx[1], clipy[1])
81+
)
82+
gc.set_linewidth(evald["linewidth"])
83+
# gc.set_dashes(*self._dash_pattern)
84+
# gc.set_capstyle(self._capstyle)
85+
# gc.set_joinstyle(self._joinstyle)
86+
87+
# gc.set_antialiased(self._antialiased)
88+
89+
# gc.set_url(self._url)
90+
# gc.set_snap(self.get_snap())
91+
92+
gc.set_alpha(evald["alpha"])
93+
94+
if evald["hatch"] is not None:
95+
gc.set_hatch(evald["hatch"])
96+
gc.set_hatch_color(evald["hatch_color"])
97+
98+
# if self.get_sketch_params() is not None:
99+
# gc.set_sketch_params(*self.get_sketch_params())
100+
101+
# if self.get_path_effects():
102+
# from matplotlib.patheffects import PathEffectRenderer
103+
# renderer = PathEffectRenderer(self.get_path_effects(), renderer)
104+
105+
renderer.draw_path(
106+
gc,
107+
path,
108+
mtransforms.IdentityTransform(),
109+
mcolors.to_rgba(evald["facecolor"]),
110+
)
111+
gc.restore()
7112

8113

9114
class PatchWrapper(ProxyWrapper):

examples/new_patch.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
======
3+
Circle
4+
======
5+
6+
Example of directly creating a Patch artist that is defined by a
7+
x, y, and path codes.
8+
9+
10+
"""
11+
12+
import matplotlib.pyplot as plt
13+
14+
15+
from data_prototype.artist import CompatibilityAxes
16+
from data_prototype.patches import Patch
17+
from data_prototype.containers import ArrayContainer
18+
19+
from matplotlib.path import Path
20+
21+
c = Path.unit_circle()
22+
23+
sc = ArrayContainer(None, x=c.vertices[:, 0], y=c.vertices[:, 1], codes=c.codes)
24+
lw2 = Patch(sc, linewidth=3, linestyle=":", edgecolor="C5", alpha=1, hatch=None)
25+
26+
fig, nax = plt.subplots()
27+
nax.set_aspect("equal")
28+
ax = CompatibilityAxes(nax)
29+
nax.add_artist(ax)
30+
ax.add_artist(lw2, 2)
31+
ax.set_xlim(-1.1, 1.1)
32+
ax.set_ylim(-1.1, 1.1)
33+
34+
plt.show()

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ exclude = '''
1818
| tests/data
1919
)/
2020
'''
21+
22+
[tool.setuptools_scm]

0 commit comments

Comments
 (0)