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

Commit7a82ea8

Browse files
committed
Added (experimental) support for large arcs
svn path=/branches/transforms/; revision=4700
1 parent12cc2fc commit7a82ea8

File tree

5 files changed

+191
-18
lines changed

5 files changed

+191
-18
lines changed

‎lib/matplotlib/patches.py

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs):
839839
self._width,self._height=width,height
840840
self._angle=angle
841841
self._recompute_transform()
842+
self._path=Path.unit_circle()
842843

843844
def_recompute_transform(self):
844845
self._patch_transform=transforms.Affine2D() \
@@ -850,7 +851,7 @@ def get_path(self):
850851
"""
851852
Return the vertices of the rectangle
852853
"""
853-
returnPath.unit_circle()
854+
returnself._path
854855

855856
defget_patch_transform(self):
856857
returnself._patch_transform
@@ -881,7 +882,6 @@ def _set_angle(self, angle):
881882
self._recompute_transform()
882883
angle=property(_get_angle,_set_angle)
883884

884-
885885
classCircle(Ellipse):
886886
"""
887887
A circle patch
@@ -908,6 +908,179 @@ def __init__(self, xy, radius=5, **kwargs):
908908
Ellipse.__init__(self,xy,radius*2,radius*2,**kwargs)
909909
__init__.__doc__=cbook.dedent(__init__.__doc__)%artist.kwdocd
910910

911+
classArc(Ellipse):
912+
"""
913+
An elliptical arc. Because it performs various optimizations, it may not be
914+
filled.
915+
"""
916+
def__str__(self):
917+
return"Arc(%d,%d;%dx%d)"%(self.center[0],self.center[1],self.width,self.height)
918+
919+
def__init__(self,xy,width,height,angle=0.0,theta1=0.0,theta2=360.0,**kwargs):
920+
"""
921+
xy - center of ellipse
922+
width - length of horizontal axis
923+
height - length of vertical axis
924+
angle - rotation in degrees (anti-clockwise)
925+
theta1 - starting angle of the arc in degrees
926+
theta2 - ending angle of the arc in degrees
927+
928+
If theta1 and theta2 are not provided, the arc will form a
929+
complete ellipse.
930+
931+
Valid kwargs are:
932+
%(Patch)s
933+
"""
934+
fill=kwargs.pop('fill')
935+
iffill:
936+
raiseValueError("Arc objects can not be filled")
937+
kwargs['fill']=False
938+
939+
Ellipse.__init__(self,xy,width,height,angle,**kwargs)
940+
941+
self._theta1=theta1
942+
self._theta2=theta2
943+
944+
defdraw(self,renderer):
945+
"""
946+
Ellipses are normally drawn using an approximation that uses
947+
eight cubic bezier splines. The error of this approximation
948+
is 1.89818e-6, according to this unverified source:
949+
950+
Lancaster, Don. Approximating a Circle or an Ellipse Using
951+
Four Bezier Cubic Splines.
952+
953+
http://www.tinaja.com/glib/ellipse4.pdf
954+
955+
There is a use case where very large ellipses must be drawn
956+
with very high accuracy, and it is too expensive to render the
957+
entire ellipse with enough segments (either splines or line
958+
segments). Therefore, in the case where either radius of the
959+
ellipse is large enough that the error of the spline
960+
approximation will be visible (greater than one pixel offset
961+
from the ideal), a different technique is used.
962+
963+
In that case, only the visible parts of the ellipse are drawn,
964+
with each visible arc using a fixed number of spline segments
965+
(8). The algorithm proceeds as follows:
966+
967+
1. The points where the ellipse intersects the axes bounding
968+
box are located. (This is done be performing an inverse
969+
transformation on the axes bbox such that it is relative to
970+
the unit circle -- this makes the intersection calculation
971+
much easier than doing rotated ellipse intersection
972+
directly).
973+
974+
This uses the "line intersecting a circle" algorithm from:
975+
976+
Vince, John. Geometry for Computer Graphics: Formulae,
977+
Examples & Proofs. London: Springer-Verlag, 2005.
978+
979+
2. The angles of each of the intersection points are
980+
calculated.
981+
982+
3. Proceeding counterclockwise starting in the positive
983+
x-direction, each of the visible arc-segments between the
984+
pairs of vertices are drawn using the bezier arc
985+
approximation technique implemented in Path.arc().
986+
"""
987+
# Get the width and height in pixels
988+
width,height=self.get_transform().transform_point(
989+
(self._width,self._height))
990+
inv_error= (1.0/1.89818e-6)
991+
992+
ifwidth<inv_errorandheight<inv_errorandFalse:
993+
self._path=Path.arc(self._theta1,self._theta2)
994+
returnPatch.draw(self,renderer)
995+
996+
# Transforms the axes box_path so that it is relative to the unit
997+
# circle in the same way that it is relative to the desired
998+
# ellipse.
999+
box_path=Path.unit_rectangle()
1000+
box_path_transform=transforms.BboxTransformTo(self.axes.bbox)+ \
1001+
self.get_transform().inverted()
1002+
box_path=box_path.transformed(box_path_transform)
1003+
vertices= []
1004+
1005+
defiter_circle_intersect_on_line(x0,y0,x1,y1):
1006+
dx=x1-x0
1007+
dy=y1-y0
1008+
dr2=dx*dx+dy*dy
1009+
dr=npy.sqrt(dr2)
1010+
D=x0*y1-x1*y0
1011+
D2=D*D
1012+
discrim=dr2-D2
1013+
1014+
# Single (tangential) intersection
1015+
ifdiscrim==0.0:
1016+
x= (D*dy)/dr2
1017+
y= (-D*dx)/dr2
1018+
yieldx,y
1019+
elifdiscrim>0.0:
1020+
ifdy<0:
1021+
sign_dy=-1.0
1022+
else:
1023+
sign_dy=1.0
1024+
sqrt_discrim=npy.sqrt(discrim)
1025+
forsignin (1.,-1.):
1026+
x= (D*dy+sign*sign_dy*dx*sqrt_discrim)/dr2
1027+
y= (-D*dx+sign*npy.abs(dy)*sqrt_discrim)/dr2
1028+
yieldx,y
1029+
1030+
defiter_circle_intersect_on_line_seg(x0,y0,x1,y1):
1031+
epsilon=1e-9
1032+
ifx1<x0:
1033+
x0e,x1e=x1,x0
1034+
else:
1035+
x0e,x1e=x0,x1
1036+
ify1<y0:
1037+
y0e,y1e=y1,y0
1038+
else:
1039+
y0e,y1e=y0,y1
1040+
x0e-=epsilon
1041+
y0e-=epsilon
1042+
x1e+=epsilon
1043+
y1e+=epsilon
1044+
forx,yiniter_circle_intersect_on_line(x0,y0,x1,y1):
1045+
ifx>=x0eandx<=x1eandy>=y0eandy<=y1e:
1046+
yieldx,y
1047+
1048+
PI=npy.pi
1049+
TWOPI=PI*2.0
1050+
RAD2DEG=180.0/PI
1051+
DEG2RAD=PI/180.0
1052+
theta1=self._theta1
1053+
theta2=self._theta2
1054+
thetas= {}
1055+
# For each of the point pairs, there is a line segment
1056+
forp0,p1inzip(box_path.vertices[:-1],box_path.vertices[1:]):
1057+
x0,y0=p0
1058+
x1,y1=p1
1059+
forx,yiniter_circle_intersect_on_line_seg(x0,y0,x1,y1):
1060+
# Convert radians to angles
1061+
theta=npy.arccos(x)
1062+
ify<0:
1063+
theta=TWOPI-theta
1064+
theta*=RAD2DEG
1065+
iftheta>theta1andtheta<theta2:
1066+
thetas[theta]=None
1067+
1068+
thetas=thetas.keys()
1069+
thetas.sort()
1070+
thetas.append(theta2)
1071+
1072+
last_theta=theta1
1073+
theta1_rad=theta1*DEG2RAD
1074+
inside=box_path.contains_point((npy.cos(theta1_rad),npy.sin(theta1_rad)))
1075+
1076+
forthetainthetas:
1077+
ifinside:
1078+
self._path=Path.arc(last_theta,theta,8)
1079+
Patch.draw(self,renderer)
1080+
inside=False
1081+
else:
1082+
inside=True
1083+
last_theta=theta
9111084

9121085
defbbox_artist(artist,renderer,props=None,fill=True):
9131086
"""

