diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 3264c67ad2e8..e0ceee254363 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -808,6 +808,7 @@ def __init__(self, ax, *args, **kwargs): self.hatches = kwargs.get('hatches', [None]) self.alpha = kwargs.get('alpha', None) + self.clip_path = kwargs.get('clip_path', None) self.origin = kwargs.get('origin', None) self.extent = kwargs.get('extent', None) cmap = kwargs.get('cmap', None) @@ -957,6 +958,7 @@ def __init__(self, ax, *args, **kwargs): col.sticky_edges.y[:] = [self._mins[1], self._maxs[1]] self.ax.update_datalim([self._mins, self._maxs]) self.ax.autoscale_view(tight=True) + self.set_clip_path(self.clip_path) self.changed() # set the colors @@ -1322,6 +1324,27 @@ def set_alpha(self, alpha): self.alpha = alpha self.changed() + def get_clip_path(self): + """Returns clip_path to be applied to all ContourSet artists.""" + return self.clip_path + + def set_clip_path(self, path, transform=None): + """ + Sets clip_path for all ContourSet artists. + + Optional keyword arguments: + + *transform*: + A :class:`~matplotlib.transforms.Transform` instance + which will be applied to the path before using it for + clipping. May be provided if *path* is a + :class:`~matplotlib.path.Path` instance. + + """ + self.clip_path = path + for col in self.collections: + col.set_clip_path(path, transform) + def find_nearest_contour(self, x, y, indices=None, pixel=True): """ Finds contour that is closest to a point. Defaults to @@ -1783,6 +1806,12 @@ def _initialize_x_y(self, z): however introduce rendering artifacts at chunk boundaries depending on the backend, the *antialiased* flag and value of *alpha*. + *clip_path*: [ *None* | Patch | Path ] + A :class:`~matplotlib.patches.Patch` (or subclass), or + :class:`~matplotlib.path.Path` instance for setting a clip_path + for the ContourSet. If *clip_path* is *None* then the clipping + path is removed. + contour-only keyword arguments: *linewidths*: [ *None* | number | tuple of numbers ] diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_arrow.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_arrow.png new file mode 100644 index 000000000000..7d5b6e56991f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_arrow.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_circle.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_circle.png new file mode 100644 index 000000000000..4fe0c0dc8b7d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_circle.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_none.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_none.png new file mode 100644 index 000000000000..9b069ff42205 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_none.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_polygon.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_polygon.png new file mode 100644 index 000000000000..0aaba7ec57a3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_clip_path_polygon.png differ diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index cc90aa2510b0..faae6aabbcc6 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -7,6 +7,7 @@ from matplotlib import mlab from matplotlib.testing.decorators import image_comparison from matplotlib import pyplot as plt +import matplotlib.patches as mpatches from numpy.testing import assert_array_almost_equal import pytest import warnings @@ -340,3 +341,126 @@ def test_internal_cpp_api(): with pytest.raises(ValueError) as excinfo: qcg.create_filled_contour(1, 0) excinfo.match(r'filled contour levels must be increasing') + + +@image_comparison(baseline_images=['contour_clip_path_arrow', + 'contour_clip_path_circle', + 'contour_clip_path_polygon', + 'contour_clip_path_none'], + extensions=['png']) +def test_contourf_clip_path_kwarg(): + # GitHub Issue 2369 + data = np.arange(100).reshape(10, 10) + + fig = plt.figure() + ax = fig.add_subplot(111) + arrow = mpatches.Arrow(0, 0.5, 0.5, 0, + transform=ax.transAxes) + plt.contourf(data, clip_path=arrow) + + fig = plt.figure() + ax = fig.add_subplot(111) + circle = mpatches.Circle([0.5, 0.5], 0.5, + transform=ax.transAxes) + plt.contourf(data, clip_path=circle) + + fig = plt.figure() + ax = fig.add_subplot(111) + polygon = mpatches.RegularPolygon([0.5, 0.5], 5, 0.5, + transform=ax.transAxes) + plt.contourf(data, clip_path=polygon) + + fig = plt.figure() + ax = fig.add_subplot(111) + plt.contourf(data, clip_path=None) + + +@image_comparison(baseline_images=['contour_clip_path_arrow', + 'contour_clip_path_circle', + 'contour_clip_path_polygon', + 'contour_clip_path_none'], + extensions=['png']) +def test_contourf_set_clip_path(): + # GitHub Issue 2369 + data = np.arange(100).reshape(10, 10) + + fig = plt.figure() + ax = fig.add_subplot(111) + arrow = mpatches.Arrow(0, 0.5, 0.5, 0, + transform=ax.transAxes) + plt.contourf(data).set_clip_path(arrow) + + fig = plt.figure() + ax = fig.add_subplot(111) + circle = mpatches.Circle([0.5, 0.5], 0.5, + transform=ax.transAxes) + plt.contourf(data).set_clip_path(circle) + + fig = plt.figure() + ax = fig.add_subplot(111) + polygon = mpatches.RegularPolygon([0.5, 0.5], 5, 0.5, + transform=ax.transAxes) + plt.contourf(data).set_clip_path(polygon) + + fig = plt.figure() + ax = fig.add_subplot(111) + plt.contourf(data).set_clip_path(None) + + +def test_contourf_get_clip_path(): + # Issue 2369 + data = np.arange(100).reshape(10, 10) + fig = plt.figure() + ax = fig.add_subplot(111) + polygon = mpatches.RegularPolygon([0.5, 0.5], 5, 0.5, + transform=ax.transAxes) + cs = plt.contourf(data) + cs.set_clip_path(polygon) + # Get clip_path of the ContourSet + get_polygon = cs.get_clip_path() + # Getter should return the same clip_path + assert polygon == get_polygon + + +@image_comparison(baseline_images=['contour_clip_path_polygon'], + extensions=['png']) +def test_contourf_reset_clip_path_none_to_shape(): + # Issue 2369 + data = np.arange(100).reshape(10, 10) + fig = plt.figure() + ax = fig.add_subplot(111) + polygon = mpatches.RegularPolygon([0.5, 0.5], 5, 0.5, + transform=ax.transAxes) + cs = plt.contourf(data) + cs.set_clip_path(None) + cs.set_clip_path(polygon) + + +@image_comparison(baseline_images=['contour_clip_path_none'], + extensions=['png']) +def test_contourf_reset_clip_path_shape_to_none(): + # Issue 2369 + data = np.arange(100).reshape(10, 10) + fig = plt.figure() + ax = fig.add_subplot(111) + polygon = mpatches.RegularPolygon([0.5, 0.5], 5, 0.5, + transform=ax.transAxes) + cs = plt.contourf(data) + cs.set_clip_path(polygon) + cs.set_clip_path(None) + + +@image_comparison(baseline_images=['contour_clip_path_arrow'], + extensions=['png']) +def test_contourf_reset_clip_path_shape_to_shape(): + # Issue 2369 + data = np.arange(100).reshape(10, 10) + fig = plt.figure() + ax = fig.add_subplot(111) + polygon = mpatches.RegularPolygon([0.5, 0.5], 5, 0.5, + transform=ax.transAxes) + arrow = mpatches.Arrow(0, 0.5, 0.5, 0, + transform=ax.transAxes) + cs = plt.contourf(data) + cs.set_clip_path(polygon) + cs.set_clip_path(arrow)