Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/ellipse.py
4036 views
1
"""
2
Ellipses
3
"""
4
#*****************************************************************************
5
# Copyright (C) 2010 Vincent Delecroix <[email protected]>
6
#
7
# Distributed under the terms of the GNU General Public License (GPL)
8
#
9
# This code is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
# General Public License for more details.
13
#
14
# The full text of the GPL is available at:
15
#
16
# http://www.gnu.org/licenses/
17
#*****************************************************************************
18
from primitive import GraphicPrimitive
19
from sage.plot.misc import options, rename_keyword
20
from sage.plot.colors import to_mpl_color
21
from math import sin, cos, sqrt, pi, fmod
22
23
class Ellipse(GraphicPrimitive):
24
"""
25
Primitive class for the ``Ellipse`` graphics type. See ``ellipse?`` for
26
information about actually plotting ellipses.
27
28
INPUT:
29
30
- ``x,y`` - coordinates of the center of the ellipse
31
32
- ``r1, r2`` - radii of the ellipse
33
34
- ``angle`` - angle
35
36
- ``options`` - dictionary of options
37
38
EXAMPLES:
39
40
Note that this construction should be done using ``ellipse``::
41
42
sage: from sage.plot.ellipse import Ellipse
43
sage: Ellipse(0, 0, 2, 1, pi/4, {})
44
Ellipse centered at (0.0, 0.0) with radii (2.0, 1.0) and angle 0.785398163397
45
"""
46
def __init__(self, x, y, r1, r2, angle, options):
47
"""
48
Initializes base class ``Ellipse``.
49
50
TESTS::
51
52
sage: from sage.plot.ellipse import Ellipse
53
sage: e = Ellipse(0, 0, 1, 1, 0, {})
54
sage: print loads(dumps(e))
55
Ellipse centered at (0.0, 0.0) with radii (1.0, 1.0) and angle 0.0
56
sage: ellipse((0,0),0,1)
57
Traceback (most recent call last):
58
...
59
ValueError: both radii must be positive
60
"""
61
self.x = float(x)
62
self.y = float(y)
63
self.r1 = float(r1)
64
self.r2 = float(r2)
65
if self.r1 <= 0 or self.r2 <= 0:
66
raise ValueError, "both radii must be positive"
67
self.angle = fmod(angle,2*pi)
68
if self.angle < 0: self.angle += 2*pi
69
GraphicPrimitive.__init__(self, options)
70
71
def get_minmax_data(self):
72
"""
73
Returns a dictionary with the bounding box data.
74
75
The bounding box is computed to be as minimal as possible.
76
77
EXAMPLES:
78
79
An example without an angle::
80
81
sage: p = ellipse((-2, 3), 1, 2)
82
sage: d = p.get_minmax_data()
83
sage: d['xmin']
84
-3.0
85
sage: d['xmax']
86
-1.0
87
sage: d['ymin']
88
1.0
89
sage: d['ymax']
90
5.0
91
92
The same example with a rotation of angle `\pi/2`::
93
94
sage: p = ellipse((-2, 3), 1, 2, pi/2)
95
sage: d = p.get_minmax_data()
96
sage: d['xmin']
97
-4.0
98
sage: d['xmax']
99
0.0
100
sage: d['ymin']
101
2.0
102
sage: d['ymax']
103
4.0
104
"""
105
from sage.plot.plot import minmax_data
106
107
epsilon = 0.000001
108
cos_angle = cos(self.angle)
109
110
if abs(cos_angle) > 1-epsilon:
111
xmax = self.r1
112
ymax = self.r2
113
elif abs(cos_angle) < epsilon:
114
xmax = self.r2
115
ymax = self.r1
116
else:
117
sin_angle = sin(self.angle)
118
tan_angle = sin_angle / cos_angle
119
sxmax = ((self.r2*tan_angle)/self.r1)**2
120
symax = (self.r2/(self.r1*tan_angle))**2
121
xmax = (
122
abs(self.r1 * cos_angle / sqrt(sxmax+1.)) +
123
abs(self.r2 * sin_angle / sqrt(1./sxmax+1.)))
124
ymax = (
125
abs(self.r1 * sin_angle / sqrt(symax+1.)) +
126
abs(self.r2 * cos_angle / sqrt(1./symax+1.)))
127
128
return minmax_data([self.x - xmax, self.x + xmax],
129
[self.y - ymax, self.y + ymax],
130
dict=True)
131
132
def _allowed_options(self):
133
"""
134
Return the allowed options for the ``Ellipse`` class.
135
136
EXAMPLES::
137
138
sage: p = ellipse((3, 3), 2, 1)
139
sage: p[0]._allowed_options()['alpha']
140
'How transparent the figure is.'
141
sage: p[0]._allowed_options()['facecolor']
142
'2D only: The color of the face as an RGB tuple.'
143
"""
144
return {'alpha':'How transparent the figure is.',
145
'fill': 'Whether or not to fill the ellipse.',
146
'legend_label':'The label for this item in the legend.',
147
'thickness':'How thick the border of the ellipse is.',
148
'edgecolor':'2D only: The color of the edge as an RGB tuple.',
149
'facecolor':'2D only: The color of the face as an RGB tuple.',
150
'rgbcolor':'The color (edge and face) as an RGB tuple.',
151
'hue':'The color given as a hue.',
152
'zorder':'2D only: The layer level in which to draw',
153
'linestyle':"2D only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."}
154
155
def _repr_(self):
156
"""
157
String representation of ``Ellipse`` primitive.
158
159
TESTS::
160
161
sage: from sage.plot.ellipse import Ellipse
162
sage: Ellipse(0,0,2,1,0,{})._repr_()
163
'Ellipse centered at (0.0, 0.0) with radii (2.0, 1.0) and angle 0.0'
164
"""
165
return "Ellipse centered at (%s, %s) with radii (%s, %s) and angle %s"%(self.x, self.y, self.r1, self.r2, self.angle)
166
167
def _render_on_subplot(self, subplot):
168
"""
169
Render this ellipse in a subplot. This is the key function that
170
defines how this ellipse graphics primitive is rendered in matplotlib's
171
library.
172
173
TESTS::
174
175
sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3)
176
177
::
178
179
sage: ellipse((3,2),1,2)
180
"""
181
import matplotlib.patches as patches
182
options = self.options()
183
p = patches.Ellipse(
184
(self.x,self.y),
185
self.r1*2.,self.r2*2.,self.angle/pi*180.)
186
p.set_linewidth(float(options['thickness']))
187
p.set_fill(options['fill'])
188
a = float(options['alpha'])
189
p.set_alpha(a)
190
ec = to_mpl_color(options['edgecolor'])
191
fc = to_mpl_color(options['facecolor'])
192
if 'rgbcolor' in options:
193
ec = fc = to_mpl_color(options['rgbcolor'])
194
p.set_edgecolor(ec)
195
p.set_facecolor(fc)
196
p.set_linestyle(options['linestyle'])
197
p.set_label(options['legend_label'])
198
z = int(options.pop('zorder', 0))
199
p.set_zorder(z)
200
subplot.add_patch(p)
201
202
def plot3d(self):
203
r"""
204
Plotting in 3D is not implemented.
205
206
TESTS::
207
208
sage: from sage.plot.ellipse import Ellipse
209
sage: Ellipse(0,0,2,1,pi/4,{}).plot3d()
210
Traceback (most recent call last):
211
...
212
NotImplementedError
213
"""
214
raise NotImplementedError
215
216
@rename_keyword(color='rgbcolor')
217
@options(alpha=1, fill=False, thickness=1, edgecolor='blue', facecolor='blue', linestyle='solid', zorder=5,
218
aspect_ratio=1.0, legend_label=None)
219
def ellipse(center, r1, r2, angle=0, **options):
220
"""
221
Return an ellipse centered at a point center = ``(x,y)`` with radii =
222
``r1,r2`` and angle ``angle``. Type ``ellipse.options`` to see all
223
options.
224
225
INPUT:
226
227
- ``center`` - 2-tuple of real numbers - coordinates of the center
228
229
- ``r1``, ``r2`` - positive real numbers - the radii of the ellipse
230
231
- ``angle`` - real number (default: 0) - the angle between the first axis
232
and the horizontal
233
234
OPTIONS:
235
236
- ``alpha`` - default: 1 - transparency
237
238
- ``fill`` - default: False - whether to fill the ellipse or not
239
240
- ``thickness`` - default: 1 - thickness of the line
241
242
- ``linestyle`` - default: 'solid'
243
244
- ``edgecolor`` - default: 'black' - color of the contour
245
246
- ``facecolor`` - default: 'red' - color of the filling
247
248
- ``rgbcolor`` - 2D or 3D plotting. This option overrides
249
``edgecolor`` and ``facecolor`` for 2D plotting.
250
251
EXAMPLES:
252
253
An ellipse centered at (0,0) with major and minor axes of lengths 2 and 1.
254
Note that the default color is blue::
255
256
sage: ellipse((0,0),2,1)
257
258
More complicated examples with tilted axes and drawing options::
259
260
sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle="dashed")
261
262
::
263
264
sage: ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red')
265
266
We see that ``rgbcolor`` overrides these other options, as this plot
267
is green::
268
269
sage: ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red',rgbcolor='green')
270
271
The default aspect ratio for ellipses is 1.0::
272
273
sage: ellipse((0,0),2,1).aspect_ratio()
274
1.0
275
276
One cannot yet plot ellipses in 3D::
277
278
sage: ellipse((0,0,0),2,1)
279
Traceback (most recent call last):
280
...
281
NotImplementedError: plotting ellipse in 3D is not implemented
282
"""
283
from sage.plot.all import Graphics
284
g = Graphics()
285
g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
286
g.add_primitive(Ellipse(center[0],center[1],r1,r2,angle,options))
287
if options['legend_label']:
288
g.legend(True)
289
if len(center)==2:
290
return g
291
elif len(center)==3:
292
raise NotImplementedError, "plotting ellipse in 3D is not implemented"
293
294