Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/plot/ellipse.py
8815 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
'legend_color':'The color of the legend text.',
148
'thickness':'How thick the border of the ellipse is.',
149
'edgecolor':'2D only: The color of the edge as an RGB tuple.',
150
'facecolor':'2D only: The color of the face as an RGB tuple.',
151
'rgbcolor':'The color (edge and face) as an RGB tuple.',
152
'hue':'The color given as a hue.',
153
'zorder':'2D only: The layer level in which to draw',
154
'linestyle':"2D only: The style of the line, which is one of "
155
"'dashed', 'dotted', 'solid', 'dashdot', or '--', ':', '-', '-.', "
156
"respectively."}
157
158
def _repr_(self):
159
"""
160
String representation of ``Ellipse`` primitive.
161
162
TESTS::
163
164
sage: from sage.plot.ellipse import Ellipse
165
sage: Ellipse(0,0,2,1,0,{})._repr_()
166
'Ellipse centered at (0.0, 0.0) with radii (2.0, 1.0) and angle 0.0'
167
"""
168
return "Ellipse centered at (%s, %s) with radii (%s, %s) and angle %s"%(self.x, self.y, self.r1, self.r2, self.angle)
169
170
def _render_on_subplot(self, subplot):
171
"""
172
Render this ellipse in a subplot. This is the key function that
173
defines how this ellipse graphics primitive is rendered in matplotlib's
174
library.
175
176
TESTS::
177
178
sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3)
179
180
::
181
182
sage: ellipse((3,2),1,2)
183
"""
184
import matplotlib.patches as patches
185
from sage.plot.misc import get_matplotlib_linestyle
186
187
options = self.options()
188
p = patches.Ellipse(
189
(self.x,self.y),
190
self.r1*2.,self.r2*2.,self.angle/pi*180.)
191
p.set_linewidth(float(options['thickness']))
192
p.set_fill(options['fill'])
193
a = float(options['alpha'])
194
p.set_alpha(a)
195
ec = to_mpl_color(options['edgecolor'])
196
fc = to_mpl_color(options['facecolor'])
197
if 'rgbcolor' in options:
198
ec = fc = to_mpl_color(options['rgbcolor'])
199
p.set_edgecolor(ec)
200
p.set_facecolor(fc)
201
p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long'))
202
p.set_label(options['legend_label'])
203
z = int(options.pop('zorder', 0))
204
p.set_zorder(z)
205
subplot.add_patch(p)
206
207
def plot3d(self):
208
r"""
209
Plotting in 3D is not implemented.
210
211
TESTS::
212
213
sage: from sage.plot.ellipse import Ellipse
214
sage: Ellipse(0,0,2,1,pi/4,{}).plot3d()
215
Traceback (most recent call last):
216
...
217
NotImplementedError
218
"""
219
raise NotImplementedError
220
221
@rename_keyword(color='rgbcolor')
222
@options(alpha=1, fill=False, thickness=1, edgecolor='blue', facecolor='blue', linestyle='solid', zorder=5,
223
aspect_ratio=1.0, legend_label=None, legend_color=None)
224
def ellipse(center, r1, r2, angle=0, **options):
225
"""
226
Return an ellipse centered at a point center = ``(x,y)`` with radii =
227
``r1,r2`` and angle ``angle``. Type ``ellipse.options`` to see all
228
options.
229
230
INPUT:
231
232
- ``center`` - 2-tuple of real numbers - coordinates of the center
233
234
- ``r1``, ``r2`` - positive real numbers - the radii of the ellipse
235
236
- ``angle`` - real number (default: 0) - the angle between the first axis
237
and the horizontal
238
239
OPTIONS:
240
241
- ``alpha`` - default: 1 - transparency
242
243
- ``fill`` - default: False - whether to fill the ellipse or not
244
245
- ``thickness`` - default: 1 - thickness of the line
246
247
- ``linestyle`` - default: ``'solid'`` - The style of the line, which is one
248
of ``'dashed'``, ``'dotted'``, ``'solid'``, ``'dashdot'``, or ``'--'``,
249
``':'``, ``'-'``, ``'-.'``, respectively.
250
251
- ``edgecolor`` - default: 'black' - color of the contour
252
253
- ``facecolor`` - default: 'red' - color of the filling
254
255
- ``rgbcolor`` - 2D or 3D plotting. This option overrides
256
``edgecolor`` and ``facecolor`` for 2D plotting.
257
258
- ``legend_label`` - the label for this item in the legend
259
260
- ``legend_color`` - the color for the legend label
261
262
EXAMPLES:
263
264
An ellipse centered at (0,0) with major and minor axes of lengths 2 and 1.
265
Note that the default color is blue::
266
267
sage: ellipse((0,0),2,1)
268
269
More complicated examples with tilted axes and drawing options::
270
271
sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle="dashed")
272
sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle="--")
273
274
::
275
276
sage: ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red')
277
278
We see that ``rgbcolor`` overrides these other options, as this plot
279
is green::
280
281
sage: ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red',rgbcolor='green')
282
283
The default aspect ratio for ellipses is 1.0::
284
285
sage: ellipse((0,0),2,1).aspect_ratio()
286
1.0
287
288
One cannot yet plot ellipses in 3D::
289
290
sage: ellipse((0,0,0),2,1)
291
Traceback (most recent call last):
292
...
293
NotImplementedError: plotting ellipse in 3D is not implemented
294
295
We can also give ellipses a legend::
296
297
sage: ellipse((0,0),2,1,legend_label="My ellipse", legend_color='green')
298
"""
299
from sage.plot.all import Graphics
300
g = Graphics()
301
302
# Reset aspect_ratio to 'automatic' in case scale is 'semilog[xy]'.
303
# Otherwise matplotlib complains.
304
scale = options.get('scale', None)
305
if isinstance(scale, (list, tuple)):
306
scale = scale[0]
307
if scale == 'semilogy' or scale == 'semilogx':
308
options['aspect_ratio'] = 'automatic'
309
310
g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
311
g.add_primitive(Ellipse(center[0],center[1],r1,r2,angle,options))
312
if options['legend_label']:
313
g.legend(True)
314
g._legend_colors = [options['legend_color']]
315
if len(center)==2:
316
return g
317
elif len(center)==3:
318
raise NotImplementedError, "plotting ellipse in 3D is not implemented"
319
320