‎lib/matplotlib/path.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ def unit_circle(cls):
408408
unit_circle=classmethod(unit_circle)
409409

410410
#@classmethod
411-
defarc(cls,theta1,theta2,is_wedge=False,n=None):
411+
defarc(cls,theta1,theta2,n=None,is_wedge=False):
412412
"""
413413
Returns an arc on the unit circle from angle theta1 to angle
414414
theta2 (in degrees).
@@ -486,12 +486,12 @@ def arc(cls, theta1, theta2, is_wedge=False, n=None):
486486
arc=classmethod(arc)
487487

488488
#@classmethod
489-
defwedge(cls,theta1,theta2):
489+
defwedge(cls,theta1,theta2,n=None):
490490
"""
491491
Returns a wedge of the unit circle from angle theta1 to angle
492492
theta2 (in degrees).
493493
"""
494-
returncls.arc(theta1,theta2,True)
494+
returncls.arc(theta1,theta2,True,n)
495495
wedge=classmethod(wedge)
496496

497497
_get_path_collection_extents=get_path_collection_extents

‎src/_path.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class _path_module : public Py::ExtensionModule<_path_module>
109109
// Input 2D polygon _pgon_ with _numverts_ number of vertices and test point
110110
// _point_, returns 1 if inside, 0 if outside.
111111
template<classT>
112-
boolpoint_in_path_impl(double tx,double ty, T& path)
112+
boolpoint_in_path_impl(constdouble tx,constdouble ty, T& path)
113113
{
114114
int yflag0, yflag1, inside_flag;
115115
double vtx0, vty0, vtx1, vty1, sx, sy;
@@ -132,7 +132,7 @@ bool point_in_path_impl(double tx, double ty, T& path)
132132
yflag0 = (vty0 >= ty);
133133

134134
vtx1 = x;
135-
vty1 =x;
135+
vty1 =y;
136136

137137
inside_flag =0;
138138
do
@@ -141,7 +141,7 @@ bool point_in_path_impl(double tx, double ty, T& path)
141141

142142
// The following cases denote the beginning on a new subpath
143143
if (code == agg::path_cmd_stop ||
144-
(code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly)
144+
(code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly)
145145
{
146146
x = sx;
147147
y = sy;
@@ -169,7 +169,7 @@ bool point_in_path_impl(double tx, double ty, T& path)
169169
// by Joseph Samosky's and Mark Haigh-Hutchinson's different
170170
// polygon inclusion tests.
171171
if ( ((vty1-ty) * (vtx0-vtx1) >=
172-
(vtx1-tx) * (vty0-vty1)) == yflag1 )
172+
(vtx1-tx) * (vty0-vty1)) == yflag1 )
173173
{
174174
inside_flag ^=1;
175175
}
@@ -184,7 +184,7 @@ bool point_in_path_impl(double tx, double ty, T& path)
184184
vty1 = y;
185185
}
186186
while (code != agg::path_cmd_stop &&
187-
(code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly);
187+
(code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly);
188188

189189
yflag1 = (vty1 >= ty);
190190
if (yflag0 != yflag1)

‎unit/ellipse_compare.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Compare the ellipse generated with arcs versus a polygonal approximation
2+
Compare the ellipse generated with arcs versus a polygonal approximation
33
"""
44
importnumpyasnpy
55
frommatplotlibimportpatches
@@ -29,7 +29,7 @@
2929
ax=fig.add_subplot(211,aspect='auto')
3030
ax.fill(x,y,alpha=0.2,facecolor='yellow',edgecolor='yellow',linewidth=1,zorder=1)
3131

