Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitffdd0cf

Browse files
committed
FIX: update arc to do better rounding and allow wrap
1 parent492a478 commitffdd0cf

File tree

4 files changed

+93
-31
lines changed

4 files changed

+93
-31
lines changed

‎lib/matplotlib/path.py

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -949,36 +949,70 @@ def unit_circle_righthalf(cls):
949949
returncls._unit_circle_righthalf
950950

951951
@classmethod
952-
defarc(cls,theta1,theta2,n=None,is_wedge=False):
952+
defarc(cls,theta1,theta2,n=None,is_wedge=False,wrap=True):
953953
"""
954954
Return a `Path` for the unit circle arc from angles *theta1* to
955955
*theta2* (in degrees).
956956
957-
*theta2* is unwrapped to produce the shortest arc within 360 degrees.
958-
That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to
959-
*theta2* - 360 and not a full circle plus some extra overlap.
960-
961-
If *n* is provided, it is the number of spline segments to make.
962-
If *n* is not provided, the number of spline segments is
963-
determined based on the delta between *theta1* and *theta2*.
957+
Parameters
958+
----------
959+
theta1, theta2 : float
960+
The angles (in degrees) of the start and end of the arc. If the
961+
arc is greater than 360 degrees, then the arc will be wrapped to
962+
fit between theta1 and theta1 + 360, if *wrap* is True.
963+
964+
n : int, optional
965+
The number of spline segments to make. If not provided, the number
966+
of spline segments is determined based on the delta between
967+
*theta1* and *theta2*.
968+
969+
is_wedge : bool, default: False
970+
If True, the arc is a wedge. The first vertex is the center of the
971+
wedge, the second vertex is the start of the arc, and the last
972+
vertex is the end of the arc. The wedge is closed with a line
973+
segment to the center of the wedge. If False, the arc is a
974+
polyline. The first vertex is the start of the arc, and the last
975+
vertex is the end of the arc. The arc is closed with a line
976+
segment to the start of the arc. The wedge is not closed with a
977+
line segment to the start of the arc.
978+
979+
wrap : bool, default: True
980+
If True, the arc is wrapped to fit between *theta1* and *theta1* +
981+
360 degrees. If False, the arc is not wrapped. The arc will be
982+
drawn from *theta1* to *theta2*.
964983
965-
Masionobe, L. 2003. `Drawing an elliptical arc using
966-
polylines, quadratic or cubic Bezier curves
984+
Notes
985+
-----
986+
The arc is approximated using cubic Bézier curves, as described in
987+
Masionobe, L. 2003. `Drawing an elliptical arc using polylines,
988+
quadratic or cubic Bezier curves
967989
<https://web.archive.org/web/20190318044212/http://www.spaceroots.org/documents/ellipse/index.html>`_.
968990
"""
969-
halfpi=np.pi*0.5
970991

971992
eta1=theta1
972-
eta2=theta2-360*np.floor((theta2-theta1)/360)
973-
# Ensure 2pi range is not flattened to 0 due to floating-point errors,
974-
# but don't try to expand existing 0 range.
975-
iftheta2!=theta1andeta2<=eta1:
976-
eta2+=360
993+
ifwrap:
994+
# Wrap theta2 to 0-360 degrees from theta1.
995+
eta2=np.mod(theta2-theta1,360.0)+theta1
996+
print('Eta1, Eta20',eta1,eta2)
997+
# Ensure 360-deg range is not flattened to 0 due to floating-point
998+
# errors, but don't try to expand existing 0 range.
999+
iftheta2!=theta1andeta2<=eta1:
1000+
eta2+=360
1001+
print('Eta1, Eta2',eta1,eta2)
1002+
else:
1003+
eta2=theta2
9771004
eta1,eta2=np.deg2rad([eta1,eta2])
9781005

9791006
# number of curve segments to make
9801007
ifnisNone:
981-
n=int(2**np.ceil((eta2-eta1)/halfpi))
1008+
ifnp.abs(eta2-eta1)<=2.2*np.pi:
1009+
# this doesn't need to grow exponentially, but we have left
1010+
# this way for back compatibility
1011+
n=int(2**np.ceil(2*np.abs(eta2-eta1)/np.pi))
1012+
print('Here')
1013+
else:
1014+
# this will not grow exponentially if we allow wrapping arcs:
1015+
n=int(2*np.ceil(2*np.abs(eta2-eta1)/np.pi))
9821016
ifn<1:
9831017
raiseValueError("n must be >= 1 or None")
9841018

