diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index a4614f214299..0f7478de0c66 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -12,7 +12,7 @@ from matplotlib.cbook import mplDeprecation from matplotlib import docstring, rcParams from .transforms import (Bbox, IdentityTransform, TransformedBbox, - TransformedPath, Transform) + TransformedPatchPath, TransformedPath, Transform) from .path import Path # Note, matplotlib artists use the doc strings for set and get @@ -685,9 +685,7 @@ def set_clip_path(self, path, transform=None): self._clippath = None success = True elif isinstance(path, Patch): - self._clippath = TransformedPath( - path.get_path(), - path.get_transform()) + self._clippath = TransformedPatchPath(path) success = True elif isinstance(path, tuple): path, transform = path @@ -698,6 +696,9 @@ def set_clip_path(self, path, transform=None): elif isinstance(path, Path): self._clippath = TransformedPath(path, transform) success = True + elif isinstance(path, TransformedPatchPath): + self._clippath = path + success = True elif isinstance(path, TransformedPath): self._clippath = path success = True diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 5190ddc873fe..d961741c06ac 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -10,7 +10,8 @@ import numpy.testing as np_test from numpy.testing import assert_almost_equal, assert_array_equal from numpy.testing import assert_array_almost_equal -from matplotlib.transforms import Affine2D, BlendedGenericTransform, Bbox +from matplotlib.transforms import (Affine2D, BlendedGenericTransform, Bbox, + TransformedPath, TransformedPatchPath) from matplotlib.path import Path from matplotlib.scale import LogScale from matplotlib.testing.decorators import cleanup, image_comparison @@ -576,6 +577,47 @@ def test_invalid_arguments(): assert_raises(RuntimeError, t.transform, [[1, 2, 3]]) +def test_transformed_path(): + points = [(0, 0), (1, 0), (1, 1), (0, 1)] + codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY] + path = Path(points, codes) + + trans = mtrans.Affine2D() + trans_path = TransformedPath(path, trans) + assert np.allclose(trans_path.get_fully_transformed_path().vertices, + points) + + # Changing the transform should change the result. + r2 = 1 / np.sqrt(2) + trans.rotate(np.pi / 4) + assert np.allclose(trans_path.get_fully_transformed_path().vertices, + [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)]) + + # Changing the path does not change the result (it's cached). + path.points = [(0, 0)] * 4 + assert np.allclose(trans_path.get_fully_transformed_path().vertices, + [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)]) + + +def test_transformed_patch_path(): + trans = mtrans.Affine2D() + patch = mpatches.Wedge((0, 0), 1, 45, 135, transform=trans) + + tpatch = TransformedPatchPath(patch) + points = tpatch.get_fully_transformed_path().vertices + + # Changing the transform should change the result. + trans.scale(2) + assert np.allclose(tpatch.get_fully_transformed_path().vertices, + points * 2) + + # Changing the path should change the result (and cancel out the scaling + # from the transform). + patch.set_radius(0.5) + assert np.allclose(tpatch.get_fully_transformed_path().vertices, + points) + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 812047910e11..dd56352255b9 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -2715,6 +2715,46 @@ def get_affine(self): return self._transform.get_affine() +class TransformedPatchPath(TransformedPath): + """ + A :class:`TransformedPatchPath` caches a non-affine transformed copy of + the :class:`~matplotlib.path.Patch`. This cached copy is automatically + updated when the non-affine part of the transform or the patch changes. + """ + def __init__(self, patch): + """ + Create a new :class:`TransformedPatchPath` from the given + :class:`~matplotlib.path.Patch`. + """ + TransformNode.__init__(self) + + transform = patch.get_transform() + self._patch = patch + self._transform = transform + self.set_children(transform) + self._path = patch.get_path() + self._transformed_path = None + self._transformed_points = None + + def _revalidate(self): + patch_path = self._patch.get_path() + # Only recompute if the invalidation includes the non_affine part of + # the transform, or the Patch's Path has changed. + if (self._transformed_path is None or self._path != patch_path or + (self._invalid & self.INVALID_NON_AFFINE == + self.INVALID_NON_AFFINE)): + self._path = patch_path + self._transformed_path = \ + self._transform.transform_path_non_affine(patch_path) + self._transformed_points = \ + Path._fast_from_codes_and_verts( + self._transform.transform_non_affine(patch_path.vertices), + None, + {'interpolation_steps': patch_path._interpolation_steps, + 'should_simplify': patch_path.should_simplify}) + self._invalid = 0 + + def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): ''' Modify the endpoints of a range as needed to avoid singularities.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: