Skip to content

Commit de19d5c

Browse files
committed
Merge pull request matplotlib#4920 from QuLogic/transformedpatch
ENH: Add TransformedPatchPath for clipping.
2 parents 54ead93 + c2877b3 commit de19d5c

File tree

3 files changed

+88
-5
lines changed

3 files changed

+88
-5
lines changed

lib/matplotlib/artist.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from matplotlib.cbook import mplDeprecation
1313
from matplotlib import docstring, rcParams
1414
from .transforms import (Bbox, IdentityTransform, TransformedBbox,
15-
TransformedPath, Transform)
15+
TransformedPatchPath, TransformedPath, Transform)
1616
from .path import Path
1717

1818
# Note, matplotlib artists use the doc strings for set and get
@@ -685,9 +685,7 @@ def set_clip_path(self, path, transform=None):
685685
self._clippath = None
686686
success = True
687687
elif isinstance(path, Patch):
688-
self._clippath = TransformedPath(
689-
path.get_path(),
690-
path.get_transform())
688+
self._clippath = TransformedPatchPath(path)
691689
success = True
692690
elif isinstance(path, tuple):
693691
path, transform = path
@@ -698,6 +696,9 @@ def set_clip_path(self, path, transform=None):
698696
elif isinstance(path, Path):
699697
self._clippath = TransformedPath(path, transform)
700698
success = True
699+
elif isinstance(path, TransformedPatchPath):
700+
self._clippath = path
701+
success = True
701702
elif isinstance(path, TransformedPath):
702703
self._clippath = path
703704
success = True

lib/matplotlib/tests/test_transforms.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import numpy.testing as np_test
1111
from numpy.testing import assert_almost_equal, assert_array_equal
1212
from numpy.testing import assert_array_almost_equal
13-
from matplotlib.transforms import Affine2D, BlendedGenericTransform, Bbox
13+
from matplotlib.transforms import (Affine2D, BlendedGenericTransform, Bbox,
14+
TransformedPath, TransformedPatchPath)
1415
from matplotlib.path import Path
1516
from matplotlib.scale import LogScale
1617
from matplotlib.testing.decorators import cleanup, image_comparison
@@ -576,6 +577,47 @@ def test_invalid_arguments():
576577
assert_raises(RuntimeError, t.transform, [[1, 2, 3]])
577578

578579

580+
def test_transformed_path():
581+
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
582+
codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]
583+
path = Path(points, codes)
584+
585+
trans = mtrans.Affine2D()
586+
trans_path = TransformedPath(path, trans)
587+
assert np.allclose(trans_path.get_fully_transformed_path().vertices,
588+
points)
589+
590+
# Changing the transform should change the result.
591+
r2 = 1 / np.sqrt(2)
592+
trans.rotate(np.pi / 4)
593+
assert np.allclose(trans_path.get_fully_transformed_path().vertices,
594+
[(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)])
595+
596+
# Changing the path does not change the result (it's cached).
597+
path.points = [(0, 0)] * 4
598+
assert np.allclose(trans_path.get_fully_transformed_path().vertices,
599+
[(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)])
600+
601+
602+
def test_transformed_patch_path():
603+
trans = mtrans.Affine2D()
604+
patch = mpatches.Wedge((0, 0), 1, 45, 135, transform=trans)
605+
606+
tpatch = TransformedPatchPath(patch)
607+
points = tpatch.get_fully_transformed_path().vertices
608+
609+
# Changing the transform should change the result.
610+
trans.scale(2)
611+
assert np.allclose(tpatch.get_fully_transformed_path().vertices,
612+
points * 2)
613+
614+
# Changing the path should change the result (and cancel out the scaling
615+
# from the transform).
616+
patch.set_radius(0.5)
617+
assert np.allclose(tpatch.get_fully_transformed_path().vertices,
618+
points)
619+
620+
579621
if __name__ == '__main__':
580622
import nose
581623
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

lib/matplotlib/transforms.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,46 @@ def get_affine(self):
27152715
return self._transform.get_affine()
27162716

27172717

2718+
class TransformedPatchPath(TransformedPath):
2719+
"""
2720+
A :class:`TransformedPatchPath` caches a non-affine transformed copy of
2721+
the :class:`~matplotlib.path.Patch`. This cached copy is automatically
2722+
updated when the non-affine part of the transform or the patch changes.
2723+
"""
2724+
def __init__(self, patch):
2725+
"""
2726+
Create a new :class:`TransformedPatchPath` from the given
2727+
:class:`~matplotlib.path.Patch`.
2728+
"""
2729+
TransformNode.__init__(self)
2730+
2731+
transform = patch.get_transform()
2732+
self._patch = patch
2733+
self._transform = transform
2734+
self.set_children(transform)
2735+
self._path = patch.get_path()
2736+
self._transformed_path = None
2737+
self._transformed_points = None
2738+
2739+
def _revalidate(self):
2740+
patch_path = self._patch.get_path()
2741+
# Only recompute if the invalidation includes the non_affine part of
2742+
# the transform, or the Patch's Path has changed.
2743+
if (self._transformed_path is None or self._path != patch_path or
2744+
(self._invalid & self.INVALID_NON_AFFINE ==
2745+
self.INVALID_NON_AFFINE)):
2746+
self._path = patch_path
2747+
self._transformed_path = \
2748+
self._transform.transform_path_non_affine(patch_path)
2749+
self._transformed_points = \
2750+
Path._fast_from_codes_and_verts(
2751+
self._transform.transform_non_affine(patch_path.vertices),
2752+
None,
2753+
{'interpolation_steps': patch_path._interpolation_steps,
2754+
'should_simplify': patch_path.should_simplify})
2755+
self._invalid = 0
2756+
2757+
27182758
def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True):
27192759
'''
27202760
Modify the endpoints of a range as needed to avoid singularities.

0 commit comments

Comments
 (0)