diff --git a/data_prototype/artist.py b/data_prototype/artist.py index 7beddc3..9426c52 100644 --- a/data_prototype/artist.py +++ b/data_prototype/artist.py @@ -1,5 +1,6 @@ from bisect import insort from typing import Sequence +from contextlib import contextmanager import numpy as np @@ -316,3 +317,12 @@ def set_xlim(self, min_=None, max_=None): def set_ylim(self, min_=None, max_=None): self.axes.set_ylim(min_, max_) + + +@contextmanager +def _renderer_group(renderer, group, gid): + renderer.open_group(group, gid) + try: + yield + finally: + renderer.close_group(group) diff --git a/data_prototype/patches.py b/data_prototype/patches.py index 3c05c01..daf50d8 100644 --- a/data_prototype/patches.py +++ b/data_prototype/patches.py @@ -1,9 +1,114 @@ +from matplotlib.patches import Patch as _Patch, Rectangle as _Rectangle +import matplotlib.path as mpath +import matplotlib.transforms as mtransforms +import matplotlib.colors as mcolors import numpy as np + from .wrappers import ProxyWrapper, _stale_wrapper + from .containers import DataContainer -from matplotlib.patches import Patch as _Patch, Rectangle as _Rectangle +from .artist import Artist, _renderer_group +from .description import Desc +from .conversion_edge import Graph, CoordinateEdge, DefaultEdge + + +class Patch(Artist): + def __init__(self, container, edges=None, **kwargs): + super().__init__(container, edges, **kwargs) + + scalar = Desc((), "display") # ... this needs thinking... + edges = [ + CoordinateEdge.from_coords("xycoords", {"x": "auto", "y": "auto"}, "data"), + CoordinateEdge.from_coords("codes", {"codes": "auto"}, "display"), + CoordinateEdge.from_coords("facecolor", {"color": Desc(())}, "display"), + CoordinateEdge.from_coords("edgecolor", {"color": Desc(())}, "display"), + CoordinateEdge.from_coords("linewidth", {"linewidth": Desc(())}, "display"), + CoordinateEdge.from_coords("hatch", {"hatch": Desc(())}, "display"), + CoordinateEdge.from_coords("alpha", {"alpha": Desc(())}, "display"), + DefaultEdge.from_default_value("facecolor_def", "facecolor", scalar, "C0"), + DefaultEdge.from_default_value("edgecolor_def", "edgecolor", scalar, "C0"), + DefaultEdge.from_default_value("linewidth_def", "linewidth", scalar, 1), + DefaultEdge.from_default_value("linestyle_def", "linestyle", scalar, "-"), + DefaultEdge.from_default_value("alpha_def", "alpha", scalar, 1), + DefaultEdge.from_default_value("hatch_def", "hatch", scalar, None), + ] + self._graph = self._graph + Graph(edges) + + def draw(self, renderer, graph: Graph) -> None: + if not self.get_visible(): + return + g = graph + self._graph + desc = Desc(("N",), "display") + scalar = Desc((), "display") # ... this needs thinking... + + require = { + "x": desc, + "y": desc, + "codes": desc, + "facecolor": scalar, + "edgecolor": scalar, + "linewidth": scalar, + "linestyle": scalar, + "hatch": scalar, + "alpha": scalar, + } + + # copy from line + conv = g.evaluator(self._container.describe(), require) + query, _ = self._container.query(g) + evald = conv.evaluate(query) + + clip_conv = g.evaluator( + self._clip_box.describe(), + {"x": Desc(("N",), "display"), "y": Desc(("N",), "display")}, + ) + clip_query, _ = self._clip_box.query(g) + clipx, clipy = clip_conv.evaluate(clip_query).values() + # copy from line + + path = mpath.Path._fast_from_codes_and_verts( + verts=np.vstack([evald["x"], evald["y"]]).T, codes=evald["codes"] + ) + + with _renderer_group(renderer, "patch", None): + gc = renderer.new_gc() + + gc.set_foreground(evald["facecolor"], isRGBA=False) + gc.set_clip_rectangle( + mtransforms.Bbox.from_extents(clipx[0], clipy[0], clipx[1], clipy[1]) + ) + gc.set_linewidth(evald["linewidth"]) + # gc.set_dashes(*self._dash_pattern) + # gc.set_capstyle(self._capstyle) + # gc.set_joinstyle(self._joinstyle) + + # gc.set_antialiased(self._antialiased) + + # gc.set_url(self._url) + # gc.set_snap(self.get_snap()) + + gc.set_alpha(evald["alpha"]) + + if evald["hatch"] is not None: + gc.set_hatch(evald["hatch"]) + gc.set_hatch_color(evald["hatch_color"]) + + # if self.get_sketch_params() is not None: + # gc.set_sketch_params(*self.get_sketch_params()) + + # if self.get_path_effects(): + # from matplotlib.patheffects import PathEffectRenderer + # renderer = PathEffectRenderer(self.get_path_effects(), renderer) + + renderer.draw_path( + gc, + path, + mtransforms.IdentityTransform(), + mcolors.to_rgba(evald["facecolor"]), + ) + gc.restore() class PatchWrapper(ProxyWrapper): diff --git a/examples/new_patch.py b/examples/new_patch.py new file mode 100644 index 0000000..2c9d4c3 --- /dev/null +++ b/examples/new_patch.py @@ -0,0 +1,34 @@ +""" +====== +Circle +====== + +Example of directly creating a Patch artist that is defined by a +x, y, and path codes. + + +""" + +import matplotlib.pyplot as plt + + +from data_prototype.artist import CompatibilityAxes +from data_prototype.patches import Patch +from data_prototype.containers import ArrayContainer + +from matplotlib.path import Path + +c = Path.unit_circle() + +sc = ArrayContainer(None, x=c.vertices[:, 0], y=c.vertices[:, 1], codes=c.codes) +lw2 = Patch(sc, linewidth=3, linestyle=":", edgecolor="C5", alpha=1, hatch=None) + +fig, nax = plt.subplots() +nax.set_aspect("equal") +ax = CompatibilityAxes(nax) +nax.add_artist(ax) +ax.add_artist(lw2, 2) +ax.set_xlim(-1.1, 1.1) +ax.set_ylim(-1.1, 1.1) + +plt.show() diff --git a/pyproject.toml b/pyproject.toml index 8ce676f..a10e30d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,3 +18,5 @@ exclude = ''' | tests/data )/ ''' + +[tool.setuptools_scm]