diff --git a/doc/api/api_changes/2017-02-10-DS_elliptical_arc_angle.rst b/doc/api/api_changes/2017-02-10-DS_elliptical_arc_angle.rst new file mode 100644 index 000000000000..1570a17d9549 --- /dev/null +++ b/doc/api/api_changes/2017-02-10-DS_elliptical_arc_angle.rst @@ -0,0 +1,8 @@ +Elliptical arcs now drawn between correct angles +```````````````````````````````````````````````` + +The `matplotlib.patches.Arc` patch is now correctly drawn between the given +angles. + +Previously a circular arc was drawn and then stretched into an ellipse, +so the resulting arc did not lie between *theta1* and *theta2*. diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 0e5073b6c9c2..9bbe19d50d8c 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1554,8 +1554,6 @@ def __init__(self, xy, width, height, angle=0.0, self.theta1 = theta1 self.theta2 = theta2 - self._path = Path.arc(self.theta1, self.theta2) - @allow_rasterization def draw(self, renderer): """ @@ -1607,15 +1605,25 @@ def draw(self, renderer): self._recompute_transform() - # Get the width and height in pixels width = self.convert_xunits(self.width) height = self.convert_yunits(self.height) + + # If the width and height of ellipse are not equal, take into account + # stretching when calculating angles to draw between + def theta_stretch(theta, scale): + theta = np.deg2rad(theta) + x = np.cos(theta) + y = np.sin(theta) + return np.rad2deg(np.arctan2(scale * y, x)) + theta1 = theta_stretch(self.theta1, width / height) + theta2 = theta_stretch(self.theta2, width / height) + + # Get width and height in pixels width, height = self.get_transform().transform_point( (width, height)) inv_error = (1.0 / 1.89818e-6) * 0.5 - if width < inv_error and height < inv_error: - # self._path = Path.arc(self.theta1, self.theta2) + self._path = Path.arc(theta1, theta2) return Patch.draw(self, renderer) def iter_circle_intersect_on_line(x0, y0, x1, y1): @@ -1670,8 +1678,6 @@ def iter_circle_intersect_on_line_seg(x0, y0, x1, y1): self.get_transform().inverted() box_path = box_path.transformed(box_path_transform) - theta1 = self.theta1 - theta2 = self.theta2 thetas = set() # For each of the point pairs, there is a line segment for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]): diff --git a/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png b/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png new file mode 100644 index 000000000000..82a9840913f3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 19cc9267b794..86829df66bfa 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -982,6 +982,32 @@ def test_canonical(): ax.plot([1, 2, 3]) +@image_comparison(baseline_images=['arc_angles'], remove_text=True, + style='default', extensions=['png']) +def test_arc_angles(): + from matplotlib import patches + # Ellipse parameters + w = 2 + h = 1 + centre = (0.2, 0.5) + + fig, axs = plt.subplots(3, 3) + for i, ax in enumerate(axs.flat): + theta2 = i * 360 / 9 + theta1 = theta2 - 45 + ax.add_patch(patches.Ellipse(centre, w, h, alpha=0.3)) + ax.add_patch(patches.Arc(centre, w, h, theta1=theta1, theta2=theta2)) + # Straight lines intersecting start and end of arc + ax.plot([2 * np.cos(np.deg2rad(theta1)) + centre[0], + centre[0], + 2 * np.cos(np.deg2rad(theta2)) + centre[0]], + [2 * np.sin(np.deg2rad(theta1)) + centre[1], + centre[1], + 2 * np.sin(np.deg2rad(theta2)) + centre[1]]) + ax.set_xlim(-2, 2) + ax.set_ylim(-2, 2) + + @image_comparison(baseline_images=['arc_ellipse'], remove_text=True) def test_arc_ellipse(): @@ -990,15 +1016,14 @@ def test_arc_ellipse(): width, height = 1e-1, 3e-1 angle = -30 - theta = np.arange(0.0, 360.0, 1.0)*np.pi/180.0 - x = width/2. * np.cos(theta) - y = height/2. * np.sin(theta) + theta = np.arange(0.0, 360.0, 1.0) * np.pi / 180.0 + x = width / 2. * np.cos(theta) + y = height / 2. * np.sin(theta) - rtheta = angle*np.pi/180. + rtheta = angle * np.pi / 180. R = np.array([ - [np.cos(rtheta), -np.sin(rtheta)], - [np.sin(rtheta), np.cos(rtheta)], - ]) + [np.cos(rtheta), -np.sin(rtheta)], + [np.sin(rtheta), np.cos(rtheta)]]) x, y = np.dot(R, np.array([x, y])) x += xcenter @@ -1006,7 +1031,8 @@ def test_arc_ellipse(): fig = plt.figure() ax = fig.add_subplot(211, aspect='auto') - ax.fill(x, y, alpha=0.2, facecolor='yellow', edgecolor='yellow', linewidth=1, zorder=1) + ax.fill(x, y, alpha=0.2, facecolor='yellow', edgecolor='yellow', + linewidth=1, zorder=1) e1 = patches.Arc((xcenter, ycenter), width, height, angle=angle, linewidth=2, fill=False, zorder=2)