diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index d94155550d27..490148ed0794 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1647,6 +1647,8 @@ def draw(self, renderer): """ if not hasattr(self, 'axes'): raise RuntimeError('Arcs can only be used in Axes instances') + if not self.get_visible(): + return self._recompute_transform() @@ -1659,14 +1661,40 @@ 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((width, height)) + stheta = np.rad2deg(np.arctan2(scale * y, x)) + # arctan2 has the range [-pi, pi], we expect [0, 2*pi] + return (stheta + 360) % 360 + + theta1 = self.theta1 + theta2 = self.theta2 + + if ( + # if we need to stretch the angles because we are distorted + width != height + # and we are not doing a full circle. + # + # 0 and 360 do not exactly round-trip through the angle + # stretching (due to both float precision limitations and + # the difference between the range of arctan2 [-pi, pi] and + # this method [0, 360]) so avoid doing it if we don't have to. + and not (theta1 != theta2 and theta1 % 360 == theta2 % 360) + ): + theta1 = theta_stretch(self.theta1, width / height) + theta2 = theta_stretch(self.theta2, width / height) + + # Get width and height in pixels we need to use + # `self.get_data_transform` rather than `self.get_transform` + # because we want the transform from dataspace to the + # screen space to estimate how big the arc will be in physical + # units when rendered (the transform that we get via + # `self.get_transform()` goes from an idealized unit-radius + # space to screen space). + data_to_screen_trans = self.get_data_transform() + pwidth, pheight = (data_to_screen_trans.transform((width, height)) - + data_to_screen_trans.transform((0, 0))) inv_error = (1.0 / 1.89818e-6) * 0.5 - if width < inv_error and height < inv_error: + + if pwidth < inv_error and pheight < inv_error: self._path = Path.arc(theta1, theta2) return Patch.draw(self, renderer) @@ -1700,29 +1728,32 @@ def segment_circle_intersect(x0, y0, x1, y1): y0e, y1e = y0, y1 xys = line_circle_intersect(x0, y0, x1, y1) xs, ys = xys.T - return xys[(x0e - epsilon < xs) & (xs < x1e + epsilon) - & (y0e - epsilon < ys) & (ys < y1e + epsilon)] + return xys[ + (x0e - epsilon < xs) & (xs < x1e + epsilon) + & (y0e - epsilon < ys) & (ys < y1e + epsilon) + ] # Transforms the axes box_path so that it is relative to the unit # circle in the same way that it is relative to the desired ellipse. - box_path = Path.unit_rectangle() box_path_transform = (transforms.BboxTransformTo(self.axes.bbox) + self.get_transform().inverted()) - box_path = box_path.transformed(box_path_transform) + box_path = Path.unit_rectangle().transformed(box_path_transform) 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:]): xy = segment_circle_intersect(*p0, *p1) x, y = xy.T - theta = np.rad2deg(np.arctan2(y, x)) + # arctan2 return [-pi, pi), the rest of our angles are in + # [0, 360], adjust as needed. + theta = (np.rad2deg(np.arctan2(y, x)) + 360) % 360 thetas.update(theta[(theta1 < theta) & (theta < theta2)]) thetas = sorted(thetas) + [theta2] - last_theta = theta1 theta1_rad = np.deg2rad(theta1) - inside = box_path.contains_point((np.cos(theta1_rad), - np.sin(theta1_rad))) + inside = box_path.contains_point( + (np.cos(theta1_rad), np.sin(theta1_rad)) + ) # save original path path_original = self._path diff --git a/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png b/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png index 8b81cde703ef..caa050aed900 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png and b/lib/matplotlib/tests/baseline_images/test_axes/arc_angles.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/all_quadrants_arcs.svg b/lib/matplotlib/tests/baseline_images/test_patches/all_quadrants_arcs.svg new file mode 100644 index 000000000000..a45712972165 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_patches/all_quadrants_arcs.svg @@ -0,0 +1,481 @@ + + + + + + + + + 2020-06-15T18:44:55.456487 + image/svg+xml + + + Matplotlib v3.2.1.post2847.dev0+g36a8896133, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patches/large_arc.svg b/lib/matplotlib/tests/baseline_images/test_patches/large_arc.svg index 96cd6b203314..d902870aa7b7 100644 --- a/lib/matplotlib/tests/baseline_images/test_patches/large_arc.svg +++ b/lib/matplotlib/tests/baseline_images/test_patches/large_arc.svg @@ -3,6 +3,20 @@ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + + + + + 2020-06-15T19:03:05.186353 + image/svg+xml + + + Matplotlib v3.2.1.post2849.dev0+ge5dca476ed, https://matplotlib.org/ + + + + + diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 0f32c1575902..475300b7c2d4 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -503,7 +503,56 @@ def test_fancyarrow_units(): @image_comparison(["large_arc.svg"], style="mpl20") def test_large_arc(): - ax = plt.figure().add_subplot() - ax.set_axis_off() - # A large arc that crosses the axes view limits. - ax.add_patch(mpatches.Arc((-100, 0), 201, 201)) + fig, (ax1, ax2) = plt.subplots(1, 2) + x = 210 + y = -2115 + diameter = 4261 + for ax in [ax1, ax2]: + a = mpatches.Arc((x, y), diameter, diameter, lw=2, color='k') + ax.add_patch(a) + ax.set_axis_off() + ax.set_aspect('equal') + # force the high accuracy case + ax1.set_xlim(7, 8) + ax1.set_ylim(5, 6) + + # force the low accuracy case + ax2.set_xlim(-25000, 18000) + ax2.set_ylim(-20000, 6600) + + +@image_comparison(["all_quadrants_arcs.svg"], style="mpl20") +def test_rotated_arcs(): + fig, ax_arr = plt.subplots(2, 2, squeeze=False, figsize=(10, 10)) + + scale = 10_000_000 + diag_centers = ((-1, -1), (-1, 1), (1, 1), (1, -1)) + on_axis_centers = ((0, 1), (1, 0), (0, -1), (-1, 0)) + skews = ((2, 2), (2, 1/10), (2, 1/100), (2, 1/1000)) + + for ax, (sx, sy) in zip(ax_arr.ravel(), skews): + k = 0 + for prescale, centers in zip((1 - .0001, (1 - .0001) / np.sqrt(2)), + (on_axis_centers, diag_centers)): + for j, (x_sign, y_sign) in enumerate(centers, start=k): + a = mpatches.Arc( + (x_sign * scale * prescale, + y_sign * scale * prescale), + scale * sx, + scale * sy, + lw=4, + color=f"C{j}", + zorder=1 + j, + angle=np.rad2deg(np.arctan2(y_sign, x_sign)) % 360, + label=f'big {j}', + gid=f'big {j}' + ) + ax.add_patch(a) + + k = j+1 + ax.set_xlim(-scale / 4000, scale / 4000) + ax.set_ylim(-scale / 4000, scale / 4000) + ax.axhline(0, color="k") + ax.axvline(0, color="k") + ax.set_axis_off() + ax.set_aspect("equal") pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy