Skip to content

Commit bac9b74

Browse files
committed
Use an 8-spline approximation of an ellipse instead of a 4-spline one.
svn path=/trunk/matplotlib/; revision=4679
1 parent 0fe4b50 commit bac9b74

File tree

3 files changed

+160
-41
lines changed

3 files changed

+160
-41
lines changed

lib/matplotlib/backends/backend_ps.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,11 +1644,15 @@ class FigureManagerPS(FigureManagerBase):
16441644
} bind def""",
16451645
"""/unitcircle {
16461646
newpath
1647-
-1. 0. moveto
1648-
-1.0 0.552284749831 -0.552284749831 1.0 0.0 1.0 curveto
1649-
0.552284749831 1.0 1.0 0.552284749831 1.0 0.0 curveto
1650-
1.0 -0.552284749831 0.552284749831 -1.0 0.0 -1.0 curveto
1651-
-0.552284749831 -1.0 -1.0 -0.552284749831 -1.0 0.0 curveto
1647+
0. -1. moveto
1648+
0.2652031 -1.0 0.519579870785 -0.894633691588 0.707106781187 -0.707106781187 curveto
1649+
0.894633691588 -0.519579870785 1.0 -0.2652031 1.0 0.0 curveto
1650+
1.0 0.2652031 0.894633691588 0.519579870785 0.707106781187 0.707106781187 curveto
1651+
0.519579870785 0.894633691588 0.2652031 1.0 0.0 1.0 curveto
1652+
-0.2652031 1.0 -0.519579870785 0.894633691588 -0.707106781187 0.707106781187 curveto
1653+
-0.894633691588 0.519579870785 -1.0 0.2652031 -1.0 0.0 curveto
1654+
-1.0 -0.2652031 -0.894633691588 -0.519579870785 -0.707106781187 -0.707106781187 curveto
1655+
-0.519579870785 -0.894633691588 -0.2652031 -1.0 0.0 -1.0 curveto
16521656
closepath
16531657
} bind def""",
16541658

lib/matplotlib/patches.py

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def draw(self, renderer):
201201

202202
if cbook.is_string_like(self._edgecolor) and self._edgecolor.lower()=='none':
203203
gc.set_linewidth(0)
204-
else:
204+
else:
205205
gc.set_foreground(self._edgecolor)
206206
gc.set_linewidth(self._linewidth)
207207

@@ -764,32 +764,46 @@ class Ellipse(Patch):
764764
"""
765765
A scale-free ellipse
766766
"""
767-
offset = 4.0 * (npy.sqrt(2) - 1) / 3.0
767+
MAGIC = 0.2652031
768+
SQRT2 = npy.sqrt(0.5)
769+
MAGIC45 = npy.sqrt((MAGIC*MAGIC) / 2.0)
770+
771+
circle = npy.array(
772+
[[0.0, -1.0],
773+
774+
[MAGIC, -1.0],
775+
[SQRT2-MAGIC45, -SQRT2-MAGIC45],
776+
[SQRT2, -SQRT2],
768777

769-
circle = npy.array([
770-
[-1.0, 0.0],
778+
[SQRT2+MAGIC45, -SQRT2+MAGIC45],
779+
[1.0, -MAGIC],
780+
[1.0, 0.0],
771781

772-
[-1.0, offset],
773-
[-offset, 1.0],
774-
[0.0, 1.0],
782+
[1.0, MAGIC],
783+
[SQRT2+MAGIC45, SQRT2-MAGIC45],
784+
[SQRT2, SQRT2],
775785

776-
[offset, 1.0],
777-
[1.0, offset],
778-
[1.0, 0.0],
786+
[SQRT2-MAGIC45, SQRT2+MAGIC45],
787+
[MAGIC, 1.0],
788+
[0.0, 1.0],
779789

780-
[1.0, -offset],
781-
[offset, -1.0],
782-
[0.0, -1.0],
790+
[-MAGIC, 1.0],
791+
[-SQRT2+MAGIC45, SQRT2+MAGIC45],
792+
[-SQRT2, SQRT2],
783793

784-
[-offset, -1.0],
785-
[-1.0, -offset],
786-
[-1.0, 0.0],
794+
[-SQRT2-MAGIC45, SQRT2-MAGIC45],
795+
[-1.0, MAGIC],
796+
[-1.0, 0.0],
787797

788-
[-1.0, 0.0]
789-
],
790-
npy.float_)
798+
[-1.0, -MAGIC],
799+
[-SQRT2-MAGIC45, -SQRT2+MAGIC45],
800+
[-SQRT2, -SQRT2],
801+
802+
[-SQRT2+MAGIC45, -SQRT2-MAGIC45],
803+
[-MAGIC, -1.0],
804+
[0.0, -1.0]],
805+
npy.float_)
791806

792-
793807
def __str__(self):
794808
return "Ellipse(%d,%d;%dx%d)"%(self.center[0],self.center[1],self.width,self.height)
795809

@@ -823,9 +837,9 @@ def get_verts(self):
823837
width, height = self.width, self.height
824838

825839
xcenter = self.convert_xunits(xcenter)
826-
width = self.convert_xunits(width)
840+
width = self.convert_xunits(width)
827841
ycenter = self.convert_yunits(ycenter)
828-
height = self.convert_xunits(height)
842+
height = self.convert_xunits(height)
829843

830844

831845

@@ -871,7 +885,7 @@ def draw(self, renderer):
871885
mpl.verbose.report('patches.Ellipse renderer does not support path drawing; falling back on vertex approximation for nonlinear transformation')
872886
renderer.draw_polygon(gc, rgbFace, self.get_verts())
873887
return
874-
888+
875889

876890
x, y = self.center
877891
x = self.convert_xunits(x)
@@ -887,14 +901,14 @@ def draw(self, renderer):
887901

888902

889903

890-
904+
891905
S = npy.array([
892906
[w, 0, 0],
893907
[0, h, 0],
894908
[0, 0, 1]])
895909

896910

897-
911+
898912
# rotate by theta
899913
R = npy.array([
900914
[npy.cos(theta), -npy.sin(theta), 0],
@@ -903,7 +917,7 @@ def draw(self, renderer):
903917

904918
# transform unit circle into ellipse
905919
E = npy.dot(T, npy.dot(R, S))
906-
920+
907921

908922
# Apply the display affine
909923
sx, b, c, sy, tx, ty = self.get_transform().as_vec6_val()
@@ -918,24 +932,18 @@ def draw(self, renderer):
918932

919933
C = npy.ones((3, len(self.circle)))
920934
C[0:2,:] = self.circle.T
921-
935+
922936
ellipse = npy.dot(M, C).T[:,:2]
923937

924938
path = agg.path_storage()
925939
path.move_to(*ellipse[0])
926-
verts = ellipse[1:4].flat
927-
path.curve4(*verts)
928-
verts = ellipse[4:7].flat
929-
path.curve4(*verts)
930-
verts = ellipse[7:10].flat
931-
path.curve4(*verts)
932-
verts = ellipse[10:13].flat
933-
path.curve4(*verts)
940+
for i in range(1, 25, 3):
941+
path.curve4(*ellipse[i:i+3].flat)
934942
path.close_polygon()
935943

936944
renderer.draw_path(gc, rgbFace, path)
937945

938-
946+
939947

940948
class Circle(Ellipse):
941949
"""

unit/ellipse_large.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
# This example can be boiled down to a more simplistic example
3+
# to show the problem, but bu including the upper and lower
4+
# bound ellipses, it demonstrates how significant this error
5+
# is to our plots.
6+
7+
import math
8+
from pylab import *
9+
from matplotlib.patches import Ellipse
10+
11+
# given a point x, y
12+
x = 2692.440
13+
y = 6720.850
14+
15+
# get is the radius of a circle through this point
16+
r = math.sqrt( x*x+y*y )
17+
18+
# show some comparative circles
19+
delta = 6
20+
21+
22+
##################################################
23+
def custom_ellipse( ax, x, y, major, minor, theta, numpoints = 750, **kwargs ):
24+
xs = []
25+
ys = []
26+
incr = 2.0*math.pi / numpoints
27+
incrTheta = 0.0
28+
while incrTheta <= (2.0*math.pi):
29+
a = major * math.cos( incrTheta )
30+
b = minor * math.sin( incrTheta )
31+
l = math.sqrt( ( a**2 ) + ( b**2 ) )
32+
phi = math.atan2( b, a )
33+
incrTheta += incr
34+
35+
xs.append( x + ( l * math.cos( theta + phi ) ) )
36+
ys.append( y + ( l * math.sin( theta + phi ) ) )
37+
# end while
38+
39+
incrTheta = 2.0*math.pi
40+
a = major * math.cos( incrTheta )
41+
b = minor * math.sin( incrTheta )
42+
l = sqrt( ( a**2 ) + ( b**2 ) )
43+
phi = math.atan2( b, a )
44+
xs.append( x + ( l * math.cos( theta + phi ) ) )
45+
ys.append( y + ( l * math.sin( theta + phi ) ) )
46+
47+
ellipseLine = ax.plot( xs, ys, **kwargs )
48+
49+
50+
##################################################
51+
# make the axes
52+
ax = subplot( 211, aspect='equal' )
53+
ax.set_aspect( 'equal', 'datalim' )
54+
55+
# make the lower-bound ellipse
56+
diam = (r - delta) * 2.0
57+
lower_ellipse = Ellipse( (0.0, 0.0), diam, diam, 0.0, fill=False, edgecolor="darkgreen" )
58+
ax.add_patch( lower_ellipse )
59+
60+
# make the target ellipse
61+
diam = r * 2.0
62+
target_ellipse = Ellipse( (0.0, 0.0), diam, diam, 0.0, fill=False, edgecolor="darkred" )
63+
ax.add_patch( target_ellipse )
64+
65+
# make the upper-bound ellipse
66+
diam = (r + delta) * 2.0
67+
upper_ellipse = Ellipse( (0.0, 0.0), diam, diam, 0.0, fill=False, edgecolor="darkblue" )
68+
ax.add_patch( upper_ellipse )
69+
70+
# make the target
71+
diam = delta * 2.0
72+
target = Ellipse( (x, y), diam, diam, 0.0, fill=False, edgecolor="#DD1208" )
73+
ax.add_patch( target )
74+
75+
# give it a big marker
76+
ax.plot( [x], [y], marker='x', linestyle='None', mfc='red', mec='red', markersize=10 )
77+
78+
##################################################
79+
# now lets do the same thing again using a custom ellipse function
80+
81+
# make the axes
82+
ax = subplot( 212, aspect='equal', sharex=ax, sharey=ax )
83+
ax.set_aspect( 'equal', 'datalim' )
84+
85+
# make the lower-bound ellipse
86+
custom_ellipse( ax, 0.0, 0.0, r-delta, r-delta, 0.0, color="darkgreen" )
87+
88+
# make the target ellipse
89+
custom_ellipse( ax, 0.0, 0.0, r, r, 0.0, color="darkred" )
90+
91+
# make the upper-bound ellipse
92+
custom_ellipse( ax, 0.0, 0.0, r+delta, r+delta, 0.0, color="darkblue" )
93+
94+
# make the target
95+
custom_ellipse( ax, x, y, delta, delta, 0.0, color="#BB1208" )
96+
97+
# give it a big marker
98+
ax.plot( [x], [y], marker='x', linestyle='None', mfc='red', mec='red', markersize=10 )
99+
100+
##################################################
101+
# lets zoom in to see the area of interest
102+
103+
ax.set_xlim(2650, 2735)
104+
ax.set_ylim(6705, 6735)
105+
show()
106+
107+
savefig("ellipse")

0 commit comments

Comments
 (0)