diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index be0ddc6ac3b5..09467429e052 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1083,6 +1083,7 @@ def tk_window_focus(): 'matplotlib.tests.test_tightlayout', 'matplotlib.tests.test_triangulation', 'matplotlib.tests.test_transforms', + 'matplotlib.tests.test_arrow_patches', ] diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 23e364f884cd..1f9314148c19 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -2841,6 +2841,18 @@ def connect(self, posA, posB): {"AvailableConnectorstyles": _pprint_styles(_style_list)} +def _point_along_a_line(x0, y0, x1, y1, d): + """ + find a point along a line connecting (x0, y0) -- (x1, y1) whose + distance from (x0, y0) is d. + """ + dx, dy = x0 - x1, y0 - y1 + ff = d/(dx*dx+dy*dy)**.5 + x2, y2 = x0 - ff*dx, y0 - ff*dy + + return x2, y2 + + class ArrowStyle(_Style): """ :class:`ArrowStyle` is a container class which defines several @@ -3460,35 +3472,57 @@ def transmute(self, path, mutation_size, linewidth): head_length = self.head_length * mutation_size in_f = inside_circle(x2, y2, head_length) arrow_path = [(x0, y0), (x1, y1), (x2, y2)] - arrow_out, arrow_in = \ - split_bezier_intersecting_with_closedpath(arrow_path, - in_f, - tolerence=0.01) + + from bezier import NonIntersectingPathException + + try: + arrow_out, arrow_in = \ + split_bezier_intersecting_with_closedpath(arrow_path, + in_f, + tolerence=0.01) + except NonIntersectingPathException: + # if this happens, make a straight line of the head_length long. + x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) + x1n, y1n = 0.5*(x0+x2), 0.5*(y0+y2) + arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)] + arrow_out = None # head head_width = self.head_width * mutation_size - head_l, head_r = make_wedged_bezier2(arrow_in, head_width / 2., - wm=.5) + head_left, head_right = \ + make_wedged_bezier2(arrow_in, head_width/2., + wm=.5) + # tail - tail_width = self.tail_width * mutation_size - tail_left, tail_right = get_parallels(arrow_out, tail_width / 2.) + if arrow_out is not None: + tail_width = self.tail_width * mutation_size + tail_left, tail_right = get_parallels(arrow_out, tail_width/2.) + + #head_right, head_left = head_r, head_l + patch_path = [(Path.MOVETO, tail_right[0]), + (Path.CURVE3, tail_right[1]), + (Path.CURVE3, tail_right[2]), + (Path.LINETO, head_right[0]), + (Path.CURVE3, head_right[1]), + (Path.CURVE3, head_right[2]), + (Path.CURVE3, head_left[1]), + (Path.CURVE3, head_left[0]), + (Path.LINETO, tail_left[2]), + (Path.CURVE3, tail_left[1]), + (Path.CURVE3, tail_left[0]), + (Path.LINETO, tail_right[0]), + (Path.CLOSEPOLY, tail_right[0]), + ] + else: + patch_path = [(Path.MOVETO, head_right[0]), + (Path.CURVE3, head_right[1]), + (Path.CURVE3, head_right[2]), + (Path.CURVE3, head_left[1]), + (Path.CURVE3, head_left[0]), + (Path.CLOSEPOLY, head_left[0]), + ] - head_right, head_left = head_r, head_l - patch_path = [(Path.MOVETO, tail_right[0]), - (Path.CURVE3, tail_right[1]), - (Path.CURVE3, tail_right[2]), - (Path.LINETO, head_right[0]), - (Path.CURVE3, head_right[1]), - (Path.CURVE3, head_right[2]), - (Path.CURVE3, head_left[1]), - (Path.CURVE3, head_left[0]), - (Path.LINETO, tail_left[2]), - (Path.CURVE3, tail_left[1]), - (Path.CURVE3, tail_left[0]), - (Path.LINETO, tail_right[0]), - (Path.CLOSEPOLY, tail_right[0]), - ] path = Path([p for c, p in patch_path], [c for c, p in patch_path]) return path, True @@ -3536,12 +3570,10 @@ def transmute(self, path, mutation_size, linewidth): in_f, tolerence=0.01) except NonIntersectingPathException: - # if this happens, make a straight line of the head_length - # long. - dx, dy = x2 - x1, y2 - y1 - ff = head_length / (dx * dx + dy * dy) ** .5 - x0, y0 = x2 - ff * dx, y2 - ff * dy - arrow_path = [(x0, y0), (x1, y1), (x2, y2)] + # if this happens, make a straight line of the head_length long. + x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) + x1n, y1n = 0.5*(x0+x2), 0.5*(y0+y2) + arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)] path_head = arrow_path else: path_head = path_in diff --git a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.pdf b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.pdf new file mode 100644 index 000000000000..195b54845e01 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.png b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.png new file mode 100644 index 000000000000..02eb43ae179d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.svg b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.svg new file mode 100644 index 000000000000..cddabf144d0c --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.svg @@ -0,0 +1,1577 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_arrow_patches.py b/lib/matplotlib/tests/test_arrow_patches.py new file mode 100644 index 000000000000..721a53e45157 --- /dev/null +++ b/lib/matplotlib/tests/test_arrow_patches.py @@ -0,0 +1,28 @@ +import matplotlib.pyplot as plt +from matplotlib.testing.decorators import image_comparison + +def draw_arrow(ax, t, r): + ax.annotate('', xy=(0.5, 0.5+r), xytext=(0.5, 0.5), size=30, + arrowprops=dict(arrowstyle=t, + fc="b", ec='k')) + +@image_comparison(baseline_images=['fancyarrow_test_image']) +def test_fancyarrow(): + r = [0.4, 0.3, 0.2, 0.1] + t = ["fancy", "simple"] + + fig, axes = plt.subplots(len(t), len(r), squeeze=False, + subplot_kw=dict(aspect=True), + figsize=(8, 4.5)) + + for i_r, r1 in enumerate(r): + for i_t, t1 in enumerate(t): + ax = axes[i_t, i_r] + draw_arrow(ax, t1, r1) + ax.tick_params(labelleft=False, labelbottom=False) + + +if __name__=='__main__': + import nose + nose.runmodule(argv=['-s','--with-doctest'], exit=False) +