diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index c0d98da701e8..396d91a147d7 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -130,9 +130,18 @@ def contains(self, mouseevent, radius=None): if callable(self._contains): return self._contains(self, mouseevent) radius = self._process_radius(radius) - inside = self.get_path().contains_point( - (mouseevent.x, mouseevent.y), self.get_transform(), radius) - return inside, {} + codes = self.get_path().codes + vertices = self.get_path().vertices + # if the current path is concatenated by multiple sub paths. + # get the indexes of the starting code(MOVETO) of all sub paths + idxs, = np.where(codes == Path.MOVETO) + # Don't split before the first MOVETO. + idxs = idxs[1:] + return any( + subpath.contains_point( + (mouseevent.x, mouseevent.y), self.get_transform(), radius) + for subpath in map( + Path, np.split(vertices, idxs), np.split(codes, idxs))), {} def contains_point(self, point, radius=None): """ diff --git a/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png b/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png new file mode 100644 index 000000000000..4e871e6cd02c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_path/arrow_contains_point.png differ diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index cc988caf3f9d..c99701097d95 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -5,11 +5,13 @@ from numpy.testing import assert_array_equal import pytest +from matplotlib import patches from matplotlib.path import Path from matplotlib.patches import Polygon from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt from matplotlib import transforms +from matplotlib.backend_bases import MouseEvent def test_empty_closed_path(): @@ -70,6 +72,46 @@ def test_nonlinear_containment(): ax.transData.transform_point((50, .5)), ax.transData) +@image_comparison( + baseline_images=['arrow_contains_point'], extensions=['png'], + remove_text=True, style='mpl20') +def test_arrow_contains_point(): + # fix bug (#8384) + fig, ax = plt.subplots() + ax.set_xlim((0, 2)) + ax.set_ylim((0, 2)) + + # create an arrow with Curve style + arrow = patches.FancyArrowPatch((0.5, 0.25), (1.5, 0.75), + arrowstyle='->', + mutation_scale=40) + ax.add_patch(arrow) + # create an arrow with Bracket style + arrow1 = patches.FancyArrowPatch((0.5, 1), (1.5, 1.25), + arrowstyle=']-[', + mutation_scale=40) + ax.add_patch(arrow1) + # create an arrow with other arrow style + arrow2 = patches.FancyArrowPatch((0.5, 1.5), (1.5, 1.75), + arrowstyle='fancy', + fill=False, + mutation_scale=40) + ax.add_patch(arrow2) + patches_list = [arrow, arrow1, arrow2] + + # generate some points + X, Y = np.meshgrid(np.arange(0, 2, 0.1), + np.arange(0, 2, 0.1)) + for k, (x, y) in enumerate(zip(X.ravel(), Y.ravel())): + xdisp, ydisp = ax.transData.transform_point([x, y]) + event = MouseEvent('button_press_event', fig.canvas, xdisp, ydisp) + for m, patch in enumerate(patches_list): + # set the points to red only if the arrow contains the point + inside, res = patch.contains(event) + if inside: + ax.scatter(x, y, s=5, c="r") + + @image_comparison(baseline_images=['path_clipping'], extensions=['svg'], remove_text=True) def test_path_clipping():