@@ -1028,22 +1062,29 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False):
10281062
returncls(vertices,codes,readonly=True)
10291063

10301064
@classmethod
1031-
defwedge(cls,theta1,theta2,n=None):
1065+
defwedge(cls,theta1,theta2,n=None,wrap=True):
10321066
"""
10331067
Return a `Path` for the unit circle wedge from angles *theta1* to
10341068
*theta2* (in degrees).
10351069
1036-
*theta2* is unwrapped to produce the shortest wedge within 360 degrees.
1037-
That is, if *theta2* > *theta1* + 360, the wedge will be from *theta1*
1038-
to *theta2* - 360 and not a full circle plus some extra overlap.
1039-
1040-
If *n* is provided, it is the number of spline segments to make.
1041-
If *n* is not provided, the number of spline segments is
1042-
determined based on the delta between *theta1* and *theta2*.
1043-
1044-
See `Path.arc` for the reference on the approximation used.
1045-
"""
1046-
returncls.arc(theta1,theta2,n,True)
1070+
Parameters
1071+
----------
1072+
theta1, theta2 : float
1073+
The angles (in degrees) of the start and end of the arc. If the
1074+
arc is greater than 360 degrees, then the arc will be wrapped to
1075+
fit between theta1 and theta1 + 360, if *wrap* is True.
1076+
1077+
n : int, optional
1078+
The number of spline segments to make. If not provided, the number
1079+
of spline segments is determined based on the delta between
1080+
*theta1* and *theta2*.
1081+
1082+
wrap : bool, default: True
1083+
If True, the arc is wrapped to fit between *theta1* and *theta1* +
1084+
360 degrees. If False, the arc is not wrapped. The arc will be
1085+
drawn from *theta1* to *theta2*.
1086+
"""
1087+
returncls.arc(theta1,theta2,n,wedge=True,wrap=wrap)
10471088

10481089
@staticmethod
10491090
@lru_cache(8)

‎lib/matplotlib/path.pyi

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,14 @@ class Path:
119119
defunit_circle_righthalf(cls)->Path: ...
120120
@classmethod
121121
defarc(
122-
cls,theta1:float,theta2:float,n:int|None= ...,is_wedge:bool= ...
122+
cls,theta1:float,theta2:float,n:int|None= ...,
123+
is_wedge:bool= ...,wrap:bool= ...
123124
)->Path: ...
124125
@classmethod
125-
defwedge(cls,theta1:float,theta2:float,n:int|None= ...)->Path: ...
126+
defwedge(
127+
cls,theta1:float,theta2:float,n:int|None= ...,
128+
wrap:bool= ...
129+
)->Path: ...
126130
@overload
127131
@staticmethod
128132
defhatch(hatchpattern:str,density:float= ...)->Path: ...

‎lib/matplotlib/tests/test_path.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,12 +471,29 @@ def test_full_arc(offset):
471471
high=360+offset
472472

473473
path=Path.arc(low,high)
474+
print(path.vertices)
474475
mins=np.min(path.vertices,axis=0)
475476
maxs=np.max(path.vertices,axis=0)
476477
np.testing.assert_allclose(mins,-1)
477478
np.testing.assert_allclose(maxs,1)
478479

479480

481+
@image_comparison(['arc_close360'],style='default',remove_text=True,
482+
extensions=['png'])
483+
deftest_arc_close360():
484+
fig,ax=plt.subplots(ncols=3)
485+
ax[0].add_patch(patches.PathPatch(Path.arc(theta1=-90-1e-14,theta2=270)))
486+
#ax[0].set_title("arc(-90-1e-14, 270), should be a circle")
487+
ax[1].add_patch(patches.PathPatch(Path.arc(theta1=-90,theta2=270)))
488+
#ax[1].set_title("arc(-90, 270), is a circle")
489+
ax[2].add_patch(patches.PathPatch(Path.arc(theta1=-90-1e-14,theta2=-90)))
490+
#ax[2].set_title("arc(-90, -90-1e-14), should not be a circle")
491+
forainax:
492+
a.set_xlim(-1,1)
493+
a.set_ylim(-1,1)
494+
a.set_aspect("equal")
495+
496+
480497
deftest_disjoint_zero_length_segment():
481498
this_path=Path(
482499
np.array([

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp