passagemath-plot 10.6.31rc3__cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-plot might be problematic. Click here for more details.
- passagemath_plot-10.6.31rc3.dist-info/METADATA +172 -0
- passagemath_plot-10.6.31rc3.dist-info/RECORD +82 -0
- passagemath_plot-10.6.31rc3.dist-info/WHEEL +6 -0
- passagemath_plot-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_plot.libs/libgfortran-83c28eba.so.5.0.0 +0 -0
- passagemath_plot.libs/libgsl-cda90e79.so.28.0.0 +0 -0
- passagemath_plot.libs/libopenblasp-r0-6dcb67f9.3.29.so +0 -0
- passagemath_plot.libs/libquadmath-2284e583.so.0.0.0 +0 -0
- sage/all__sagemath_plot.py +15 -0
- sage/ext_data/threejs/animation.css +195 -0
- sage/ext_data/threejs/animation.html +85 -0
- sage/ext_data/threejs/animation.js +273 -0
- sage/ext_data/threejs/fat_lines.js +48 -0
- sage/ext_data/threejs/threejs-version.txt +1 -0
- sage/ext_data/threejs/threejs_template.html +597 -0
- sage/interfaces/all__sagemath_plot.py +1 -0
- sage/interfaces/gnuplot.py +196 -0
- sage/interfaces/jmoldata.py +208 -0
- sage/interfaces/povray.py +56 -0
- sage/plot/all.py +42 -0
- sage/plot/animate.py +1796 -0
- sage/plot/arc.py +504 -0
- sage/plot/arrow.py +671 -0
- sage/plot/bar_chart.py +205 -0
- sage/plot/bezier_path.py +400 -0
- sage/plot/circle.py +435 -0
- sage/plot/colors.py +1606 -0
- sage/plot/complex_plot.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/complex_plot.pyx +1446 -0
- sage/plot/contour_plot.py +1792 -0
- sage/plot/density_plot.py +318 -0
- sage/plot/disk.py +373 -0
- sage/plot/ellipse.py +375 -0
- sage/plot/graphics.py +3580 -0
- sage/plot/histogram.py +354 -0
- sage/plot/hyperbolic_arc.py +404 -0
- sage/plot/hyperbolic_polygon.py +416 -0
- sage/plot/hyperbolic_regular_polygon.py +296 -0
- sage/plot/line.py +626 -0
- sage/plot/matrix_plot.py +629 -0
- sage/plot/misc.py +509 -0
- sage/plot/multigraphics.py +1294 -0
- sage/plot/plot.py +4183 -0
- sage/plot/plot3d/all.py +23 -0
- sage/plot/plot3d/base.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/base.pxd +12 -0
- sage/plot/plot3d/base.pyx +3378 -0
- sage/plot/plot3d/implicit_plot3d.py +659 -0
- sage/plot/plot3d/implicit_surface.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/implicit_surface.pyx +1453 -0
- sage/plot/plot3d/index_face_set.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/index_face_set.pxd +32 -0
- sage/plot/plot3d/index_face_set.pyx +1873 -0
- sage/plot/plot3d/introduction.py +131 -0
- sage/plot/plot3d/list_plot3d.py +649 -0
- sage/plot/plot3d/parametric_plot3d.py +1130 -0
- sage/plot/plot3d/parametric_surface.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/parametric_surface.pxd +12 -0
- sage/plot/plot3d/parametric_surface.pyx +893 -0
- sage/plot/plot3d/platonic.py +601 -0
- sage/plot/plot3d/plot3d.py +1442 -0
- sage/plot/plot3d/plot_field3d.py +162 -0
- sage/plot/plot3d/point_c.pxi +148 -0
- sage/plot/plot3d/revolution_plot3d.py +309 -0
- sage/plot/plot3d/shapes.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/shapes.pxd +22 -0
- sage/plot/plot3d/shapes.pyx +1382 -0
- sage/plot/plot3d/shapes2.py +1512 -0
- sage/plot/plot3d/tachyon.py +1779 -0
- sage/plot/plot3d/texture.py +453 -0
- sage/plot/plot3d/transform.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/transform.pxd +21 -0
- sage/plot/plot3d/transform.pyx +268 -0
- sage/plot/plot3d/tri_plot.py +589 -0
- sage/plot/plot_field.py +362 -0
- sage/plot/point.py +624 -0
- sage/plot/polygon.py +562 -0
- sage/plot/primitive.py +249 -0
- sage/plot/scatter_plot.py +199 -0
- sage/plot/step.py +85 -0
- sage/plot/streamline_plot.py +328 -0
- sage/plot/text.py +432 -0
|
@@ -0,0 +1,1382 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-plot
|
|
2
|
+
"""
|
|
3
|
+
Basic objects such as Sphere, Box, Cone, etc.
|
|
4
|
+
|
|
5
|
+
AUTHORS:
|
|
6
|
+
|
|
7
|
+
- Robert Bradshaw 2007-02: initial version
|
|
8
|
+
- Robert Bradshaw 2007-08: obj/tachyon rendering, much updating
|
|
9
|
+
- Robert Bradshaw 2007-08: cythonization
|
|
10
|
+
|
|
11
|
+
EXAMPLES::
|
|
12
|
+
|
|
13
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
14
|
+
sage: S = Sphere(.5, color='yellow')
|
|
15
|
+
sage: S += Cone(.5, .5, color='red').translate(0,0,.3)
|
|
16
|
+
sage: S += Sphere(.1, color='white').translate(.45,-.1,.15)
|
|
17
|
+
sage: S += Sphere(.05, color='black').translate(.51,-.1,.17)
|
|
18
|
+
sage: S += Sphere(.1, color='white').translate(.45, .1,.15)
|
|
19
|
+
sage: S += Sphere(.05, color='black').translate(.51, .1,.17)
|
|
20
|
+
sage: S += Sphere(.1, color='yellow').translate(.5, 0, -.2)
|
|
21
|
+
sage: S.show()
|
|
22
|
+
sage: S.scale(1,1,2).show()
|
|
23
|
+
|
|
24
|
+
.. PLOT::
|
|
25
|
+
|
|
26
|
+
from sage.plot.plot3d.shapes import *
|
|
27
|
+
S = Sphere(.5, color='yellow')
|
|
28
|
+
S += Cone(.5, .5, color='red').translate(0,0,.3)
|
|
29
|
+
S += Sphere(.1, color='white').translate(.45,-.1,.15) + Sphere(.05, color='black').translate(.51,-.1,.17)
|
|
30
|
+
S += Sphere(.1, color='white').translate(.45, .1,.15) + Sphere(.05, color='black').translate(.51, .1,.17)
|
|
31
|
+
S += Sphere(.1, color='yellow').translate(.5, 0, -.2)
|
|
32
|
+
sphinx_plot(S)
|
|
33
|
+
|
|
34
|
+
::
|
|
35
|
+
|
|
36
|
+
sage: from sage.plot.plot3d.shapes import *
|
|
37
|
+
sage: Torus(.7, .2, color=(0,.3,0)).show()
|
|
38
|
+
|
|
39
|
+
.. PLOT::
|
|
40
|
+
|
|
41
|
+
from sage.plot.plot3d.shapes import *
|
|
42
|
+
sphinx_plot(Torus(.7, .2, color=(0,.3,0)))
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# ****************************************************************************
|
|
46
|
+
# Copyright (C) 2007 Robert Bradshaw <robertwb@math.washington.edu>
|
|
47
|
+
#
|
|
48
|
+
# This program is free software: you can redistribute it and/or modify
|
|
49
|
+
# it under the terms of the GNU General Public License as published by
|
|
50
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
51
|
+
# (at your option) any later version.
|
|
52
|
+
# https://www.gnu.org/licenses/
|
|
53
|
+
# ****************************************************************************
|
|
54
|
+
|
|
55
|
+
from libc.math cimport sqrt, sin, cos, acos, M_PI
|
|
56
|
+
from sage.rings.real_double import RDF
|
|
57
|
+
from sage.modules.free_module_element import vector
|
|
58
|
+
from sage.misc.decorators import rename_keyword
|
|
59
|
+
from sage.plot.plot3d.base import Graphics3dGroup
|
|
60
|
+
from sage.plot.plot3d.index_face_set cimport IndexFaceSet, PrimitiveObject
|
|
61
|
+
from sage.plot.plot3d.transform cimport point_c
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Helper function to check that Box input is right
|
|
65
|
+
def validate_frame_size(size):
|
|
66
|
+
"""
|
|
67
|
+
Check that the input is an iterable of length 3 with all
|
|
68
|
+
elements nonnegative and coercible to floats.
|
|
69
|
+
|
|
70
|
+
EXAMPLES::
|
|
71
|
+
|
|
72
|
+
sage: from sage.plot.plot3d.shapes import validate_frame_size
|
|
73
|
+
sage: validate_frame_size([3,2,1])
|
|
74
|
+
[3.0, 2.0, 1.0]
|
|
75
|
+
|
|
76
|
+
TESTS::
|
|
77
|
+
|
|
78
|
+
sage: from sage.plot.plot3d.shapes import validate_frame_size
|
|
79
|
+
sage: validate_frame_size([3,2,-1])
|
|
80
|
+
Traceback (most recent call last):
|
|
81
|
+
...
|
|
82
|
+
ValueError: each box dimension must be nonnegative
|
|
83
|
+
sage: validate_frame_size([sqrt(-1),3,2]) # needs sage.symbolic
|
|
84
|
+
Traceback (most recent call last):
|
|
85
|
+
...
|
|
86
|
+
TypeError: each box dimension must coerce to a float
|
|
87
|
+
"""
|
|
88
|
+
if not isinstance(size, (list, tuple)):
|
|
89
|
+
raise TypeError("size must be a list or tuple")
|
|
90
|
+
if len(size) != 3:
|
|
91
|
+
raise TypeError("size must be of length 3")
|
|
92
|
+
try:
|
|
93
|
+
size = [float(x) for x in size]
|
|
94
|
+
except TypeError:
|
|
95
|
+
raise TypeError("each box dimension must coerce to a float")
|
|
96
|
+
for x in size:
|
|
97
|
+
if x < 0:
|
|
98
|
+
raise ValueError("each box dimension must be nonnegative")
|
|
99
|
+
return size
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Box(IndexFaceSet):
|
|
103
|
+
"""
|
|
104
|
+
Return a box.
|
|
105
|
+
|
|
106
|
+
EXAMPLES::
|
|
107
|
+
|
|
108
|
+
sage: from sage.plot.plot3d.shapes import Box
|
|
109
|
+
|
|
110
|
+
A square black box::
|
|
111
|
+
|
|
112
|
+
sage: show(Box([1,1,1]), color='black')
|
|
113
|
+
|
|
114
|
+
.. PLOT::
|
|
115
|
+
|
|
116
|
+
from sage.plot.plot3d.shapes import Box
|
|
117
|
+
sphinx_plot(Box([1,1,1], color='black'))
|
|
118
|
+
|
|
119
|
+
A red rectangular box::
|
|
120
|
+
|
|
121
|
+
sage: show(Box([2,3,4], color='red'))
|
|
122
|
+
|
|
123
|
+
.. PLOT::
|
|
124
|
+
|
|
125
|
+
from sage.plot.plot3d.shapes import Box
|
|
126
|
+
sphinx_plot(Box([2,3,4], color='red'))
|
|
127
|
+
|
|
128
|
+
A stack of boxes::
|
|
129
|
+
|
|
130
|
+
sage: show(sum(Box([2,3,1], color='red').translate((0,0,6*i))
|
|
131
|
+
....: for i in [0..3]))
|
|
132
|
+
|
|
133
|
+
.. PLOT::
|
|
134
|
+
|
|
135
|
+
from sage.plot.plot3d.shapes import Box
|
|
136
|
+
P = sum([Box([2,3,1], color='red').translate((0,0,6*i)) for i in range(0,4)])
|
|
137
|
+
sphinx_plot(P)
|
|
138
|
+
|
|
139
|
+
A sinusoidal stack of multicolored boxes::
|
|
140
|
+
|
|
141
|
+
sage: B = sum(Box([2,4,1/4], # needs sage.symbolic
|
|
142
|
+
....: color=(i/4,i/5,1)).translate((sin(i),0,5-i))
|
|
143
|
+
....: for i in [0..20])
|
|
144
|
+
sage: show(B, figsize=6) # needs sage.symbolic
|
|
145
|
+
|
|
146
|
+
.. PLOT::
|
|
147
|
+
|
|
148
|
+
from sage.plot.plot3d.shapes import Box
|
|
149
|
+
B = sum([Box([2,4,1/4], color=(i/4.0,i/5.0,1)).translate((sin(i),0,5-i)) for i in range(0,21)])
|
|
150
|
+
sphinx_plot(B)
|
|
151
|
+
"""
|
|
152
|
+
def __init__(self, *size, **kwds):
|
|
153
|
+
"""
|
|
154
|
+
EXAMPLES::
|
|
155
|
+
|
|
156
|
+
sage: from sage.plot.plot3d.shapes import Box
|
|
157
|
+
sage: Box(10, 1, 1) + Box(1, 10, 1) + Box(1, 1, 10)
|
|
158
|
+
Graphics3d Object
|
|
159
|
+
"""
|
|
160
|
+
if isinstance(size[0], (tuple, list)):
|
|
161
|
+
size = validate_frame_size(size[0])
|
|
162
|
+
self.size = size
|
|
163
|
+
x, y, z = self.size
|
|
164
|
+
faces = [[(x, y, z), (-x, y, z), (-x,-y, z), ( x,-y, z)],
|
|
165
|
+
[(x, y, z), ( x, y,-z), (-x, y,-z), (-x, y, z)],
|
|
166
|
+
[(x, y, z), ( x,-y, z), ( x,-y,-z), ( x, y,-z)] ]
|
|
167
|
+
faces += [list(reversed([(-x,-y,-z) for x,y,z in face])) for face in faces]
|
|
168
|
+
IndexFaceSet.__init__(self, faces, enclosed=True, **kwds)
|
|
169
|
+
|
|
170
|
+
def bounding_box(self):
|
|
171
|
+
"""
|
|
172
|
+
EXAMPLES::
|
|
173
|
+
|
|
174
|
+
sage: from sage.plot.plot3d.shapes import Box
|
|
175
|
+
sage: Box([1,2,3]).bounding_box()
|
|
176
|
+
((-1.0, -2.0, -3.0), (1.0, 2.0, 3.0))
|
|
177
|
+
"""
|
|
178
|
+
return tuple([-a for a in self.size]), tuple(self.size)
|
|
179
|
+
|
|
180
|
+
def x3d_geometry(self):
|
|
181
|
+
"""
|
|
182
|
+
EXAMPLES::
|
|
183
|
+
|
|
184
|
+
sage: from sage.plot.plot3d.shapes import Box
|
|
185
|
+
sage: Box([1,2,1/4]).x3d_geometry()
|
|
186
|
+
"<Box size='1.0 2.0 0.25'/>"
|
|
187
|
+
"""
|
|
188
|
+
return "<Box size='%s %s %s'/>" % tuple(self.size)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@rename_keyword(alpha='opacity')
|
|
192
|
+
def ColorCube(size, colors, opacity=1, **kwds):
|
|
193
|
+
"""
|
|
194
|
+
Return a cube with given size and sides with given colors.
|
|
195
|
+
|
|
196
|
+
INPUT:
|
|
197
|
+
|
|
198
|
+
- ``size`` -- 3-tuple of sizes (same as for box and frame)
|
|
199
|
+
- ``colors`` -- list of either 3 or 6 colors
|
|
200
|
+
- ``opacity`` -- (default: 1) opacity of cube sides
|
|
201
|
+
- ``**kwds`` -- passed to the face constructor
|
|
202
|
+
|
|
203
|
+
OUTPUT: a 3d graphics object
|
|
204
|
+
|
|
205
|
+
EXAMPLES:
|
|
206
|
+
|
|
207
|
+
A color cube with translucent sides::
|
|
208
|
+
|
|
209
|
+
sage: from sage.plot.plot3d.shapes import ColorCube
|
|
210
|
+
sage: c = ColorCube([1,2,3], ['red', 'blue', 'green', 'black', 'white', 'orange'], opacity=0.5)
|
|
211
|
+
sage: c.show()
|
|
212
|
+
|
|
213
|
+
.. PLOT::
|
|
214
|
+
|
|
215
|
+
from sage.plot.plot3d.shapes import ColorCube
|
|
216
|
+
c = ColorCube([1,2,3], ['red', 'blue', 'green', 'black', 'white', 'orange'], opacity=0.5)
|
|
217
|
+
sphinx_plot(c)
|
|
218
|
+
|
|
219
|
+
::
|
|
220
|
+
|
|
221
|
+
sage: list(c.texture_set())[0].opacity
|
|
222
|
+
0.5
|
|
223
|
+
|
|
224
|
+
If you omit the last 3 colors then the first three are repeated (with
|
|
225
|
+
repeated colors on opposing faces)::
|
|
226
|
+
|
|
227
|
+
sage: c = ColorCube([0.5,0.5,0.5], ['red', 'blue', 'green'])
|
|
228
|
+
|
|
229
|
+
.. PLOT::
|
|
230
|
+
|
|
231
|
+
from sage.plot.plot3d.shapes import ColorCube
|
|
232
|
+
c = ColorCube([0.5,0.5,0.5], ['red', 'blue', 'green'])
|
|
233
|
+
sphinx_plot(c)
|
|
234
|
+
"""
|
|
235
|
+
if not isinstance(size, (tuple, list)):
|
|
236
|
+
size = (size, size, size)
|
|
237
|
+
box = Box(size)
|
|
238
|
+
faces = box.face_list()
|
|
239
|
+
if len(colors) == 3:
|
|
240
|
+
colors = colors * 2
|
|
241
|
+
all = []
|
|
242
|
+
kwds['opacity'] = opacity
|
|
243
|
+
|
|
244
|
+
from sage.plot.plot3d.texture import Texture
|
|
245
|
+
for k in range(6):
|
|
246
|
+
all.append(IndexFaceSet([faces[k]], enclosed=True,
|
|
247
|
+
texture=Texture(colors[k], opacity=opacity),
|
|
248
|
+
**kwds))
|
|
249
|
+
return Graphics3dGroup(all)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
cdef class Cone(ParametricSurface):
|
|
253
|
+
"""
|
|
254
|
+
A cone, with base in the xy-plane pointing up the z-axis.
|
|
255
|
+
|
|
256
|
+
INPUT:
|
|
257
|
+
|
|
258
|
+
- ``radius`` -- positive real number
|
|
259
|
+
|
|
260
|
+
- ``height`` -- positive real number
|
|
261
|
+
|
|
262
|
+
- ``closed`` -- whether or not to include the base (default: ``True``)
|
|
263
|
+
|
|
264
|
+
- ``**kwds`` -- passed to the ParametricSurface constructor
|
|
265
|
+
|
|
266
|
+
EXAMPLES::
|
|
267
|
+
|
|
268
|
+
sage: from sage.plot.plot3d.shapes import Cone
|
|
269
|
+
sage: c = Cone(3/2, 1, color='red')
|
|
270
|
+
sage: c += Cone(1, 2, color='yellow').translate(3, 0, 0)
|
|
271
|
+
sage: c.show(aspect_ratio=1)
|
|
272
|
+
|
|
273
|
+
.. PLOT::
|
|
274
|
+
|
|
275
|
+
from sage.plot.plot3d.shapes import Cone
|
|
276
|
+
c = Cone(3/2, 1, color='red') + Cone(1, 2, color='yellow').translate(3, 0, 0)
|
|
277
|
+
sphinx_plot(c)
|
|
278
|
+
|
|
279
|
+
We may omit the base::
|
|
280
|
+
|
|
281
|
+
sage: Cone(1, 1, closed=False)
|
|
282
|
+
Graphics3d Object
|
|
283
|
+
|
|
284
|
+
.. PLOT::
|
|
285
|
+
|
|
286
|
+
from sage.plot.plot3d.shapes import Cone
|
|
287
|
+
sphinx_plot(Cone(1, 1, closed=False))
|
|
288
|
+
|
|
289
|
+
A spiky plot of the sine function::
|
|
290
|
+
|
|
291
|
+
sage: sum(Cone(.1, sin(n), color='yellow').translate(n, sin(n), 0) # needs sage.symbolic
|
|
292
|
+
....: for n in [0..10, step=.1])
|
|
293
|
+
Graphics3d Object
|
|
294
|
+
|
|
295
|
+
.. PLOT::
|
|
296
|
+
|
|
297
|
+
from sage.plot.plot3d.shapes import Cone
|
|
298
|
+
sphinx_plot(sum(Cone(.1, sin(n/10.0), color='yellow').translate(n/10.0, sin(n/10.0), 0) for n in range(0,100)))
|
|
299
|
+
|
|
300
|
+
A Christmas tree::
|
|
301
|
+
|
|
302
|
+
sage: T = sum(Cone(exp(-n/5), 4/3*exp(-n/5), # needs sage.symbolic
|
|
303
|
+
....: color=(0, .5, 0)).translate(0, 0, -3*exp(-n/5))
|
|
304
|
+
....: for n in [1..7])
|
|
305
|
+
sage: T += Cone(1/8, 1, color='brown').translate(0, 0, -3) # needs sage.symbolic
|
|
306
|
+
sage: T.show(aspect_ratio=1, frame=False) # needs sage.symbolic
|
|
307
|
+
|
|
308
|
+
.. PLOT::
|
|
309
|
+
|
|
310
|
+
from sage.plot.plot3d.shapes import Cone
|
|
311
|
+
T = sum(Cone(exp(-n/5.0), 4/3*exp(-n/5.0), color=(0, .5, 0)).translate(0, 0, -3*exp(-n/5.0)) for n in range(8))
|
|
312
|
+
T += Cone(1/8, 1, color='brown').translate(0, 0, -3)
|
|
313
|
+
sphinx_plot(T)
|
|
314
|
+
"""
|
|
315
|
+
def __init__(self, radius, height, closed=True, **kwds):
|
|
316
|
+
"""
|
|
317
|
+
TESTS::
|
|
318
|
+
|
|
319
|
+
sage: from sage.plot.plot3d.shapes import Cone
|
|
320
|
+
sage: c = Cone(1/2, 1, opacity=.5)
|
|
321
|
+
"""
|
|
322
|
+
ParametricSurface.__init__(self, **kwds)
|
|
323
|
+
self.radius = radius
|
|
324
|
+
self.height = height
|
|
325
|
+
self.closed = closed
|
|
326
|
+
|
|
327
|
+
def x3d_geometry(self):
|
|
328
|
+
"""
|
|
329
|
+
EXAMPLES::
|
|
330
|
+
|
|
331
|
+
sage: from sage.plot.plot3d.shapes import Cone
|
|
332
|
+
sage: Cone(1, 3).x3d_geometry()
|
|
333
|
+
"<Cone bottomRadius='1.0' height='3.0'/>"
|
|
334
|
+
"""
|
|
335
|
+
return "<Cone bottomRadius='%s' height='%s'/>" % (self.radius,
|
|
336
|
+
self.height)
|
|
337
|
+
|
|
338
|
+
def get_grid(self, ds):
|
|
339
|
+
"""
|
|
340
|
+
Return the grid on which to evaluate this parametric surface.
|
|
341
|
+
|
|
342
|
+
EXAMPLES::
|
|
343
|
+
|
|
344
|
+
sage: from sage.plot.plot3d.shapes import Cone
|
|
345
|
+
sage: Cone(1, 3, closed=True).get_grid(100)
|
|
346
|
+
([1, 0, -1], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
|
|
347
|
+
sage: Cone(1, 3, closed=False).get_grid(100)
|
|
348
|
+
([1, 0], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
|
|
349
|
+
sage: len(Cone(1, 3).get_grid(.001)[1])
|
|
350
|
+
38
|
|
351
|
+
"""
|
|
352
|
+
cdef int k, t_res = min(max(int(2*M_PI*self.radius/ds), 5), 37)
|
|
353
|
+
if self.closed:
|
|
354
|
+
urange = [1,0,-1]
|
|
355
|
+
else:
|
|
356
|
+
urange = [1,0]
|
|
357
|
+
vrange = [2*M_PI*k/t_res for k in range(t_res)] + [0.0]
|
|
358
|
+
return urange, vrange
|
|
359
|
+
|
|
360
|
+
cdef int eval_c(self, point_c *res, double u, double v) except -1:
|
|
361
|
+
if u == -1:
|
|
362
|
+
res.x, res.y, res.z = 0, 0, 0
|
|
363
|
+
elif u == 0:
|
|
364
|
+
res.x = self.radius*sin(v)
|
|
365
|
+
res.y = self.radius*cos(v)
|
|
366
|
+
res.z = 0
|
|
367
|
+
else: # u == 1:
|
|
368
|
+
res.x, res.y, res.z = 0, 0, self.height
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
cdef class Cylinder(ParametricSurface):
|
|
372
|
+
"""
|
|
373
|
+
A cone, with base in the xy-plane pointing up the z-axis.
|
|
374
|
+
|
|
375
|
+
INPUT:
|
|
376
|
+
|
|
377
|
+
- ``radius`` -- positive real number
|
|
378
|
+
|
|
379
|
+
- ``height`` -- positive real number
|
|
380
|
+
|
|
381
|
+
- ``closed`` -- whether or not to include the ends (default: ``True``)
|
|
382
|
+
|
|
383
|
+
- ``**kwds`` -- passed to the :class:`ParametricSurface` constructor
|
|
384
|
+
|
|
385
|
+
EXAMPLES::
|
|
386
|
+
|
|
387
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
388
|
+
sage: c = Cylinder(3/2, 1, color='red')
|
|
389
|
+
sage: c += Cylinder(1, 2, color='yellow').translate(3, 0, 0)
|
|
390
|
+
sage: c.show(aspect_ratio=1)
|
|
391
|
+
|
|
392
|
+
.. PLOT::
|
|
393
|
+
|
|
394
|
+
from sage.plot.plot3d.shapes import Cylinder
|
|
395
|
+
c = Cylinder(3/2, 1, color='red') + Cylinder(1, 2, color='yellow').translate(3, 0, 0)
|
|
396
|
+
sphinx_plot(c)
|
|
397
|
+
|
|
398
|
+
We may omit the base::
|
|
399
|
+
|
|
400
|
+
sage: Cylinder(1, 1, closed=False)
|
|
401
|
+
Graphics3d Object
|
|
402
|
+
|
|
403
|
+
.. PLOT::
|
|
404
|
+
|
|
405
|
+
from sage.plot.plot3d.shapes import Cylinder
|
|
406
|
+
sphinx_plot(Cylinder(1, 1, closed=False))
|
|
407
|
+
|
|
408
|
+
Some gears::
|
|
409
|
+
|
|
410
|
+
sage: # needs sage.symbolic
|
|
411
|
+
sage: G = Cylinder(1, .5) + Cylinder(.25, 3).translate(0, 0, -3)
|
|
412
|
+
sage: G += sum(Cylinder(.2, 1).translate(cos(2*pi*n/9), sin(2*pi*n/9), 0)
|
|
413
|
+
....: for n in [1..9])
|
|
414
|
+
sage: G += G.translate(2.3, 0, -.5)
|
|
415
|
+
sage: G += G.translate(3.5, 2, -1)
|
|
416
|
+
sage: G.show(aspect_ratio=1, frame=False)
|
|
417
|
+
|
|
418
|
+
.. PLOT::
|
|
419
|
+
|
|
420
|
+
from sage.plot.plot3d.shapes import Cylinder
|
|
421
|
+
G = Cylinder(1, .5) + Cylinder(.25, 3).translate(0, 0, -3)
|
|
422
|
+
G += sum(Cylinder(.2, 1).translate(cos(2*pi*n/9.0), sin(2*pi*n/9.0), 0) for n in range(10))
|
|
423
|
+
G += G.translate(2.3, 0, -.5)
|
|
424
|
+
G += G.translate(3.5, 2, -1)
|
|
425
|
+
sphinx_plot(G)
|
|
426
|
+
"""
|
|
427
|
+
def __init__(self, radius, height, closed=True, **kwds):
|
|
428
|
+
"""
|
|
429
|
+
TESTS::
|
|
430
|
+
|
|
431
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
432
|
+
sage: Cylinder(1, 1, color='red')
|
|
433
|
+
Graphics3d Object
|
|
434
|
+
"""
|
|
435
|
+
ParametricSurface.__init__(self, **kwds)
|
|
436
|
+
self.radius = radius
|
|
437
|
+
self.height = height
|
|
438
|
+
self.closed = closed
|
|
439
|
+
|
|
440
|
+
def bounding_box(self):
|
|
441
|
+
"""
|
|
442
|
+
EXAMPLES::
|
|
443
|
+
|
|
444
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
445
|
+
sage: Cylinder(1, 2).bounding_box()
|
|
446
|
+
((-1.0, -1.0, 0), (1.0, 1.0, 2.0))
|
|
447
|
+
"""
|
|
448
|
+
return ((-self.radius, -self.radius, 0),
|
|
449
|
+
(self.radius, self.radius, self.height))
|
|
450
|
+
|
|
451
|
+
def x3d_geometry(self):
|
|
452
|
+
"""
|
|
453
|
+
EXAMPLES::
|
|
454
|
+
|
|
455
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
456
|
+
sage: Cylinder(1, 2).x3d_geometry()
|
|
457
|
+
"<Cylinder radius='1.0' height='2.0'/>"
|
|
458
|
+
"""
|
|
459
|
+
return "<Cylinder radius='%s' height='%s'/>" % (self.radius,
|
|
460
|
+
self.height)
|
|
461
|
+
|
|
462
|
+
def tachyon_repr(self, render_params):
|
|
463
|
+
r"""
|
|
464
|
+
EXAMPLES::
|
|
465
|
+
|
|
466
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
467
|
+
sage: C = Cylinder(1/2, 4, closed=False)
|
|
468
|
+
sage: C.tachyon_repr(C.default_render_params())
|
|
469
|
+
'FCylinder\n Base 0 0 0\n Apex 0 0 4.0\n Rad 0.5\n texture... '
|
|
470
|
+
sage: C = Cylinder(1, 2)
|
|
471
|
+
sage: C.tachyon_repr(C.default_render_params())
|
|
472
|
+
['Ring Center 0 0 0 Normal 0 0 1 Inner 0 Outer 1.0 texture...',
|
|
473
|
+
'FCylinder\n Base 0 0 0\n Apex 0 0 2.0\n Rad 1.0\n texture... ',
|
|
474
|
+
'Ring Center 0 0 2.0 Normal 0 0 1 Inner 0 Outer 1.0 texture...']
|
|
475
|
+
"""
|
|
476
|
+
transform = render_params.transform
|
|
477
|
+
if not (transform is None or transform.is_uniform_on([(1,0,0),(0,1,0)])):
|
|
478
|
+
# Tachyon can't do squashed
|
|
479
|
+
return ParametricSurface.tachyon_repr(self, render_params)
|
|
480
|
+
|
|
481
|
+
base, top = self.get_endpoints(transform)
|
|
482
|
+
rad = self.get_radius(transform)
|
|
483
|
+
cyl = """FCylinder
|
|
484
|
+
Base %s %s %s
|
|
485
|
+
Apex %s %s %s
|
|
486
|
+
Rad %s
|
|
487
|
+
%s """ % (base[0], base[1], base[2], top[0], top[1], top[2], rad, self.texture.id)
|
|
488
|
+
if self.closed:
|
|
489
|
+
normal = (0,0,1)
|
|
490
|
+
if transform is not None:
|
|
491
|
+
normal = transform.transform_vector(normal)
|
|
492
|
+
base_cap = """Ring Center %s %s %s Normal %s %s %s Inner 0 Outer %s %s""" \
|
|
493
|
+
% (base[0], base[1], base[2], normal[0], normal[1], normal[2], rad, self.texture.id)
|
|
494
|
+
top_cap = """Ring Center %s %s %s Normal %s %s %s Inner 0 Outer %s %s""" \
|
|
495
|
+
% (top[0], top[1], top[2], normal[0], normal[1], normal[2], rad, self.texture.id)
|
|
496
|
+
return [base_cap, cyl, top_cap]
|
|
497
|
+
else:
|
|
498
|
+
return cyl
|
|
499
|
+
|
|
500
|
+
def jmol_repr(self, render_params):
|
|
501
|
+
r"""
|
|
502
|
+
EXAMPLES::
|
|
503
|
+
|
|
504
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
505
|
+
|
|
506
|
+
For thin cylinders, lines are used::
|
|
507
|
+
|
|
508
|
+
sage: C = Cylinder(.1, 4)
|
|
509
|
+
sage: C.jmol_repr(C.default_render_params())
|
|
510
|
+
['\ndraw line_1 width 0.1 {0 0 0} {0 0 4.0}\ncolor $line_1 [102,102,255]\n']
|
|
511
|
+
|
|
512
|
+
For anything larger, we use a pmesh::
|
|
513
|
+
|
|
514
|
+
sage: C = Cylinder(3, 1, closed=False)
|
|
515
|
+
sage: C.jmol_repr(C.testing_render_params())
|
|
516
|
+
['pmesh obj_1 "obj_1.pmesh"\ncolor pmesh [102,102,255]']
|
|
517
|
+
"""
|
|
518
|
+
transform = render_params.transform
|
|
519
|
+
base, top = self.get_endpoints(transform)
|
|
520
|
+
rad = self.get_radius(transform)
|
|
521
|
+
|
|
522
|
+
cdef double ratio = sqrt(rad*rad / ((base[0]-top[0])**2 + (base[1]-top[1])**2 + (base[2]-top[2])**2))
|
|
523
|
+
|
|
524
|
+
if ratio > .02:
|
|
525
|
+
if not (transform is None or transform.is_uniform_on([(1,0,0),(0,1,0)])) or ratio > .05:
|
|
526
|
+
# Jmol can't do squashed
|
|
527
|
+
return ParametricSurface.jmol_repr(self, render_params)
|
|
528
|
+
|
|
529
|
+
name = render_params.unique_name('line')
|
|
530
|
+
return ["""
|
|
531
|
+
draw %s width %s {%s %s %s} {%s %s %s}\n%s
|
|
532
|
+
""" % (name, rad,
|
|
533
|
+
base[0], base[1], base[2],
|
|
534
|
+
top[0], top[1], top[2],
|
|
535
|
+
self.texture.jmol_str("$" + name))]
|
|
536
|
+
|
|
537
|
+
def get_endpoints(self, transform=None):
|
|
538
|
+
"""
|
|
539
|
+
EXAMPLES::
|
|
540
|
+
|
|
541
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
542
|
+
sage: from sage.plot.plot3d.transform import Transformation
|
|
543
|
+
sage: Cylinder(1, 5).get_endpoints()
|
|
544
|
+
((0, 0, 0), (0, 0, 5.0))
|
|
545
|
+
sage: Cylinder(1, 5).get_endpoints(Transformation(trans=(1,2,3),
|
|
546
|
+
....: scale=(2,2,2)))
|
|
547
|
+
((1.0, 2.0, 3.0), (1.0, 2.0, 13.0))
|
|
548
|
+
"""
|
|
549
|
+
if transform is None:
|
|
550
|
+
return (0,0,0), (0,0,self.height)
|
|
551
|
+
else:
|
|
552
|
+
return transform.transform_point((0,0,0)), transform.transform_point((0,0,self.height))
|
|
553
|
+
|
|
554
|
+
def get_radius(self, transform=None):
|
|
555
|
+
"""
|
|
556
|
+
EXAMPLES::
|
|
557
|
+
|
|
558
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
559
|
+
sage: from sage.plot.plot3d.transform import Transformation
|
|
560
|
+
sage: Cylinder(3, 1).get_radius()
|
|
561
|
+
3.0
|
|
562
|
+
sage: Cylinder(3, 1).get_radius(Transformation(trans=(1,2,3),
|
|
563
|
+
....: scale=(2,2,2)))
|
|
564
|
+
6.0
|
|
565
|
+
"""
|
|
566
|
+
if transform is None:
|
|
567
|
+
return self.radius
|
|
568
|
+
radv = transform.transform_vector((self.radius, 0, 0))
|
|
569
|
+
return sqrt(sum([x * x for x in radv]))
|
|
570
|
+
|
|
571
|
+
def get_grid(self, ds):
|
|
572
|
+
"""
|
|
573
|
+
Return the grid on which to evaluate this parametric surface.
|
|
574
|
+
|
|
575
|
+
EXAMPLES::
|
|
576
|
+
|
|
577
|
+
sage: from sage.plot.plot3d.shapes import Cylinder
|
|
578
|
+
sage: Cylinder(1, 3, closed=True).get_grid(100)
|
|
579
|
+
([2, 1, -1, -2], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
|
|
580
|
+
sage: Cylinder(1, 3, closed=False).get_grid(100)
|
|
581
|
+
([1, -1], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
|
|
582
|
+
sage: len(Cylinder(1, 3).get_grid(.001)[1])
|
|
583
|
+
38
|
|
584
|
+
"""
|
|
585
|
+
cdef int k, v_res = min(max(int(2*M_PI*self.radius/ds), 5), 37)
|
|
586
|
+
if self.closed:
|
|
587
|
+
urange = [2,1,-1,-2]
|
|
588
|
+
else:
|
|
589
|
+
urange = [1,-1]
|
|
590
|
+
vrange = [2*M_PI*k/v_res for k in range(v_res)] + [0.0]
|
|
591
|
+
return urange, vrange
|
|
592
|
+
|
|
593
|
+
cdef int eval_c(self, point_c *res, double u, double v) except -1:
|
|
594
|
+
if u == -2:
|
|
595
|
+
res.x, res.y, res.z = 0, 0, 0
|
|
596
|
+
elif u == -1:
|
|
597
|
+
res.x = self.radius*sin(v)
|
|
598
|
+
res.y = self.radius*cos(v)
|
|
599
|
+
res.z = 0
|
|
600
|
+
elif u == 1:
|
|
601
|
+
res.x = self.radius*sin(v)
|
|
602
|
+
res.y = self.radius*cos(v)
|
|
603
|
+
res.z = self.height
|
|
604
|
+
else: # u == 2:
|
|
605
|
+
res.x, res.y, res.z = 0, 0, self.height
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@rename_keyword(alpha='opacity')
|
|
609
|
+
def LineSegment(start, end, thickness=1, radius=None, **kwds):
|
|
610
|
+
"""
|
|
611
|
+
Create a line segment, which is drawn as a cylinder from start to
|
|
612
|
+
end with radius ``radius``.
|
|
613
|
+
|
|
614
|
+
EXAMPLES::
|
|
615
|
+
|
|
616
|
+
sage: from sage.plot.plot3d.shapes import LineSegment, Sphere
|
|
617
|
+
sage: P = (0,0,0.1)
|
|
618
|
+
sage: Q = (0.5,0.6,0.7)
|
|
619
|
+
sage: S = Sphere(.2, color='red').translate(P)
|
|
620
|
+
sage: S += Sphere(.2, color='blue').translate(Q)
|
|
621
|
+
sage: S += LineSegment(P, Q, .05, color='black')
|
|
622
|
+
sage: S.show()
|
|
623
|
+
|
|
624
|
+
.. PLOT::
|
|
625
|
+
|
|
626
|
+
from sage.plot.plot3d.shapes import LineSegment, Sphere
|
|
627
|
+
P = (0,0,0.1)
|
|
628
|
+
Q = (0.5,0.6,0.7)
|
|
629
|
+
S = Sphere(.2, color='red').translate(P)
|
|
630
|
+
S += Sphere(.2, color='blue').translate(Q)
|
|
631
|
+
S += LineSegment(P, Q, .05, color='black')
|
|
632
|
+
sphinx_plot(S)
|
|
633
|
+
|
|
634
|
+
::
|
|
635
|
+
|
|
636
|
+
sage: S = Sphere(.1, color='red').translate(P)
|
|
637
|
+
sage: S += Sphere(.1, color='blue').translate(Q)
|
|
638
|
+
sage: S += LineSegment(P, Q, .15, color='black')
|
|
639
|
+
sage: S.show()
|
|
640
|
+
|
|
641
|
+
.. PLOT::
|
|
642
|
+
|
|
643
|
+
from sage.plot.plot3d.shapes import LineSegment, Sphere
|
|
644
|
+
P = (0,0,0.1)
|
|
645
|
+
Q = (0.5,0.6,0.7)
|
|
646
|
+
S = Sphere(.1, color='red').translate(P)
|
|
647
|
+
S += Sphere(.1, color='blue').translate(Q)
|
|
648
|
+
S += LineSegment(P, Q, .15, color='black')
|
|
649
|
+
sphinx_plot(S)
|
|
650
|
+
|
|
651
|
+
AUTHOR:
|
|
652
|
+
|
|
653
|
+
- Robert Bradshaw
|
|
654
|
+
"""
|
|
655
|
+
if radius is None:
|
|
656
|
+
radius = thickness/50.0
|
|
657
|
+
start = vector(RDF, start, sparse=False)
|
|
658
|
+
end = vector(RDF, end, sparse=False)
|
|
659
|
+
zaxis = vector(RDF, (0,0,1), sparse=False)
|
|
660
|
+
diff = end - start
|
|
661
|
+
height= sqrt(diff.dot_product(diff))
|
|
662
|
+
cyl = Cylinder(radius, height, **kwds)
|
|
663
|
+
axis = zaxis.cross_product(diff)
|
|
664
|
+
if axis == 0:
|
|
665
|
+
if diff[2] < 0:
|
|
666
|
+
return cyl.translate(end)
|
|
667
|
+
else:
|
|
668
|
+
return cyl.translate(start)
|
|
669
|
+
else:
|
|
670
|
+
theta = -acos(diff[2]/height)
|
|
671
|
+
return cyl.rotate(axis, theta).translate(start)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def arrow3d(start, end, width=1, radius=None, head_radius=None, head_len=None, **kwds):
|
|
675
|
+
"""
|
|
676
|
+
Create a 3d arrow.
|
|
677
|
+
|
|
678
|
+
INPUT:
|
|
679
|
+
|
|
680
|
+
- ``start`` -- (x,y,z) point; the starting point of the arrow
|
|
681
|
+
- ``end`` -- (x,y,z) point; the end point
|
|
682
|
+
- ``width`` -- (default: 1) how wide the arrow is
|
|
683
|
+
- ``radius`` -- (default: ``width/50.0``) the radius of the arrow
|
|
684
|
+
- ``head_radius`` -- (default: ``3*radius``) radius of arrow head
|
|
685
|
+
- ``head_len`` -- (default: ``3*head_radius``) len of arrow head
|
|
686
|
+
|
|
687
|
+
EXAMPLES:
|
|
688
|
+
|
|
689
|
+
The default arrow::
|
|
690
|
+
|
|
691
|
+
sage: arrow3d((0,0,0), (1,1,1), 1)
|
|
692
|
+
Graphics3d Object
|
|
693
|
+
|
|
694
|
+
.. PLOT::
|
|
695
|
+
|
|
696
|
+
sphinx_plot(arrow3d((0,0,0), (1,1,1), 1))
|
|
697
|
+
|
|
698
|
+
A fat arrow::
|
|
699
|
+
|
|
700
|
+
sage: arrow3d((0,0,0), (1,1,1), radius=0.1)
|
|
701
|
+
Graphics3d Object
|
|
702
|
+
|
|
703
|
+
.. PLOT::
|
|
704
|
+
|
|
705
|
+
sphinx_plot(arrow3d((0,0,0), (1,1,1), radius=0.1))
|
|
706
|
+
|
|
707
|
+
A green arrow::
|
|
708
|
+
|
|
709
|
+
sage: arrow3d((0,0,0), (1,1,1), color='green')
|
|
710
|
+
Graphics3d Object
|
|
711
|
+
|
|
712
|
+
.. PLOT::
|
|
713
|
+
|
|
714
|
+
sphinx_plot(arrow3d((0,0,0), (1,1,1), color='green'))
|
|
715
|
+
|
|
716
|
+
A fat arrow head::
|
|
717
|
+
|
|
718
|
+
sage: arrow3d((2,1,0), (1,1,1), color='green', head_radius=0.3,
|
|
719
|
+
....: aspect_ratio=[1,1,1])
|
|
720
|
+
Graphics3d Object
|
|
721
|
+
|
|
722
|
+
.. PLOT::
|
|
723
|
+
|
|
724
|
+
sphinx_plot(arrow3d((2,1,0), (1,1,1), color='green', head_radius=0.3, aspect_ratio=[1,1,1]))
|
|
725
|
+
|
|
726
|
+
Many arrows arranged in a circle (flying spears?)::
|
|
727
|
+
|
|
728
|
+
sage: sum(arrow3d((cos(t),sin(t),0), (cos(t),sin(t),1)) # needs sage.symbolic
|
|
729
|
+
....: for t in [0,0.3,..,2*pi])
|
|
730
|
+
Graphics3d Object
|
|
731
|
+
|
|
732
|
+
.. PLOT::
|
|
733
|
+
|
|
734
|
+
t=0
|
|
735
|
+
G=Graphics()
|
|
736
|
+
while (t<=2*pi):
|
|
737
|
+
G += arrow3d((cos(t),sin(t),0),(cos(t),sin(t),1))
|
|
738
|
+
t +=0.3
|
|
739
|
+
sphinx_plot(G)
|
|
740
|
+
|
|
741
|
+
Change the width of the arrow. (Note: for an arrow that scales with zoom, please consider
|
|
742
|
+
the :func:`line3d` function with the option ``arrow_head=True``)::
|
|
743
|
+
|
|
744
|
+
sage: arrow3d((0,0,0), (1,1,1), width=1)
|
|
745
|
+
Graphics3d Object
|
|
746
|
+
|
|
747
|
+
.. PLOT::
|
|
748
|
+
|
|
749
|
+
sphinx_plot(arrow3d((0,0,0), (1,1,1), width=1))
|
|
750
|
+
|
|
751
|
+
TESTS:
|
|
752
|
+
|
|
753
|
+
If the arrow is too long, the shaft and part of the head is cut off. ::
|
|
754
|
+
|
|
755
|
+
sage: a = arrow3d((0,0,0), (0,0,0.5), head_len=1)
|
|
756
|
+
sage: len(a.all)
|
|
757
|
+
1
|
|
758
|
+
sage: type(a.all[0])
|
|
759
|
+
<... 'sage.plot.plot3d.shapes.Cone'>
|
|
760
|
+
|
|
761
|
+
Arrows are always constructed pointing up in the z direction from
|
|
762
|
+
the origin, and then rotated/translated into place. This works for
|
|
763
|
+
every arrow direction except the -z direction. We take care of the
|
|
764
|
+
anomaly by testing to see if the arrow should point in the -z
|
|
765
|
+
direction, and if it should, just scaling the constructed arrow by
|
|
766
|
+
-1 (i.e., every point is sent to its negative). The scaled arrow
|
|
767
|
+
then points downwards. The doctest just tests that the scale of -1
|
|
768
|
+
is applied to the arrow. ::
|
|
769
|
+
|
|
770
|
+
sage: a = arrow3d((0,0,0), (0,0,-1))
|
|
771
|
+
sage: a.all[0].get_transformation().transform_point((0,0,1))
|
|
772
|
+
(0.0, 0.0, -1.0)
|
|
773
|
+
"""
|
|
774
|
+
if radius is None:
|
|
775
|
+
radius = width/50.0
|
|
776
|
+
if head_radius is None:
|
|
777
|
+
head_radius = 3*radius
|
|
778
|
+
if head_len is None:
|
|
779
|
+
head_len = 3*head_radius
|
|
780
|
+
start = vector(RDF, start, sparse=False)
|
|
781
|
+
end = vector(RDF, end, sparse=False)
|
|
782
|
+
zaxis = vector(RDF, (0,0,1), sparse=False)
|
|
783
|
+
diff = end - start
|
|
784
|
+
length = sqrt(diff.dot_product(diff))
|
|
785
|
+
if length <= head_len:
|
|
786
|
+
arrow = Cone(head_radius*length/head_len, length, **kwds)
|
|
787
|
+
else:
|
|
788
|
+
arrow = Cylinder(radius, length-head_len, **kwds) \
|
|
789
|
+
+ Cone(head_radius, head_len, **kwds).translate(0, 0, length-head_len)
|
|
790
|
+
axis = zaxis.cross_product(diff)
|
|
791
|
+
if axis == 0:
|
|
792
|
+
if diff[2] >= 0:
|
|
793
|
+
return arrow.translate(start)
|
|
794
|
+
else:
|
|
795
|
+
return arrow.scale(-1).translate(start)
|
|
796
|
+
else:
|
|
797
|
+
theta = -acos(diff[2]/length)
|
|
798
|
+
return arrow.rotate(axis, theta).translate(start)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
cdef class Sphere(ParametricSurface):
|
|
802
|
+
"""
|
|
803
|
+
This class represents a sphere centered at the origin.
|
|
804
|
+
|
|
805
|
+
EXAMPLES::
|
|
806
|
+
|
|
807
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
808
|
+
sage: Sphere(3)
|
|
809
|
+
Graphics3d Object
|
|
810
|
+
|
|
811
|
+
.. PLOT::
|
|
812
|
+
|
|
813
|
+
from sage.plot.plot3d.shapes import Sphere
|
|
814
|
+
sphinx_plot(Sphere(3))
|
|
815
|
+
|
|
816
|
+
Plot with ``aspect_ratio=1`` to see it unsquashed::
|
|
817
|
+
|
|
818
|
+
sage: S = Sphere(3, color='blue') + Sphere(2, color='red').translate(0,3,0)
|
|
819
|
+
sage: S.show(aspect_ratio=1)
|
|
820
|
+
|
|
821
|
+
.. PLOT::
|
|
822
|
+
|
|
823
|
+
from sage.plot.plot3d.shapes import Sphere
|
|
824
|
+
S = Sphere(3, color='blue') + Sphere(2, color='red').translate(0,3,0)
|
|
825
|
+
sphinx_plot(S)
|
|
826
|
+
|
|
827
|
+
Scale to get an ellipsoid::
|
|
828
|
+
|
|
829
|
+
sage: S = Sphere(1).scale(1,2,1/2)
|
|
830
|
+
sage: S.show(aspect_ratio=1)
|
|
831
|
+
|
|
832
|
+
.. PLOT::
|
|
833
|
+
|
|
834
|
+
from sage.plot.plot3d.shapes import Sphere
|
|
835
|
+
S = Sphere(1).scale(1,2,1/2)
|
|
836
|
+
sphinx_plot(S)
|
|
837
|
+
"""
|
|
838
|
+
def __init__(self, radius, **kwds):
|
|
839
|
+
"""
|
|
840
|
+
TESTS::
|
|
841
|
+
|
|
842
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
843
|
+
sage: Sphere(3)
|
|
844
|
+
Graphics3d Object
|
|
845
|
+
"""
|
|
846
|
+
ParametricSurface.__init__(self, **kwds)
|
|
847
|
+
self.radius = radius
|
|
848
|
+
|
|
849
|
+
def bounding_box(self):
|
|
850
|
+
"""
|
|
851
|
+
Return the bounding box that contains this sphere.
|
|
852
|
+
|
|
853
|
+
EXAMPLES::
|
|
854
|
+
|
|
855
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
856
|
+
sage: Sphere(3).bounding_box()
|
|
857
|
+
((-3.0, -3.0, -3.0), (3.0, 3.0, 3.0))
|
|
858
|
+
"""
|
|
859
|
+
return ((-self.radius, -self.radius, -self.radius),
|
|
860
|
+
(self.radius, self.radius, self.radius))
|
|
861
|
+
|
|
862
|
+
def x3d_geometry(self):
|
|
863
|
+
"""
|
|
864
|
+
EXAMPLES::
|
|
865
|
+
|
|
866
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
867
|
+
sage: Sphere(12).x3d_geometry()
|
|
868
|
+
"<Sphere radius='12.0'/>"
|
|
869
|
+
"""
|
|
870
|
+
return "<Sphere radius='%s'/>" % (self.radius)
|
|
871
|
+
|
|
872
|
+
def tachyon_repr(self, render_params):
|
|
873
|
+
r"""
|
|
874
|
+
Tachyon can natively handle spheres. Ellipsoids rendering is done
|
|
875
|
+
as a parametric surface.
|
|
876
|
+
|
|
877
|
+
EXAMPLES::
|
|
878
|
+
|
|
879
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
880
|
+
sage: S = Sphere(2)
|
|
881
|
+
sage: S.tachyon_repr(S.default_render_params())
|
|
882
|
+
'Sphere center 0 0 0 Rad 2.0 texture...'
|
|
883
|
+
sage: S.translate(1, 2, 3).scale(3).tachyon_repr(S.default_render_params())
|
|
884
|
+
[['Sphere center 3.0 6.0 9.0 Rad 6.0 texture...']]
|
|
885
|
+
sage: S.scale(1,1/2,1/4).tachyon_repr(S.default_render_params())
|
|
886
|
+
[['TRI V0 0 0 -0.5 V1 0.308116 0.0271646 -0.493844 V2 0.312869 0 -0.493844',
|
|
887
|
+
'texture...',
|
|
888
|
+
...
|
|
889
|
+
'TRI V0 0.308116 -0.0271646 0.493844 V1 0.312869 0 0.493844 V2 0 0 0.5',
|
|
890
|
+
'texture...']]
|
|
891
|
+
"""
|
|
892
|
+
transform = render_params.transform
|
|
893
|
+
if not (transform is None or transform.is_uniform()):
|
|
894
|
+
return ParametricSurface.tachyon_repr(self, render_params)
|
|
895
|
+
|
|
896
|
+
if transform is None:
|
|
897
|
+
cen = (0,0,0)
|
|
898
|
+
rad = self.radius
|
|
899
|
+
else:
|
|
900
|
+
cen = transform.transform_point((0,0,0))
|
|
901
|
+
radv = transform.transform_vector((self.radius,0,0))
|
|
902
|
+
rad = sqrt(sum([x*x for x in radv]))
|
|
903
|
+
return "Sphere center %s %s %s Rad %s %s" % (cen[0], cen[1], cen[2], rad, self.texture.id)
|
|
904
|
+
|
|
905
|
+
def jmol_repr(self, render_params):
|
|
906
|
+
r"""
|
|
907
|
+
EXAMPLES::
|
|
908
|
+
|
|
909
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
910
|
+
|
|
911
|
+
Jmol has native code for handling spheres::
|
|
912
|
+
|
|
913
|
+
sage: S = Sphere(2)
|
|
914
|
+
sage: S.jmol_repr(S.default_render_params())
|
|
915
|
+
['isosurface sphere_1 center {0 0 0} sphere 2.0\ncolor isosurface [102,102,255]']
|
|
916
|
+
sage: S.translate(10, 100, 1000).jmol_repr(S.default_render_params())
|
|
917
|
+
[['isosurface sphere_1 center {10.0 100.0 1000.0} sphere 2.0\ncolor isosurface [102,102,255]']]
|
|
918
|
+
|
|
919
|
+
It cannot natively handle ellipsoids::
|
|
920
|
+
|
|
921
|
+
sage: Sphere(1).scale(2, 3, 4).jmol_repr(S.testing_render_params())
|
|
922
|
+
[['pmesh obj_2 "obj_2.pmesh"\ncolor pmesh [102,102,255]']]
|
|
923
|
+
|
|
924
|
+
Small spheres need extra hints to render well::
|
|
925
|
+
|
|
926
|
+
sage: Sphere(.01).jmol_repr(S.default_render_params())
|
|
927
|
+
['isosurface sphere_1 resolution 100 center {0 0 0} sphere 0.01\ncolor isosurface [102,102,255]']
|
|
928
|
+
"""
|
|
929
|
+
name = render_params.unique_name('sphere')
|
|
930
|
+
transform = render_params.transform
|
|
931
|
+
if not (transform is None or transform.is_uniform()):
|
|
932
|
+
return ParametricSurface.jmol_repr(self, render_params)
|
|
933
|
+
|
|
934
|
+
if transform is None:
|
|
935
|
+
cen = (0,0,0)
|
|
936
|
+
rad = self.radius
|
|
937
|
+
else:
|
|
938
|
+
cen = transform.transform_point((0,0,0))
|
|
939
|
+
radv = transform.transform_vector((self.radius,0,0))
|
|
940
|
+
rad = sqrt(sum([x*x for x in radv]))
|
|
941
|
+
if rad < 0.5:
|
|
942
|
+
res = "resolution %s" % min(int(7/rad), 100)
|
|
943
|
+
else:
|
|
944
|
+
res = ""
|
|
945
|
+
return ["isosurface %s %s center {%s %s %s} sphere %s\n%s" % (name, res, cen[0], cen[1], cen[2], rad, self.texture.jmol_str("isosurface"))]
|
|
946
|
+
|
|
947
|
+
def get_grid(self, double ds):
|
|
948
|
+
"""
|
|
949
|
+
Return the range of variables to be evaluated on to render as a
|
|
950
|
+
parametric surface.
|
|
951
|
+
|
|
952
|
+
EXAMPLES::
|
|
953
|
+
|
|
954
|
+
sage: from sage.plot.plot3d.shapes import Sphere
|
|
955
|
+
sage: Sphere(1).get_grid(100)
|
|
956
|
+
([-10.0, ..., 10.0],
|
|
957
|
+
[0.0, ..., 3.141592653589793, ..., 0.0])
|
|
958
|
+
"""
|
|
959
|
+
cdef int u_res, v_res
|
|
960
|
+
u_res = min(max(int(M_PI*self.radius/ds), 6), 20)
|
|
961
|
+
v_res = min(max(int(2*M_PI * self.radius/ds), 6), 36)
|
|
962
|
+
urange = [-10.0] + [M_PI * k/u_res - M_PI/2 for k in range(1, u_res)] + [10.0]
|
|
963
|
+
vrange = [2*M_PI * k/v_res for k in range(v_res)] + [0.0]
|
|
964
|
+
return urange, vrange
|
|
965
|
+
|
|
966
|
+
cdef int eval_c(self, point_c *res, double u, double v) except -1:
|
|
967
|
+
if u == -10:
|
|
968
|
+
res.x, res.y, res.z = 0, 0, -self.radius
|
|
969
|
+
elif u == 10:
|
|
970
|
+
res.x, res.y, res.z = 0, 0, self.radius
|
|
971
|
+
else:
|
|
972
|
+
res.x = self.radius * cos(v) * cos(u)
|
|
973
|
+
res.y = self.radius * sin(v) * cos(u)
|
|
974
|
+
res.z = self.radius * sin(u)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
cdef class Torus(ParametricSurface):
|
|
978
|
+
"""
|
|
979
|
+
INPUT:
|
|
980
|
+
|
|
981
|
+
- ``R`` -- (default: ``1``) outer radius
|
|
982
|
+
- ``r`` -- (default: ``.3``) inner radius
|
|
983
|
+
|
|
984
|
+
OUTPUT: a 3d torus
|
|
985
|
+
|
|
986
|
+
EXAMPLES::
|
|
987
|
+
|
|
988
|
+
sage: from sage.plot.plot3d.shapes import Torus
|
|
989
|
+
sage: Torus(1, .2).show(aspect_ratio=1)
|
|
990
|
+
|
|
991
|
+
.. PLOT::
|
|
992
|
+
|
|
993
|
+
from sage.plot.plot3d.shapes import Torus
|
|
994
|
+
sphinx_plot(Torus(1, .2))
|
|
995
|
+
|
|
996
|
+
::
|
|
997
|
+
|
|
998
|
+
sage: Torus(1, .7, color='red').show(aspect_ratio=1)
|
|
999
|
+
|
|
1000
|
+
.. PLOT::
|
|
1001
|
+
|
|
1002
|
+
from sage.plot.plot3d.shapes import Torus
|
|
1003
|
+
sphinx_plot(Torus(1, .7, color='red'))
|
|
1004
|
+
|
|
1005
|
+
A rubberband ball::
|
|
1006
|
+
|
|
1007
|
+
sage: show(sum(Torus(1, .03, color=(1, t/30.0, 0)).rotate((1,1,1), t)
|
|
1008
|
+
....: for t in range(30)))
|
|
1009
|
+
|
|
1010
|
+
.. PLOT::
|
|
1011
|
+
|
|
1012
|
+
from sage.plot.plot3d.shapes import Torus
|
|
1013
|
+
sphinx_plot(sum([Torus(1, .03, color=(1, t/30.0, 0)).rotate((1,1,1),t) for t in range(30)]))
|
|
1014
|
+
|
|
1015
|
+
Mmm... doughnuts::
|
|
1016
|
+
|
|
1017
|
+
sage: D = Torus(1, .4, color=(.5, .3, .2))
|
|
1018
|
+
sage: D += Torus(1, .3, color='yellow').translate(0, 0, .15)
|
|
1019
|
+
sage: G = sum(D.translate(RDF.random_element(-.2, .2),
|
|
1020
|
+
....: RDF.random_element(-.2, .2),
|
|
1021
|
+
....: .8*t)
|
|
1022
|
+
....: for t in range(10))
|
|
1023
|
+
sage: G.show(aspect_ratio=1, frame=False)
|
|
1024
|
+
|
|
1025
|
+
.. PLOT::
|
|
1026
|
+
|
|
1027
|
+
from sage.plot.plot3d.shapes import Torus
|
|
1028
|
+
D = Torus(1, .4, color=(.5, .3, .2)) + Torus(1, .3, color='yellow').translate(0, 0, .15)
|
|
1029
|
+
G = sum(D.translate(RDF.random_element(-.2, .2), RDF.random_element(-.2, .2), .8*t) for t in range(10))
|
|
1030
|
+
sphinx_plot(G)
|
|
1031
|
+
"""
|
|
1032
|
+
def __init__(self, R=1, r=.3, **kwds):
|
|
1033
|
+
"""
|
|
1034
|
+
TESTS::
|
|
1035
|
+
|
|
1036
|
+
sage: from sage.plot.plot3d.shapes import Torus
|
|
1037
|
+
sage: T = Torus(1, .5)
|
|
1038
|
+
"""
|
|
1039
|
+
ParametricSurface.__init__(self, None, **kwds)
|
|
1040
|
+
self.R = R
|
|
1041
|
+
self.r = r
|
|
1042
|
+
|
|
1043
|
+
def get_grid(self, ds):
|
|
1044
|
+
"""
|
|
1045
|
+
Return the range of variables to be evaluated on to render as a
|
|
1046
|
+
parametric surface.
|
|
1047
|
+
|
|
1048
|
+
EXAMPLES::
|
|
1049
|
+
|
|
1050
|
+
sage: from sage.plot.plot3d.shapes import Torus
|
|
1051
|
+
sage: Torus(2, 1).get_grid(100)
|
|
1052
|
+
([0.0, -1.047..., -3.141592653589793, ..., 0.0],
|
|
1053
|
+
[0.0, 1.047..., 3.141592653589793, ..., 0.0])
|
|
1054
|
+
"""
|
|
1055
|
+
cdef int k, u_divs, v_divs
|
|
1056
|
+
u_divs = min(max(int(4*M_PI * self.R/ds), 6), 37)
|
|
1057
|
+
v_divs = min(max(int(4*M_PI * self.r/ds), 6), 37)
|
|
1058
|
+
urange = [0.0] + [-2*M_PI * k/u_divs for k in range(1, u_divs)] + [0.0]
|
|
1059
|
+
vrange = [ 2*M_PI * k/v_divs for k in range(v_divs)] + [0.0]
|
|
1060
|
+
return urange, vrange
|
|
1061
|
+
|
|
1062
|
+
cdef int eval_c(self, point_c *res, double u, double v) except -1:
|
|
1063
|
+
res.x = (self.R+self.r*sin(v))*sin(u)
|
|
1064
|
+
res.y = (self.R+self.r*sin(v))*cos(u)
|
|
1065
|
+
res.z = self.r*cos(v)
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
class Text(PrimitiveObject):
|
|
1069
|
+
"""
|
|
1070
|
+
A text label attached to a point in 3d space. It always starts at the
|
|
1071
|
+
origin, translate it to move it elsewhere.
|
|
1072
|
+
|
|
1073
|
+
EXAMPLES::
|
|
1074
|
+
|
|
1075
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1076
|
+
sage: Text("Just a lonely label.")
|
|
1077
|
+
Graphics3d Object
|
|
1078
|
+
|
|
1079
|
+
.. PLOT::
|
|
1080
|
+
|
|
1081
|
+
from sage.plot.plot3d.shapes import Text
|
|
1082
|
+
sphinx_plot(Text("Just a lonely label. "))
|
|
1083
|
+
|
|
1084
|
+
::
|
|
1085
|
+
|
|
1086
|
+
sage: pts = [(RealField(10)^3).random_element() for k in range(20)]
|
|
1087
|
+
sage: sum(Text(str(P)).translate(P) for P in pts)
|
|
1088
|
+
Graphics3d Object
|
|
1089
|
+
|
|
1090
|
+
.. PLOT::
|
|
1091
|
+
|
|
1092
|
+
from sage.plot.plot3d.shapes import Text
|
|
1093
|
+
pts = [(RealField(10)**3).random_element() for k in range(20)]
|
|
1094
|
+
sphinx_plot(sum(Text(str(P)).translate(P) for P in pts))
|
|
1095
|
+
"""
|
|
1096
|
+
def __init__(self, string, **kwds):
|
|
1097
|
+
"""
|
|
1098
|
+
TESTS::
|
|
1099
|
+
|
|
1100
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1101
|
+
sage: T = Text("Hi")
|
|
1102
|
+
"""
|
|
1103
|
+
PrimitiveObject.__init__(self, **kwds)
|
|
1104
|
+
self.string = string
|
|
1105
|
+
self._set_extra_kwds(kwds)
|
|
1106
|
+
|
|
1107
|
+
def x3d_geometry(self):
|
|
1108
|
+
"""
|
|
1109
|
+
EXAMPLES::
|
|
1110
|
+
|
|
1111
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1112
|
+
sage: Text("Hi").x3d_geometry()
|
|
1113
|
+
"<Text string='Hi' solid='true'/>"
|
|
1114
|
+
"""
|
|
1115
|
+
return "<Text string='%s' solid='true'/>" % self.string
|
|
1116
|
+
|
|
1117
|
+
def obj_repr(self, render_params):
|
|
1118
|
+
"""
|
|
1119
|
+
The obj file format does not support text strings::
|
|
1120
|
+
|
|
1121
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1122
|
+
sage: Text("Hi").obj_repr(None)
|
|
1123
|
+
''
|
|
1124
|
+
"""
|
|
1125
|
+
return ''
|
|
1126
|
+
|
|
1127
|
+
def tachyon_repr(self, render_params):
|
|
1128
|
+
"""
|
|
1129
|
+
Strings are not yet supported in Tachyon, so we ignore them for now::
|
|
1130
|
+
|
|
1131
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1132
|
+
sage: Text("Hi").tachyon_repr(None)
|
|
1133
|
+
''
|
|
1134
|
+
"""
|
|
1135
|
+
return ''
|
|
1136
|
+
# Text in Tachyon not implemented yet.
|
|
1137
|
+
# I have no idea what the code below is supposed to do.
|
|
1138
|
+
## transform = render_params.transform
|
|
1139
|
+
## if not (transform is None or transform.is_uniform()):
|
|
1140
|
+
## return ParametricSurface.tachyon_repr(self, render_params)
|
|
1141
|
+
##
|
|
1142
|
+
## if transform is None:
|
|
1143
|
+
## cen = (0,0,0)
|
|
1144
|
+
## rad = self.radius
|
|
1145
|
+
## else:
|
|
1146
|
+
## cen = transform.transform_point((0,0,0))
|
|
1147
|
+
## radv = transform.transform_vector((self.radius,0,0))
|
|
1148
|
+
## rad = sqrt(sum([x*x for x in radv]))
|
|
1149
|
+
## return "Sphere center %s %s %s Rad %s %s" % (cen[0], cen[1], cen[2], rad, self.texture.id)
|
|
1150
|
+
|
|
1151
|
+
def jmol_repr(self, render_params):
|
|
1152
|
+
"""
|
|
1153
|
+
Labels in jmol must be attached to atoms.
|
|
1154
|
+
|
|
1155
|
+
EXAMPLES::
|
|
1156
|
+
|
|
1157
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1158
|
+
sage: T = Text("Hi")
|
|
1159
|
+
sage: T.jmol_repr(T.testing_render_params())
|
|
1160
|
+
['select atomno = 1', 'color atom [102,102,255]', 'label "Hi"']
|
|
1161
|
+
sage: T = Text("Hi").translate(-1, 0, 0) + Text("Bye").translate(1, 0, 0)
|
|
1162
|
+
sage: T.jmol_repr(T.testing_render_params())
|
|
1163
|
+
[[['select atomno = 1', 'color atom [102,102,255]', 'label "Hi"']],
|
|
1164
|
+
[['select atomno = 2', 'color atom [102,102,255]', 'label "Bye"']]]
|
|
1165
|
+
"""
|
|
1166
|
+
cen = (0, 0, 0)
|
|
1167
|
+
if render_params.transform is not None:
|
|
1168
|
+
cen = render_params.transform.transform_point(cen)
|
|
1169
|
+
render_params.atom_list.append(cen)
|
|
1170
|
+
atom_no = len(render_params.atom_list)
|
|
1171
|
+
return ['select atomno = %s' % atom_no,
|
|
1172
|
+
self.get_texture().jmol_str("atom"),
|
|
1173
|
+
'label "%s"' % self.string] # .replace('\n', '|')]
|
|
1174
|
+
|
|
1175
|
+
def threejs_repr(self, render_params):
|
|
1176
|
+
r"""
|
|
1177
|
+
Return representation of the text suitable for plotting in three.js.
|
|
1178
|
+
|
|
1179
|
+
EXAMPLES::
|
|
1180
|
+
|
|
1181
|
+
sage: T = text3d("Hi", (1, 2, 3), color='red', fontfamily='serif',
|
|
1182
|
+
....: fontweight='bold', fontstyle='italic', fontsize=20,
|
|
1183
|
+
....: opacity=0.5)
|
|
1184
|
+
sage: T.threejs_repr(T.default_render_params())
|
|
1185
|
+
[('text',
|
|
1186
|
+
{'color': '#ff0000',
|
|
1187
|
+
'fontFamily': ['serif'],
|
|
1188
|
+
'fontSize': 20.0,
|
|
1189
|
+
'fontStyle': 'italic',
|
|
1190
|
+
'fontWeight': 'bold',
|
|
1191
|
+
'opacity': 0.5,
|
|
1192
|
+
'text': 'Hi',
|
|
1193
|
+
'x': 1.0,
|
|
1194
|
+
'y': 2.0,
|
|
1195
|
+
'z': 3.0})]
|
|
1196
|
+
|
|
1197
|
+
TESTS:
|
|
1198
|
+
|
|
1199
|
+
When created directly via the ``Text`` constructor instead of ``text3d``,
|
|
1200
|
+
the text is located at the origin::
|
|
1201
|
+
|
|
1202
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1203
|
+
sage: T = Text("Hi")
|
|
1204
|
+
sage: T.threejs_repr(T.default_render_params())
|
|
1205
|
+
[('text',
|
|
1206
|
+
{'color': '#6666ff',
|
|
1207
|
+
'fontFamily': ['monospace'],
|
|
1208
|
+
'fontSize': 14.0,
|
|
1209
|
+
'fontStyle': 'normal',
|
|
1210
|
+
'fontWeight': 'normal',
|
|
1211
|
+
'opacity': 1.0,
|
|
1212
|
+
'text': 'Hi',
|
|
1213
|
+
'x': 0.0,
|
|
1214
|
+
'y': 0.0,
|
|
1215
|
+
'z': 0.0})]
|
|
1216
|
+
"""
|
|
1217
|
+
center = (float(0), float(0), float(0))
|
|
1218
|
+
if render_params.transform is not None:
|
|
1219
|
+
center = render_params.transform.transform_point(center)
|
|
1220
|
+
color = '#' + str(self.texture.hex_rgb())
|
|
1221
|
+
string = str(self.string)
|
|
1222
|
+
text = _validate_threejs_text_style(self._extra_kwds)
|
|
1223
|
+
text.update(dict(text=string, x=center[0], y=center[1], z=center[2], color=color))
|
|
1224
|
+
return [('text', text)]
|
|
1225
|
+
|
|
1226
|
+
def bounding_box(self):
|
|
1227
|
+
"""
|
|
1228
|
+
Text labels have no extent::
|
|
1229
|
+
|
|
1230
|
+
sage: from sage.plot.plot3d.shapes import Text
|
|
1231
|
+
sage: Text("Hi").bounding_box()
|
|
1232
|
+
((0, 0, 0), (0, 0, 0))
|
|
1233
|
+
"""
|
|
1234
|
+
return (0,0,0), (0,0,0)
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def _validate_threejs_text_style(style):
|
|
1238
|
+
"""
|
|
1239
|
+
Validate a combination of text styles for use in the Three.js viewer.
|
|
1240
|
+
|
|
1241
|
+
INPUT:
|
|
1242
|
+
|
|
1243
|
+
- ``style`` -- dictionary optionally containing keys: 'color', 'fontSize',
|
|
1244
|
+
'fontFamily', 'fontStyle', 'fontWeight', and 'opacity'
|
|
1245
|
+
|
|
1246
|
+
OUTPUT:
|
|
1247
|
+
|
|
1248
|
+
A corrected version of the dict, having printed out warning messages for
|
|
1249
|
+
any problems found
|
|
1250
|
+
|
|
1251
|
+
TESTS:
|
|
1252
|
+
|
|
1253
|
+
Default values::
|
|
1254
|
+
|
|
1255
|
+
sage: from sage.plot.plot3d.shapes import _validate_threejs_text_style as validate
|
|
1256
|
+
sage: validate(dict())
|
|
1257
|
+
{'color': '#000000',
|
|
1258
|
+
'fontFamily': ['monospace'],
|
|
1259
|
+
'fontSize': 14.0,
|
|
1260
|
+
'fontStyle': 'normal',
|
|
1261
|
+
'fontWeight': 'normal',
|
|
1262
|
+
'opacity': 1.0}
|
|
1263
|
+
|
|
1264
|
+
Color by name or by HTML hex code::
|
|
1265
|
+
|
|
1266
|
+
sage: validate(dict(color='red'))
|
|
1267
|
+
{'color': '#ff0000',...}
|
|
1268
|
+
sage: validate(dict(color='#ff0000'))
|
|
1269
|
+
{'color': '#ff0000',...}
|
|
1270
|
+
sage: validate(dict(color='octarine'))
|
|
1271
|
+
...UserWarning: invalid color: octarine...
|
|
1272
|
+
|
|
1273
|
+
Font size in absolute units or in percentage/keyword relative to default::
|
|
1274
|
+
|
|
1275
|
+
sage: validate(dict(fontsize=20.5))
|
|
1276
|
+
{...'fontSize': 20.5...}
|
|
1277
|
+
sage: validate(dict(fontsize="200%"))
|
|
1278
|
+
{...'fontSize': 28...}
|
|
1279
|
+
sage: validate(dict(fontsize="x%"))
|
|
1280
|
+
...UserWarning: invalid fontsize: x%...
|
|
1281
|
+
sage: validate(dict(fontsize="large"))
|
|
1282
|
+
{...'fontSize': 16.8...}
|
|
1283
|
+
sage: validate(dict(fontsize="ginormous"))
|
|
1284
|
+
...UserWarning: unknown fontsize: ginormous...
|
|
1285
|
+
|
|
1286
|
+
Font family as list or comma-delimited string::
|
|
1287
|
+
|
|
1288
|
+
sage: validate(dict(fontfamily=[" Times New Roman ", " Georgia", "serif "]))
|
|
1289
|
+
{...'fontFamily': ['Times New Roman', 'Georgia', 'serif']...}
|
|
1290
|
+
sage: validate(dict(fontfamily=" Times New Roman , Georgia,serif "))
|
|
1291
|
+
{...'fontFamily': ['Times New Roman', 'Georgia', 'serif']...}
|
|
1292
|
+
|
|
1293
|
+
Font style keywords including the special syntax for 'oblique' angle::
|
|
1294
|
+
|
|
1295
|
+
sage: validate(dict(fontstyle='italic'))
|
|
1296
|
+
{...'fontStyle': 'italic'...}
|
|
1297
|
+
sage: validate(dict(fontstyle='oblique 30deg'))
|
|
1298
|
+
{...'fontStyle': 'oblique 30deg'...}
|
|
1299
|
+
sage: validate(dict(fontstyle='garrish'))
|
|
1300
|
+
...UserWarning: unknown style: garrish...
|
|
1301
|
+
|
|
1302
|
+
Font weight as keyword or integer::
|
|
1303
|
+
|
|
1304
|
+
sage: validate(dict(fontweight='bold'))
|
|
1305
|
+
{...'fontWeight': 'bold'...}
|
|
1306
|
+
sage: validate(dict(fontweight=500))
|
|
1307
|
+
{...'fontWeight': 500...}
|
|
1308
|
+
sage: validate(dict(fontweight='roman'))
|
|
1309
|
+
{...'fontWeight': 500...}
|
|
1310
|
+
sage: validate(dict(fontweight='bold & beautiful'))
|
|
1311
|
+
...UserWarning: unknown fontweight: bold & beautiful...
|
|
1312
|
+
|
|
1313
|
+
Opacity::
|
|
1314
|
+
|
|
1315
|
+
sage: validate(dict(opacity=0.5))
|
|
1316
|
+
{...'opacity': 0.5}
|
|
1317
|
+
"""
|
|
1318
|
+
default_color = '#000000' # black
|
|
1319
|
+
color = style.get('color', default_color)
|
|
1320
|
+
from sage.plot.plot3d.texture import Texture
|
|
1321
|
+
try:
|
|
1322
|
+
texture = Texture(color=color)
|
|
1323
|
+
except ValueError:
|
|
1324
|
+
import warnings
|
|
1325
|
+
warnings.warn(f"invalid color: {color}, using: {default_color}")
|
|
1326
|
+
color = default_color
|
|
1327
|
+
else:
|
|
1328
|
+
color = '#' + str(texture.hex_rgb())
|
|
1329
|
+
|
|
1330
|
+
default_size = 14.0
|
|
1331
|
+
size = style.get('fontsize', default_size)
|
|
1332
|
+
try:
|
|
1333
|
+
size = float(size)
|
|
1334
|
+
except (TypeError, ValueError):
|
|
1335
|
+
scale = str(size).lower()
|
|
1336
|
+
if scale.endswith('%'):
|
|
1337
|
+
try:
|
|
1338
|
+
scale = float(scale[:-1]) / 100.0
|
|
1339
|
+
size = default_size * scale
|
|
1340
|
+
except ValueError:
|
|
1341
|
+
import warnings
|
|
1342
|
+
warnings.warn(f"invalid fontsize: {size}, using: {default_size}")
|
|
1343
|
+
size = default_size
|
|
1344
|
+
else:
|
|
1345
|
+
from matplotlib.font_manager import font_scalings
|
|
1346
|
+
try:
|
|
1347
|
+
size = default_size * font_scalings[scale]
|
|
1348
|
+
except KeyError:
|
|
1349
|
+
import warnings
|
|
1350
|
+
warnings.warn(f"unknown fontsize: {size}, using: {default_size}")
|
|
1351
|
+
size = default_size
|
|
1352
|
+
|
|
1353
|
+
font = style.get('fontfamily', ['monospace'])
|
|
1354
|
+
if isinstance(font, str):
|
|
1355
|
+
font = font.split(',')
|
|
1356
|
+
font = [str(f).strip() for f in font]
|
|
1357
|
+
|
|
1358
|
+
default_style = 'normal'
|
|
1359
|
+
fontstyle = str(style.get('fontstyle', default_style))
|
|
1360
|
+
if fontstyle not in ['normal', 'italic'] and not fontstyle.startswith('oblique'): # ex: oblique 30deg
|
|
1361
|
+
import warnings
|
|
1362
|
+
warnings.warn(f"unknown style: {fontstyle}, using: {default_style}")
|
|
1363
|
+
fontstyle = default_style
|
|
1364
|
+
|
|
1365
|
+
default_weight = 'normal'
|
|
1366
|
+
weight = style.get('fontweight', default_weight)
|
|
1367
|
+
if weight not in ['normal', 'bold']:
|
|
1368
|
+
try:
|
|
1369
|
+
weight = int(weight)
|
|
1370
|
+
except (TypeError, ValueError):
|
|
1371
|
+
from matplotlib.font_manager import weight_dict
|
|
1372
|
+
try:
|
|
1373
|
+
weight = weight_dict[weight]
|
|
1374
|
+
except KeyError:
|
|
1375
|
+
import warnings
|
|
1376
|
+
warnings.warn(f"unknown fontweight: {weight}, using: {default_weight}")
|
|
1377
|
+
weight = default_weight
|
|
1378
|
+
|
|
1379
|
+
opacity = float(style.get('opacity', 1.0))
|
|
1380
|
+
|
|
1381
|
+
return dict(color=color, fontSize=size, fontFamily=font, fontStyle=fontstyle,
|
|
1382
|
+
fontWeight=weight, opacity=opacity)
|