Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f8a87e2

Browse files
committed
Vectorize Arc.draw.
... by replacing generators into functions working on numpy arrays. All modified functions are nested and thus not publically accessible. Also remove handling the tangential case as the angles are immediately stuffed in a set() so duplicate angles don't matter.
1 parent 9e7a235 commit f8a87e2

File tree

3 files changed

+70
-42
lines changed

3 files changed

+70
-42
lines changed

lib/matplotlib/patches.py

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,14 +1561,12 @@ def draw(self, renderer):
15611561
calculation much easier than doing rotated ellipse
15621562
intersection directly).
15631563
1564-
This uses the "line intersecting a circle" algorithm
1565-
from:
1564+
This uses the "line intersecting a circle" algorithm from:
15661565
15671566
Vince, John. *Geometry for Computer Graphics: Formulae,
15681567
Examples & Proofs.* London: Springer-Verlag, 2005.
15691568
1570-
2. The angles of each of the intersection points are
1571-
calculated.
1569+
2. The angles of each of the intersection points are calculated.
15721570
15731571
3. Proceeding counterclockwise starting in the positive
15741572
x-direction, each of the visible arc-segments between the
@@ -1601,33 +1599,25 @@ def theta_stretch(theta, scale):
16011599
self._path = Path.arc(theta1, theta2)
16021600
return Patch.draw(self, renderer)
16031601

1604-
def iter_circle_intersect_on_line(x0, y0, x1, y1):
1602+
def line_circle_intersect(x0, y0, x1, y1):
16051603
dx = x1 - x0
16061604
dy = y1 - y0
16071605
dr2 = dx * dx + dy * dy
16081606
D = x0 * y1 - x1 * y0
16091607
D2 = D * D
16101608
discrim = dr2 - D2
1611-
1612-
# Single (tangential) intersection
1613-
if discrim == 0.0:
1614-
x = (D * dy) / dr2
1615-
y = (-D * dx) / dr2
1616-
yield x, y
1617-
elif discrim > 0.0:
1618-
# The definition of "sign" here is different from
1619-
# np.sign: we never want to get 0.0
1620-
if dy < 0.0:
1621-
sign_dy = -1.0
1622-
else:
1623-
sign_dy = 1.0
1609+
if discrim >= 0.0:
1610+
sign_dy = np.copysign(1, dy) # +/-1, never 0.
16241611
sqrt_discrim = np.sqrt(discrim)
1625-
for sign in (1., -1.):
1626-
x = (D * dy + sign * sign_dy * dx * sqrt_discrim) / dr2
1627-
y = (-D * dx + sign * np.abs(dy) * sqrt_discrim) / dr2
1628-
yield x, y
1612+
return np.array(
1613+
[[(D * dy + sign_dy * dx * sqrt_discrim) / dr2,
1614+
(-D * dx + abs(dy) * sqrt_discrim) / dr2],
1615+
[(D * dy - sign_dy * dx * sqrt_discrim) / dr2,
1616+
(-D * dx - abs(dy) * sqrt_discrim) / dr2]])
1617+
else:
1618+
return np.empty((0, 2))
16291619

1630-
def iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
1620+
def segment_circle_intersect(x0, y0, x1, y1):
16311621
epsilon = 1e-9
16321622
if x1 < x0:
16331623
x0e, x1e = x1, x0
@@ -1637,17 +1627,13 @@ def iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
16371627
y0e, y1e = y1, y0
16381628
else:
16391629
y0e, y1e = y0, y1
1640-
x0e -= epsilon
1641-
y0e -= epsilon
1642-
x1e += epsilon
1643-
y1e += epsilon
1644-
for x, y in iter_circle_intersect_on_line(x0, y0, x1, y1):
1645-
if x0e <= x <= x1e and y0e <= y <= y1e:
1646-
yield x, y
1630+
xys = line_circle_intersect(x0, y0, x1, y1)
1631+
xs, ys = xys.T
1632+
return xys[(x0e - epsilon < xs) & (xs < x1e + epsilon)
1633+
& (y0e - epsilon < ys) & (ys < y1e + epsilon)]
16471634

16481635
# Transforms the axes box_path so that it is relative to the unit
1649-
# circle in the same way that it is relative to the desired
1650-
# ellipse.
1636+
# circle in the same way that it is relative to the desired ellipse.
16511637
box_path = Path.unit_rectangle()
16521638
box_path_transform = transforms.BboxTransformTo(self.axes.bbox) + \
16531639
self.get_transform().inverted()
@@ -1656,16 +1642,10 @@ def iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
16561642
thetas = set()
16571643
# For each of the point pairs, there is a line segment
16581644
for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
1659-
x0, y0 = p0
1660-
x1, y1 = p1
1661-
for x, y in iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
1662-
theta = np.arccos(x)
1663-
if y < 0:
1664-
theta = 2 * np.pi - theta
1665-
# Convert radians to angles
1666-
theta = np.rad2deg(theta)
1667-
if theta1 < theta < theta2:
1668-
thetas.add(theta)
1645+
xy = segment_circle_intersect(*p0, *p1)
1646+
x, y = xy.T
1647+
theta = np.rad2deg(np.arctan2(y, x))
1648+
thetas.update(theta[(theta1 < theta) & (theta < theta2)])
16691649
thetas = sorted(thetas) + [theta2]
16701650

16711651
last_theta = theta1
Lines changed: 40 additions & 0 deletions
Loading

lib/matplotlib/tests/test_patches.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,11 @@ def test_fancyarrow_units():
478478
fig, ax = plt.subplots()
479479
arrow = FancyArrowPatch((0, dtime), (0.01, dtime))
480480
ax.add_patch(arrow)
481+
482+
483+
@image_comparison(["large_arc.svg"], style="mpl20")
484+
def test_large_arc():
485+
ax = plt.figure().add_subplot()
486+
ax.set_axis_off()
487+
# A large arc that crosses the axes view limits.
488+
ax.add_patch(mpatches.Arc((-100, 0), 201, 201))

0 commit comments

Comments
 (0)