Skip to content

Commit d67550c

Browse files
committed
Backport PR #17564: FIX: correctly handle large arcs
Merge pull request #17564 from tacaswell/fix_big_arc FIX: big arc code path Conflicts: lib/matplotlib/patches.py - implicitly backport a change from #15356 (from `- trans ` -> `+ trans.inverted()`)
1 parent d885c0f commit d67550c

File tree

5 files changed

+632
-32
lines changed

5 files changed

+632
-32
lines changed

lib/matplotlib/patches.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,8 @@ def draw(self, renderer):
16071607
"""
16081608
if not hasattr(self, 'axes'):
16091609
raise RuntimeError('Arcs can only be used in Axes instances')
1610+
if not self.get_visible():
1611+
return
16101612

16111613
self._recompute_transform()
16121614

@@ -1619,14 +1621,40 @@ def theta_stretch(theta, scale):
16191621
theta = np.deg2rad(theta)
16201622
x = np.cos(theta)
16211623
y = np.sin(theta)
1622-
return np.rad2deg(np.arctan2(scale * y, x))
1623-
theta1 = theta_stretch(self.theta1, width / height)
1624-
theta2 = theta_stretch(self.theta2, width / height)
1625-
1626-
# Get width and height in pixels
1627-
width, height = self.get_transform().transform((width, height))
1624+
stheta = np.rad2deg(np.arctan2(scale * y, x))
1625+
# arctan2 has the range [-pi, pi], we expect [0, 2*pi]
1626+
return (stheta + 360) % 360
1627+
1628+
theta1 = self.theta1
1629+
theta2 = self.theta2
1630+
1631+
if (
1632+
# if we need to stretch the angles because we are distorted
1633+
width != height
1634+
# and we are not doing a full circle.
1635+
#
1636+
# 0 and 360 do not exactly round-trip through the angle
1637+
# stretching (due to both float precision limitations and
1638+
# the difference between the range of arctan2 [-pi, pi] and
1639+
# this method [0, 360]) so avoid doing it if we don't have to.
1640+
and not (theta1 != theta2 and theta1 % 360 == theta2 % 360)
1641+
):
1642+
theta1 = theta_stretch(self.theta1, width / height)
1643+
theta2 = theta_stretch(self.theta2, width / height)
1644+
1645+
# Get width and height in pixels we need to use
1646+
# `self.get_data_transform` rather than `self.get_transform`
1647+
# because we want the transform from dataspace to the
1648+
# screen space to estimate how big the arc will be in physical
1649+
# units when rendered (the transform that we get via
1650+
# `self.get_transform()` goes from an idealized unit-radius
1651+
# space to screen space).
1652+
data_to_screen_trans = self.get_data_transform()
1653+
pwidth, pheight = (data_to_screen_trans.transform((width, height)) -
1654+
data_to_screen_trans.transform((0, 0)))
16281655
inv_error = (1.0 / 1.89818e-6) * 0.5
1629-
if width < inv_error and height < inv_error:
1656+
1657+
if pwidth < inv_error and pheight < inv_error:
16301658
self._path = Path.arc(theta1, theta2)
16311659
return Patch.draw(self, renderer)
16321660

@@ -1660,29 +1688,32 @@ def segment_circle_intersect(x0, y0, x1, y1):
16601688
y0e, y1e = y0, y1
16611689
xys = line_circle_intersect(x0, y0, x1, y1)
16621690
xs, ys = xys.T
1663-
return xys[(x0e - epsilon < xs) & (xs < x1e + epsilon)
1664-
& (y0e - epsilon < ys) & (ys < y1e + epsilon)]
1691+
return xys[
1692+
(x0e - epsilon < xs) & (xs < x1e + epsilon)
1693+
& (y0e - epsilon < ys) & (ys < y1e + epsilon)
1694+
]
16651695

16661696
# Transforms the axes box_path so that it is relative to the unit
16671697
# circle in the same way that it is relative to the desired ellipse.
1668-
box_path = Path.unit_rectangle()
16691698
box_path_transform = (transforms.BboxTransformTo(self.axes.bbox)
1670-
- self.get_transform())
1671-
box_path = box_path.transformed(box_path_transform)
1699+
+ self.get_transform().inverted())
1700+
box_path = Path.unit_rectangle().transformed(box_path_transform)
16721701

16731702
thetas = set()
16741703
# For each of the point pairs, there is a line segment
16751704
for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
16761705
xy = segment_circle_intersect(*p0, *p1)
16771706
x, y = xy.T
1678-
theta = np.rad2deg(np.arctan2(y, x))
1707+
# arctan2 return [-pi, pi), the rest of our angles are in
1708+
# [0, 360], adjust as needed.
1709+
theta = (np.rad2deg(np.arctan2(y, x)) + 360) % 360
16791710
thetas.update(theta[(theta1 < theta) & (theta < theta2)])
16801711
thetas = sorted(thetas) + [theta2]
1681-
16821712
last_theta = theta1
16831713
theta1_rad = np.deg2rad(theta1)
1684-
inside = box_path.contains_point((np.cos(theta1_rad),
1685-
np.sin(theta1_rad)))
1714+
inside = box_path.contains_point(
1715+
(np.cos(theta1_rad), np.sin(theta1_rad))
1716+
)
16861717

16871718
# save original path
16881719
path_original = self._path
Loading

0 commit comments

Comments
 (0)
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