1
# Copyright (c) 1996, 1997, The Regents of the University of California.
2
# All rights reserved. See Legal.htm for full text and disclaimer.
4
# Nar, Numeric, and shapetest are all imported by graph:
8
from graftypes import *
10
class Graph3d ( Graph ) :
13
g = Graph3d ( <surface list>, ...keyword arguments...) will create
14
a three-dimensional graphics object consisting of several surfaces
15
plus a global environment for the object. It will accept one or
16
a list of Plotter objects or plotter_identifiers, or will try
17
to complete a generic connection of its own if asked to plot
18
without such a plotter specification.
19
<surface list> is one or a sequence of Surface objects.
20
The keyword arguments for Graph3d are:
22
plotter = <Plotter object> or a sequence of <Plotter object>s
23
if you have a plotter object or a sequence of them that
24
you want the curve to use when plotting itself. I
25
recommend against this; a curve can create its own
26
plotter if you don't give it one.
27
filename = <string> or a sequence of <string>s if you want to
28
connect to Narcisse with the named file(s). (The default
29
is " ".) Only one of the keywords 'plotter' or 'filename'
30
is allowed, and both are optional.
31
NOTE: the possibility of a sequence of file names or
32
plotters allows one to display Narcisse graphs on one's
33
own machine as well as one or more remote machines.
34
In Gist, the filename argument is the same as the
35
display argument (below).
36
display = <string> or a sequence of <string>s if you want to
37
display on the named hosts. The form of <string> is
38
the usual "hostname:server.screen". The purpose of
39
this argument is to allow you to continue without
40
exiting python if you have to open a Gist window
41
without the DISPLAY environment variable having been
42
set, or if you want to open Gist windows on more
44
titles = <value> where <value> is a string or a sequence
45
of up to four strings, giving the titles in the
46
order bottom, top, left, right.
47
title_colors = <value> where value is an integer or string
48
or a sequence of up to four integers or strings giving
49
the colors of the titles.
50
grid_type = <string> where "none" means no axis grid;
51
"axes" means a pair of axes with tick marks;
52
"wide" means a widely spaced 2d grid; and
53
"full" means a closely spaced 2d grid.
54
axis_labels = <value> where <value> is a string or
55
sequence of up to five strings representing
56
the labels of the x axis, the y axis, the z
57
axis, the c axis, and the right y axis.
58
For Gist, only the first three count, and
59
if necessary, will be truncated to a single
61
gnomon = (0|1) (Gist only). If 1, show the gnomon (a simple display
62
showing the orientation of the xyz axes of the object).
63
x_axis_label, y_axis_label, z_axis_label, c_axis_label, and
64
yr_axis_label may be used to change individual
66
axis_limits = <value> where <value> is a pair [xmin, xmax] or
67
a sequence of up to five pairs, where the second
68
would be the y limits, the third the z limits,
69
the fourth the c limits, and the fifth the right
71
x_axis_limits, y_axis_limits, z_axis_limits, c_axis_limits, and
72
yr_axis_limits may be used to change individual
74
x_factor, y_factor (defaults 1.0). A factor > 1.0 compresses
75
the graph by that factor in the specified direction
76
(relative to the plane of the graph). A factor < 1.0
77
would enlarge it in the specified direction. So far
78
available only in Gist.
79
axis_scales = <value> where <value> is a string or a sequence
80
of up to five strings, each of which is either "lin"
81
(linear scale) or "log" (logarithmic scale). Omitted
82
strings default to "lin". The order thay are
83
specified is x, y, z, c, and right y. If this
84
argument is missing, the existing axis scales
86
x_axis_scale, y_axis_scale, z_axis_scale, c_axis_scale, and
87
yr_axis_scale may be used to change individual
89
text = <value> where value is one or a sequence of strings
90
representing texts to be placed on the plot.
91
text_color = <value> where <value> is one or a sequence
92
of integer color numbers or strings (names of
93
common colors) giving colors for the texts.
94
text_size = <value> where <value> is one or a sequence of
95
integers giving (roughly) the number of characters
96
in a line on the graph. The larger this number, the
98
text_pos = <value> where <value> is a pair or a sequence of
99
reals between 0. and 1.0 giving the relative
100
position of the lower left corner of a text
101
in the graphics window.
102
Narcisse viewing angles:
103
phi = <integer value> specifies the angle that the line from
104
the view point to the origin makes with the positive
105
z axis. The angle is in degrees. (default 45)
106
theta = <integer value> specifies the angle made by the projection
107
of the line of view on the xy plane with the
108
positive x axis. The angle is in degrees. (default 45)
109
roll = <integer value> specifies the angle of rotation of the
110
graph around the line from the origin to the view point.
111
The angle is measured in the positive direction,
112
i. e., if your right thumb is aligned outwards along
113
the line from the origin to the view point, then
114
your fingers curl in the positive direction.
115
The angle is in degrees. Not implemented for
116
Gist graphics. (default 0)
118
theta = <integer value> specifies the angle that the z axis
119
makes with its projection onto the surface of the graph;
120
positive if the z axis points out of the screen,
121
negative if it points inwards. (default 30)
122
If the z axis is in the screen, then the x and y axes
123
will appear horizontal. Note: If the gnomon is turned on,
124
then axes in the plane of the graphics window or pointing
125
into it will have their labels highlighted.
126
phi = <integer value> specifies the angle through which the
127
x axis is rotated in the xy plane; positive if in the
128
direction of the y axis, negative otherwise. (default -45)
129
distance = <integer value> specifies the distance of the view
130
point from the origin. This is an integer between
131
0 and 20. 0 makes the distance infinite; otherwise
132
the smaller the number, the closer you are. This
133
number does not affect the size of the graph, but
134
rather the amount of distortion (on account of
135
perspective) in the picture (the closer you are,
136
the more distortion).
137
link = <value> Used to link surfaces of different 3d options.
138
normally all surfaces in a graph will have the same
139
3d options. This value should be set to 1 if you want to
140
graph two or more surfaces with different 3d options.
141
otherwise multiple surface graphs will appear with the
142
options of the last surface specified.
144
connect = <value> set to 1 for graphs of more than one surface
145
to provide better hidden line removal. Must not be used
148
sync = <value> set to 1 to synchronize with Narcisse before
149
plotting the next graph. Keeps graphs sent in rapid
150
succession from becoming garbled. Defaults to 1; set to
151
0 if you don't have a timing problem.
153
Lighting variables (so far available only in Gist):
154
ambient = <value> is a light level (arbitrary units)
155
that is added to every surface independent of its orientation.
156
diffuse = <value> is a light level which is proportional
157
to cos(theta), where theta is the angle between the surface
158
normal and the viewing direction, so that surfaces directly
159
facing the viewer are bright, while surfaces viewed edge on are
160
unlit (and surfaces facing away, if drawn, are shaded as if they
165
specular = S_LEVEL is a light level proportional to a high
166
power spower=N of 1+cos(alpha), where alpha is the angle between
167
the specular reflection angle and the viewing direction. The light
168
source for the calculation of alpha lies in the direction XYZ (a
169
3 element vector) in the viewer's coordinate system at infinite
170
distance. You can have ns light sources by making S_LEVEL, N, and
171
XYZ (or any combination) be vectors of length ns (3-by-ns in the
173
z_scale = <real value>
174
if not equal to 1, affects the depth in z.
175
color_bar = 0 or 1 (1 enables plotting of a color bar on
176
any graphs for which it is meaningful (colored contour
177
plots, filled contour plots, cell arrays, filled
178
meshes and polygons).
179
color_bar_pos (ignored unless a color bar is actually plotted)
180
is a 2d array [ [xmin, ymin], [xmax, ymax]] specifying
181
where (in window coordinates) the diagonally opposite
182
corners of the color bar are to be placed.
183
(the z_contours_array and/or c_contours array can be used
184
to control precisely what is shown in the color bar.)
185
currently not implemented in Gist.
186
split = 0 or 1 (default 1) If on, causes the palette to be split
187
when both planes and isosurfaces are present in a graph,
188
so that isosurfaces are shaded according to current
189
light settings, while plane sections of the mesh are
190
colored according to a specified function.
191
Currently not implemented in Narcisse.
197
_color_card_dict = { "absolute" : 0 , "binary" : 1 ,
198
"bluegreen" : 2 , "default" : 3 , "negative" : 4 , "positive" : 5 ,
199
"rainbow" : 6 , "rainbowhls" : 7 , "random" : 8 , "redblue" : 9 ,
200
"redgreen" : 10 , "shifted" : 11 }
202
_axes = ["x", "y", "z", "c", "yr"]
204
_NotASurface = "NotASurface"
205
_LinkError = "LinkError"
206
_SurfaceSpecError = "SurfaceSpecError"
207
_SurfaceChangeError = "SurfaceChangeError"
209
# The following are actually used in Graph
210
_limits_keywords = ["x_axis_limits", "y_axis_limits", "z_axis_limits",
211
"c_axis_limits", "yr_axis_limits"]
212
_scale_keywords = ["x_axis_scale", "y_axis_scale", "z_axis_scale",
213
"c_axis_scale", "yr_axis_scale"]
214
_label_keywords = ["x_axis_label", "y_axis_label", "z_axis_label",
215
"c_axis_label", "yr_axis_label"]
218
def __init__ ( self , surface_list , *kwds , ** keywords ) :
219
if len ( kwds ) == 1 :
221
if is_scalar ( surface_list ) :
222
if surface_list.type () != SurfaceType and \
223
surface_list.type () != Mesh3dType and \
224
surface_list.type () != Slice3dType :
225
raise self._NotASurface , \
226
"Attempt to initialize with something that is not a surface, " + \
228
self._s = [surface_list]
230
for i in range ( len ( surface_list ) ) :
231
if surface_list[i].type () != SurfaceType and \
232
surface_list[i].type () != Mesh3dType and \
233
surface_list[i].type () != Slice3dType :
234
raise self._NotASurface , \
235
"Attempt to initialize with something that is not a surface, " \
237
self._s = surface_list
238
self._s_ln = len ( self._s )
239
if keywords.has_key ("x_factor") :
240
self._x_factor = keywords ["x_factor"]
243
if keywords.has_key ("y_factor") :
244
self._y_factor = keywords ["y_factor"]
247
if keywords.has_key ("link") :
248
if keywords.has_key ("connect") :
249
raise _LinkError , "Can't specify both link and connect."
250
self._link = keywords ["link"]
253
if keywords.has_key ("connect") :
254
self._connect = keywords ["connect"]
257
if keywords.has_key ("phi") :
258
self._phi = keywords ["phi"]
261
if keywords.has_key ("theta") :
262
self._theta = keywords ["theta"]
265
if keywords.has_key ("roll") :
266
self._roll = keywords ["roll"]
269
if keywords.has_key ("distance") :
270
self._distance = keywords ["distance"]
273
if keywords.has_key ("z_scale") :
274
self.z_scale = keywords ["z_scale"]
277
if keywords.has_key ("gnomon") :
278
self._gnomon = keywords ["gnomon"]
281
if keywords.has_key ( "color_card" ) :
282
self._color_card = keywords ["color_card"]
284
self._color_card = "default"
285
self._axis_limits = [[0., 0.], [0., 0.], [0., 0.], [0., 0.], [0., 0.]]
286
self._axis_scales = ["lin", "lin", "lin", "lin", "lin"]
287
self._axis_labels = ["X axis", "Y axis", "Z axis", "C axis", "YR axis"]
289
self.lighting_dict = {}
290
if keywords.has_key ("ambient") :
291
self.lighting_dict ["ambient"] = keywords ["ambient"]
293
self.lighting_dict ["ambient"] = None
294
if keywords.has_key ("diffuse") :
295
self.lighting_dict ["diffuse"] = keywords ["diffuse"]
297
self.lighting_dict ["diffuse"] = None
298
if keywords.has_key ("specular") :
299
self.lighting_dict ["specular"] = keywords ["specular"]
301
self.lighting_dict ["specular"] = None
302
if keywords.has_key ("spower") :
303
self.lighting_dict ["spower"] = keywords ["spower"]
305
self.lighting_dict ["spower"] = None
306
if keywords.has_key ("sdir") :
307
self.lighting_dict ["sdir"] = keywords ["sdir"]
309
self.lighting_dict ["sdir"] = None
310
if keywords.has_key ("style") :
311
self._style = keywords ["style"]
313
self._style = "nobox.gs"
314
if keywords.has_key ("grid_type") :
315
self._grid_type = keywords ["grid_type"]
317
self._grid_type = "none"
318
if keywords.has_key ( "opt_3d" ) :
319
self.opt_3d = keywords ["opt_3d"]
322
if keywords.has_key ( "mask" ) :
323
self.mask = keywords ["mask"]
326
if keywords.has_key ( "split" ) :
327
self._split = keywords ["split"]
330
# Everything else is Graph generic:
331
self._label_type = " "
332
Graph.__init__ ( self , keywords )
334
def new ( self , surface_list , ** keywords ) :
335
"""new ( surface_list, <keyword arguments> ) cleans out a Graph3d
336
and reinitializes it. This has the same argument list as
337
Graph3d. Do not change the plotter list or filename list (to
338
avoid the tedious on-and-off flickering of windows) unless
339
this is actually requested.
341
pl = self._plotter_list
342
fl = self._filename_list
343
self._plotter_list = []
344
self._filename_list = []
345
del self._s, self._s_ln, self._link, self._connect, self._phi, \
346
self._theta, self._roll, self._distance, self._titles, \
347
self._title_colors, self._text, self._text_color, self._text_size, \
348
self._sync, self._grid_type, self._axis_labels, self._axis_scales, \
350
self.__init__ ( surface_list , keywords )
351
if self._plotter_list == [] :
352
self._plotter_list = pl
353
self._filename_list = fl
355
def add ( self , surface ) :
356
""" add ( surface ) adds a surface with its characteristics to the
357
existing plot. Surfaces are numbered in the order that they are
358
added, beginning with 1.
360
if surface.type () != SurfaceType and surface.type () != Mesh3dType and \
361
surface_list[i].type () != Slice3dType :
362
raise self._NotASurface , \
363
"Attempt to add something not a surface, slice, or mesh."
364
self._s.append ( surface )
365
self._s_ln = len (self._s)
367
def delete ( self , n ) :
368
"""delete ( n ) deletes the nth surface from the Graph. Note
369
that surfaces are numbered beginning with 1.
371
DeleteError = "DeleteError"
372
if 1 <= n <= self._s_ln :
374
self._s_ln = len (self._s)
376
raise DeleteError , "There is no surface numbered " + `n` + \
377
" in the current graph, which has only " + `self._s_ln` + \
380
def set_surface_list (self, surface_list) :
382
self._s = surface_list
383
self._s_ln = len (surface_list)
386
def replace ( self , n , surface ) :
387
"""replace ( n , surface ) replaces the nth surface in the Graph
388
with the specified surface. Note that surfaces are numbered
391
if surface.type () != SurfaceType and surface.type () != Mesh3dType and \
392
surface.type () != Slice3dType :
393
raise self._NotASurface , \
394
"Attempt to add something not a surface, slice, or mesh."
395
ReplaceError = "ReplaceError"
396
if 1 <= n <= self._s_ln :
397
self._s [n-1] = surface
399
raise ReplaceError , "There is no surface numbered " + `n` + \
400
" in the surfacent graph, which has only " + `self._s_ln` + \
403
def change_plot ( self , ** keywords ) :
404
"""change_plot ( <keyword arguments> ) is used to change any Graph3d
405
characteristics except the surfaces being graphed. Use the add,
406
delete, and/or replace commands to do that. change_plot will
407
draw the graph without sending surface coordinates, unless
408
keyword send is 1. Generally, change_plot should be used when
409
the graph needs to be recomputed, and quick_plot when it does not.
410
change_plot does no error checking and does not convert user-friendly
411
names of colors and such into numbers.
413
for k in keywords.keys ():
414
if k == "surface" or k == "mesh" :
415
raise self._SurfaceChangeError, \
416
"Use add, delete, or replace to change surfaces in a graph."
417
setattr (self, "_" + k, keywords [k])
418
if "send" in keywords.keys ():
419
send = keywords ["send"]
422
self._send_coordinates = send
424
self._send_coordinates = 1
426
def quick_plot ( self , ** keywords ) :
427
"""quick_plot ( <keyword arguments> ) is used to change some Graph3d
428
characteristics which do not demand that the graph be recomputed.
429
You can change the characteristics of a surface in the graph by
430
specifying its number (surface = n) and any combination of the
431
traits color_card, opt_3d, mesh_type, or mask. Or you can change
432
such overall graph characteristics as titles, title_colors, text,
433
text_color, text_size, text_pos, color_card, grid_type, sync, theta,
434
phi, roll, and axis_labels.
436
The changes will be effected and the graph redrawn.
437
Things that you cannot change include axis limits and scales,
438
and the coordinates of a surface. Use change_plot if axis limits
439
and scales are among the things you want to change, and use add,
440
delete, or replace followed by a call to plot, if you wish to
442
quick_plot will not work right for link'ed surfaces. Once the
443
changes have been made, you will have to call plot.
445
ChangeError = "ChangeError"
446
PlottersNotStarted = "PlottersNotStarted"
447
if not self._plotters_started :
448
raise PlottersNotStarted , \
449
"quick_plot requires that all plotters have already been started."
450
if keywords.has_key ("opt_3d") :
451
self._opt_3d = keywords ["opt_3d"]
452
self.opt_3d_change = 1
453
if keywords.has_key ("mesh_type" ) :
454
self._mesh_type = keywords ["mesh_type"]
455
self.mesh_type_change = 1
456
if keywords.has_key ("mask") :
457
self._mask = keywords ["mask"]
459
if keywords.has_key ("color_card") :
460
if type ( keywords [ "color_card" ] ) == IntType :
461
self._color_card = keywords [ "color_card" ]
462
self.color_card_change = 1
463
elif self._color_card_dict.has_key \
464
( keywords [ "color_card" ] ) :
466
self._color_card_dict [keywords [ "color_card" ]]
467
self.color_card_change = 1
468
if keywords.has_key ( "surface" ) or keywords.has_key ( "mesh" ) :
469
if keywords.has_key ( "surface" ) :
470
self.n = keywords ["surface"]
472
self.n = keywords ["mesh"]
473
self.color_card_change = 0
474
self.opt_3d_change = 0
475
self.mesh_type_change = 0
477
if 1 <= self.n <= self._s_ln :
478
if keywords.has_key ("color_card") :
479
if type ( keywords [ "color_card" ] ) == IntType :
480
self._s [self.n - 1].color_card = keywords [ "color_card" ]
481
self.color_card_change = 1
482
elif self._color_card_dict.has_key \
483
( keywords [ "color_card" ] ) :
484
self._s [self.n - 1].color_card = \
485
self._color_card_dict [keywords [ "color_card" ]]
486
self.color_card_change = 1
488
raise ChangeError , "There is no surface numbered " + `n` + \
489
" in the current graph, which has only " + `self._s_ln` + \
491
if keywords.has_key ("grid_type") :
492
self._grid_type = keywords ["grid_type"]
493
if keywords.has_key ("sync") :
494
self._sync = keywords ["sync"]
495
if keywords.has_key ("theta") :
496
self._theta = keywords ["theta"]
497
if keywords.has_key ("phi") :
498
self._phi = keywords ["phi"]
499
if keywords.has_key ("roll") :
500
self._roll = keywords ["roll"]
501
if keywords.has_key ("ambient") :
502
self.lighting_dict ["ambient"] = keywords ["ambient"]
504
self.lighting_dict ["ambient"] = None
505
if keywords.has_key ("diffuse") :
506
self.lighting_dict ["diffuse"] = keywords ["diffuse"]
508
self.lighting_dict ["diffuse"] = None
509
if keywords.has_key ("specular") :
510
self.lighting_dict ["specular"] = keywords ["specular"]
512
self.lighting_dict ["specular"] = None
513
if keywords.has_key ("spower") :
514
self.lighting_dict ["spower"] = keywords ["spower"]
516
self.lighting_dict ["spower"] = None
517
if keywords.has_key ("sdir") :
518
self.lighting_dict ["sdir"] = keywords ["sdir"]
520
self.lighting_dict ["sdir"] = None
521
Graph.change ( self , keywords ) # Change generic traits
522
for ipl in range (len (self._plotter_list)) :
523
pl = self._plotter_list [ipl]
527
"""plot ( ) plots a 3d graph object. If the user has not by
528
now specified plotter(s) or filename(s) then a generic plotter
529
object will be created, if it is possible to find a local
532
self._init_plotter ( )
533
# grid_type means something different on 3d graphs
534
for ipl in range (len (self._plotter_list)) :
535
pl = self._plotter_list [ipl]
538
def move_light_source (self, ** keywords) :
539
"""move_light_source (nframes = 30, angle = 12) causes a
540
movie of the surrent graph to be shown, with the light source
541
rotating through angle degrees each frame, for a total
542
of nframes. If angle is not specified and nframes is,
543
then the angle defaults to 360 / nframes.
545
if not keywords.has_key ("angle") and keywords.has_key ("nframes") :
546
nframes = keywords ["nframes"]
547
angle = 2 * pi / nframes
549
if keywords.has_key ("nframes") :
550
nframes = keywords ["nframes"]
553
if keywords.has_key ("angle") :
554
angle = keywords ["angle"]
556
angle = 2 * pi / nframes
557
for ipl in range (len (self._plotter_list)) :
558
pl = self._plotter_list [ipl]
559
pl.move_light_source (self, angle, nframes)
561
def rotate (self, ** keywords) :
562
"""rotate (axis = array ([-1., 1., 0.], Float), angle = 12,
563
nframes = 30) Causes the current graph to be rotated about
564
the specified axis, through the specified angle each frame,
565
for a total of nframes frames. If angle is not specified and
566
nframes is, then the angle defaults to 360 / nframes.
568
if keywords.has_key ("axis") :
569
axis = keywords ["axis"]
571
axis = array ([-1., 1., 0.], Float)
572
if not keywords.has_key ("angle") and keywords.has_key ("nframes") :
573
nframes = keywords ["nframes"]
574
angle = 2 * pi / nframes
576
if keywords.has_key ("nframes") :
577
nframes = keywords ["nframes"]
580
if keywords.has_key ("angle") :
581
angle = pi * keywords ["angle"] / 180. # angle is degrees
583
angle = 2 * pi / nframes
584
for ipl in range (len (self._plotter_list)) :
585
pl = self._plotter_list [ipl]
586
pl.rotate_graph (self, axis, angle, nframes)