32-
e1=patches.Ellipse((xcenter,ycenter),width,height,
32+
e1=patches.Arc((xcenter,ycenter),width,height,
3333
angle=angle,linewidth=2,fill=False,zorder=2)
3434

3535
ax.add_patch(e1)

‎unit/ellipse_large.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
importmath
88
frompylabimport*
9-
frommatplotlib.patchesimportEllipse
9+
frommatplotlib.patchesimportArc
1010

1111
# given a point x, y
1212
x=2692.440
@@ -54,22 +54,22 @@ def custom_ellipse( ax, x, y, major, minor, theta, numpoints = 750, **kwargs ):
5454

5555
# make the lower-bound ellipse
5656
diam= (r-delta)*2.0
57-
lower_ellipse=Ellipse( (0.0,0.0),diam,diam,0.0,fill=False,edgecolor="darkgreen" )
57+
lower_ellipse=Arc( (0.0,0.0),diam,diam,0.0,fill=False,edgecolor="darkgreen" )
5858
ax.add_patch(lower_ellipse )
5959

6060
# make the target ellipse
6161
diam=r*2.0
62-
target_ellipse=Ellipse( (0.0,0.0),diam,diam,0.0,fill=False,edgecolor="darkred" )
62+
target_ellipse=Arc( (0.0,0.0),diam,diam,0.0,fill=False,edgecolor="darkred" )
6363
ax.add_patch(target_ellipse )
6464

6565
# make the upper-bound ellipse
6666
diam= (r+delta)*2.0
67-
upper_ellipse=Ellipse( (0.0,0.0),diam,diam,0.0,fill=False,edgecolor="darkblue" )
67+
upper_ellipse=Arc( (0.0,0.0),diam,diam,0.0,fill=False,edgecolor="darkblue" )
6868
ax.add_patch(upper_ellipse )
6969

7070
# make the target
7171
diam=delta*2.0
72-
target=Ellipse( (x,y),diam,diam,0.0,fill=False,edgecolor="#DD1208" )
72+
target=Arc( (x,y),diam,diam,0.0,fill=False,edgecolor="#DD1208" )
7373
ax.add_patch(target )
7474

7575
# give it a big marker
@@ -104,4 +104,4 @@ def custom_ellipse( ax, x, y, major, minor, theta, numpoints = 750, **kwargs ):
104104
ax.set_ylim(6705,6735)
105105
show()
106106

107-
savefig("ellipse")
107+
#savefig("ellipse")

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp