Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/arc.py
4034 views
1
"""
2
Arcs of circles and 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 sage.plot.primitive import GraphicPrimitive
19
from sage.plot.colors import to_mpl_color
20
21
from sage.plot.misc import options, rename_keyword
22
23
from math import fmod, sin, cos, pi, atan
24
25
class Arc(GraphicPrimitive):
26
"""
27
Primitive class for the Arc graphics type. See ``arc?`` for information
28
about actually plotting an arc of a circle or an ellipse.
29
30
INPUT:
31
32
- ``x,y`` - coordinates of the center of the arc
33
34
- ``r1``, ``r2`` - lengths of the two radii
35
36
- ``angle`` - angle of the horizontal with width
37
38
- ``sector`` - sector of angle
39
40
- ``options`` - dict of valid plot options to pass to constructor
41
42
EXAMPLES:
43
44
Note that the construction should be done using ``arc``::
45
46
sage: from sage.plot.arc import Arc
47
sage: print Arc(0,0,1,1,pi/4,pi/4,pi/2,{})
48
Arc with center (0.0,0.0) radii (1.0,1.0) angle 0.785398163397 inside the sector (0.785398163397,1.57079632679)
49
"""
50
def __init__(self, x, y, r1, r2, angle, s1, s2, options):
51
"""
52
Initializes base class ``Arc``.
53
54
EXAMPLES:
55
56
sage: A = arc((2,3),1,1,pi/4,(0,pi))
57
sage: A[0].x == 2
58
True
59
sage: A[0].y == 3
60
True
61
sage: A[0].r1 == 1
62
True
63
sage: A[0].r2 == 1
64
True
65
sage: bool(A[0].angle == pi/4)
66
True
67
sage: bool(A[0].s1 == 0)
68
True
69
sage: bool(A[0].s2 == pi)
70
True
71
72
TESTS::
73
74
sage: from sage.plot.arc import Arc
75
sage: a = Arc(0,0,1,1,0,0,1,{})
76
sage: print loads(dumps(a))
77
Arc with center (0.0,0.0) radii (1.0,1.0) angle 0.0 inside the sector (0.0,1.0)
78
"""
79
self.x = float(x)
80
self.y = float(y)
81
self.r1 = float(r1)
82
self.r2 = float(r2)
83
if self.r1 <= 0 or self.r2 <= 0:
84
raise ValueError, "the radii must be positive real numbers."
85
86
self.angle = float(angle)
87
self.s1 = float(s1)
88
self.s2 = float(s2)
89
if self.s2 < self.s1:
90
self.s1,self.s2=self.s2,self.s1
91
GraphicPrimitive.__init__(self, options)
92
93
def get_minmax_data(self):
94
"""
95
Returns a dictionary with the bounding box data.
96
97
The bounding box is computed as minimal as possible.
98
99
EXAMPLES:
100
101
An example without angle::
102
103
sage: p = arc((-2, 3), 1, 2)
104
sage: d = p.get_minmax_data()
105
sage: d['xmin']
106
-3.0
107
sage: d['xmax']
108
-1.0
109
sage: d['ymin']
110
1.0
111
sage: d['ymax']
112
5.0
113
114
The same example with a rotation of angle `\pi/2`::
115
116
sage: p = arc((-2, 3), 1, 2, pi/2)
117
sage: d = p.get_minmax_data()
118
sage: d['xmin']
119
-4.0
120
sage: d['xmax']
121
0.0
122
sage: d['ymin']
123
2.0
124
sage: d['ymax']
125
4.0
126
"""
127
from sage.plot.plot import minmax_data
128
129
twopi = 2*pi
130
131
s1 = self.s1
132
s2 = self.s2
133
s = s2-s1
134
s1 = fmod(s1,twopi)
135
if s1 < 0: s1 += twopi
136
s2 = fmod(s1 + s,twopi)
137
if s2 < 0: s2 += twopi
138
139
r1 = self.r1
140
r2 = self.r2
141
142
angle = fmod(self.angle,twopi)
143
if angle < 0: angle += twopi
144
145
epsilon = float(0.0000001)
146
147
cos_angle = cos(angle)
148
sin_angle = sin(angle)
149
150
if cos_angle > 1-epsilon:
151
xmin=-r1; ymin=-r2
152
xmax=r1; ymax=r2
153
axmin = pi; axmax = 0
154
aymin = 3*pi/2; aymax = pi/2
155
156
elif cos_angle < -1+epsilon:
157
xmin=-r1; ymin=-r2
158
xmax=r1; ymax=r2
159
axmin=0; axmax=pi
160
aymin=pi/2; aymax=3*pi/2
161
162
elif sin_angle > 1-epsilon:
163
xmin=-r2; ymin=-r1
164
xmax=r2; ymax=r1
165
axmin = pi/2; axmax = 3*pi/2
166
aymin = pi; aymax = 0
167
168
elif sin_angle < -1+epsilon:
169
xmin=-r2; ymin=-r1
170
xmax=r2; ymax=r1
171
axmin = 3*pi/2; axmax = pi/2
172
aymin = 0; aymax = pi
173
174
else:
175
tan_angle = sin_angle / cos_angle
176
axmax = atan(-r2/r1*tan_angle)
177
if axmax < 0: axmax += twopi
178
xmax = (
179
r1 * cos_angle * cos(axmax) -
180
r2 * sin_angle * sin(axmax))
181
if xmax < 0:
182
xmax = -xmax
183
axmax = fmod(axmax+pi,twopi)
184
xmin = -xmax
185
axmin = fmod(axmax + pi,twopi)
186
187
aymax = atan(r2/(r1*tan_angle))
188
if aymax < 0: aymax += twopi
189
ymax = (
190
r1 * sin_angle * cos(aymax) +
191
r2 * cos_angle * sin(aymax))
192
if ymax < 0:
193
ymax = -ymax
194
aymax = fmod(aymax+pi,twopi)
195
ymin = -ymax
196
aymin = fmod(aymax + pi, twopi)
197
198
if s < twopi-epsilon: # bb determined by the sector
199
def is_cyclic_ordered(x1,x2,x3):
200
return (
201
(x1 < x2 and x2 < x3) or
202
(x2 < x3 and x3 < x1) or
203
(x3 < x1 and x1 < x2))
204
205
x1 = cos_angle*r1*cos(s1) - sin_angle*r2*sin(s1)
206
x2 = cos_angle*r1*cos(s2) - sin_angle*r2*sin(s2)
207
y1 = sin_angle*r1*cos(s1) + cos_angle*r2*sin(s1)
208
y2 = sin_angle*r1*cos(s2) + cos_angle*r2*sin(s2)
209
210
if is_cyclic_ordered(s1,s2,axmin): xmin = min(x1,x2)
211
if is_cyclic_ordered(s1,s2,aymin): ymin = min(y1,y2)
212
if is_cyclic_ordered(s1,s2,axmax): xmax = max(x1,x2)
213
if is_cyclic_ordered(s1,s2,aymax): ymax = max(y1,y2)
214
215
return minmax_data([self.x + xmin, self.x + xmax],
216
[self.y + ymin, self.y + ymax],
217
dict=True)
218
219
def _allowed_options(self):
220
"""
221
Return the allowed options for the ``Arc`` class.
222
223
EXAMPLES::
224
225
sage: p = arc((3, 3), 1, 1)
226
sage: p[0]._allowed_options()['alpha']
227
'How transparent the figure is.'
228
"""
229
return {'alpha':'How transparent the figure is.',
230
'thickness':'How thick the border of the arc is.',
231
'hue':'The color given as a hue.',
232
'rgbcolor':'The color',
233
'zorder':'2D only: The layer level in which to draw',
234
'linestyle':"2D only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."}
235
236
def _repr_(self):
237
"""
238
String representation of ``Arc`` primitive.
239
240
EXAMPLES::
241
242
sage: from sage.plot.arc import Arc
243
sage: print Arc(2,3,2.2,2.2,0,2,3,{})
244
Arc with center (2.0,3.0) radii (2.2,2.2) angle 0.0 inside the sector (2.0,3.0)
245
"""
246
return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" %(self.x,self.y,self.r1,self.r2,self.angle,self.s1,self.s2)
247
248
def _render_on_subplot(self, subplot):
249
"""
250
TESTS::
251
252
sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A
253
"""
254
import matplotlib.patches as patches
255
256
options = self.options()
257
258
p = patches.Arc(
259
(self.x,self.y),
260
2.*self.r1,
261
2.*self.r2,
262
fmod(self.angle,2*pi)*(180./pi),
263
self.s1*(180./pi),
264
self.s2*(180./pi))
265
p.set_linewidth(float(options['thickness']))
266
a = float(options['alpha'])
267
p.set_alpha(a)
268
z = int(options.pop('zorder',1))
269
p.set_zorder(z)
270
c = to_mpl_color(options['rgbcolor'])
271
p.set_linestyle(options['linestyle'])
272
p.set_edgecolor(c)
273
subplot.add_patch(p)
274
275
def plot3d(self):
276
r"""
277
TESTS::
278
279
sage: from sage.plot.arc import Arc
280
sage: Arc(0,0,1,1,0,0,1,{}).plot3d()
281
Traceback (most recent call last):
282
...
283
NotImplementedError
284
"""
285
raise NotImplementedError
286
287
@rename_keyword(color='rgbcolor')
288
@options(alpha=1, thickness=1, linestyle='solid', zorder=5,rgbcolor='blue',
289
aspect_ratio=1.0)
290
def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options):
291
r"""
292
An arc (that is a portion of a circle or an ellipse)
293
294
Type ``arc.options`` to see all options.
295
296
INPUT:
297
298
- ``center`` - 2-tuple of real numbers - position of the center.
299
300
- ``r1``, ``r2`` - positive real numbers - radii of the ellipse. If only ``r1``
301
is set, then the two radii are supposed to be equal and this function returns
302
an arc of of circle.
303
304
- ``angle`` - real number - angle between the horizontal and the axis that
305
corresponds to ``r1``.
306
307
- ``sector`` - 2-tuple (default: (0,2*pi))- angles sector in which the arc will
308
be drawn.
309
310
OPTIONS:
311
312
- ``alpha`` - float (default: 1) - transparency
313
314
- ``thickness`` - float (default: 1) - thickness of the arc
315
316
- ``color``, ``rgbcolor`` - string or 2-tuple (default: 'blue') - the color
317
of the arc
318
319
- ``linestyle`` - string (default: 'solid') - the style of the line
320
321
EXAMPLES:
322
323
Plot an arc of circle centered at (0,0) with radius 1 in the sector
324
`(\pi/4,3*\pi/4)`::
325
326
sage: arc((0,0), 1, sector=(pi/4,3*pi/4))
327
328
Plot an arc of an ellipse between the angles 0 and `\pi/2`::
329
330
sage: arc((2,3), 2, 1, sector=(0,pi/2))
331
332
Plot an arc of a rotated ellipse between the angles 0 and `\pi/2`::
333
334
sage: arc((2,3), 2, 1, angle=pi/5, sector=(0,pi/2))
335
336
Plot an arc of an ellipse in red with a dashed linestyle::
337
338
sage: arc((0,0), 2, 1, 0, (0,pi/2), linestyle="dashed", color="red")
339
340
The default aspect ratio for arcs is 1.0::
341
342
sage: arc((0,0), 1, sector=(pi/4,3*pi/4)).aspect_ratio()
343
1.0
344
345
It is not possible to draw arcs in 3D::
346
347
sage: A = arc((0,0,0), 1)
348
Traceback (most recent call last):
349
...
350
NotImplementedError
351
"""
352
from sage.plot.all import Graphics
353
if len(center)==2:
354
if r2 is None: r2 = r1
355
g = Graphics()
356
g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
357
if len(sector) != 2:
358
raise ValueError, "the sector must consist of two angles"
359
g.add_primitive(Arc(
360
center[0],center[1],
361
r1,r2,
362
angle,
363
sector[0],sector[1],
364
options))
365
return g
366
elif len(center)==3:
367
raise NotImplementedError
368
369
370