From be6b9415e17f26f857d6701eecafd59970f3953f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 26 Jul 2024 17:12:55 -0400 Subject: [PATCH 1/6] ENH: add a basic Patch artist --- data_prototype/artist.py | 10 ++++ data_prototype/patches.py | 107 +++++++++++++++++++++++++++++++++++++- examples/new_patch.py | 30 +++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 examples/new_patch.py 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..9a5c3cd --- /dev/null +++ b/examples/new_patch.py @@ -0,0 +1,30 @@ +""" +""" + +import matplotlib.pyplot as plt +import numpy as np + + +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() + +# x = np.array([0, 0, 0.5, 1, 0]) +# y = np.array([0, 1, 1, 0, 0]) +# codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + +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() +ax = CompatibilityAxes(nax) +nax.add_artist(ax) +ax.add_artist(lw2, 2) +ax.set_xlim(0, np.pi * 4) +ax.set_ylim(-1.1, 1.1) + +plt.show() From a3cad7e7efaae4bc1952ce0bc3e5244fe46e59cc Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 2 Aug 2024 15:49:58 -0400 Subject: [PATCH 2/6] DOC: add non-empty docstring to example --- examples/new_patch.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/new_patch.py b/examples/new_patch.py index 9a5c3cd..a54966f 100644 --- a/examples/new_patch.py +++ b/examples/new_patch.py @@ -1,4 +1,5 @@ """ +Example of directly creating a Patch artist that is defined by a Path """ import matplotlib.pyplot as plt @@ -13,10 +14,6 @@ c = Path.unit_circle() -# x = np.array([0, 0, 0.5, 1, 0]) -# y = np.array([0, 1, 1, 0, 0]) -# codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) - 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) From 73a56bd5fc129848fdb1b49acb6cb5d042808180 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 2 Aug 2024 15:52:41 -0400 Subject: [PATCH 3/6] BLD: silence install warning --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) 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] From 7dcc0f700acb8ce4ebd8ebe9f7c126503fa6e1e6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 2 Aug 2024 15:56:30 -0400 Subject: [PATCH 4/6] DOC: fix docstring --- examples/new_patch.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/new_patch.py b/examples/new_patch.py index a54966f..c9ea0b8 100644 --- a/examples/new_patch.py +++ b/examples/new_patch.py @@ -1,5 +1,12 @@ """ -Example of directly creating a Patch artist that is defined by a Path +====== +Circle +====== + +Example of directly creating a Patch artist that is defined by a +x, y, and path codes. + + """ import matplotlib.pyplot as plt @@ -18,10 +25,11 @@ 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(0, np.pi * 4) +ax.set_xlim(-1.1, 1.1) ax.set_ylim(-1.1, 1.1) plt.show() From 7079ef38e551432a9d61272644ed53b4ca84172d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 2 Aug 2024 16:55:03 -0400 Subject: [PATCH 5/6] STY: placate linter --- examples/new_patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/new_patch.py b/examples/new_patch.py index c9ea0b8..80a51f7 100644 --- a/examples/new_patch.py +++ b/examples/new_patch.py @@ -25,7 +25,7 @@ lw2 = Patch(sc, linewidth=3, linestyle=":", edgecolor="C5", alpha=1, hatch=None) fig, nax = plt.subplots() -nax.set_aspect('equal') +nax.set_aspect("equal") ax = CompatibilityAxes(nax) nax.add_artist(ax) ax.add_artist(lw2, 2) From f7d140de5b0efe714a27767ab76fc5f0dffe53b5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 2 Aug 2024 16:56:18 -0400 Subject: [PATCH 6/6] STY: placate the _other_ linter --- examples/new_patch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/new_patch.py b/examples/new_patch.py index 80a51f7..2c9d4c3 100644 --- a/examples/new_patch.py +++ b/examples/new_patch.py @@ -10,7 +10,6 @@ """ import matplotlib.pyplot as plt -import numpy as np from data_prototype.artist import CompatibilityAxes