~ubuntu-branches/ubuntu/karmic/python-scipy/karmic

« back to all changes in this revision

Viewing changes to Lib/xplt/graph3d.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel T. Chen (new)
  • Date: 2005-03-16 02:15:29 UTC
  • Revision ID: james.westby@ubuntu.com-20050316021529-xrjlowsejs0cijig
Tags: upstream-0.3.2
ImportĀ upstreamĀ versionĀ 0.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 1996, 1997, The Regents of the University of California.
 
2
# All rights reserved.  See Legal.htm for full text and disclaimer.
 
3
 
 
4
# Nar, Numeric, and shapetest are all imported by graph:
 
5
 
 
6
from types import *
 
7
from graph import *
 
8
from graftypes import *
 
9
 
 
10
class Graph3d ( Graph ) :
 
11
 
 
12
   """
 
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:
 
21
 
 
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
 
43
              than one host.
 
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
 
60
                   character.
 
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
 
65
                   axis labels.
 
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
 
70
                   y limits.
 
71
       x_axis_limits, y_axis_limits, z_axis_limits, c_axis_limits, and
 
72
                   yr_axis_limits may be used to change individual
 
73
                   axis limits.
 
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
 
85
                   are unchanged.
 
86
       x_axis_scale, y_axis_scale, z_axis_scale, c_axis_scale, and
 
87
                   yr_axis_scale may be used to change individual
 
88
                   axis scales.
 
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
 
97
                   smaller the size.
 
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)
 
117
       Gist viewing angles:
 
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.
 
143
           Narcisse only.
 
144
       connect = <value> set to 1 for graphs of more than one surface
 
145
           to provide better hidden line removal. Must not be used
 
146
           with link.
 
147
           Narcisse only.
 
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.
 
152
           Narcisse only.
 
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
 
161
           faced the viewer).
 
162
       specular = <value> 
 
163
       spower = <value>
 
164
       sdir = <value> 
 
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
 
172
           case of XYZ).
 
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.
 
192
   """
 
193
 
 
194
   def type (self) :
 
195
       return Graph3dType
 
196
 
 
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 }
 
201
 
 
202
   _axes = ["x", "y", "z", "c", "yr"]
 
203
 
 
204
   _NotASurface = "NotASurface"
 
205
   _LinkError = "LinkError"
 
206
   _SurfaceSpecError = "SurfaceSpecError"
 
207
   _SurfaceChangeError = "SurfaceChangeError"
 
208
 
 
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"]
 
216
   _no_of_axes = 5
 
217
 
 
218
   def __init__ ( self , surface_list , *kwds , ** keywords ) :
 
219
      if len ( kwds ) == 1 :
 
220
         keywords = kwds[0]
 
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, " + \
 
227
            "slice,  or mesh."
 
228
         self._s = [surface_list]
 
229
      else :
 
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, " \
 
236
               + "slice,  or mesh."
 
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"]
 
241
      else :
 
242
         self._x_factor = 1.0
 
243
      if keywords.has_key ("y_factor") :
 
244
         self._y_factor = keywords ["y_factor"]
 
245
      else :
 
246
         self._y_factor = 1.0
 
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"]
 
251
      else :
 
252
         self._link = 0
 
253
      if keywords.has_key ("connect") :
 
254
         self._connect = keywords ["connect"]
 
255
      else :
 
256
         self._connect = 0
 
257
      if keywords.has_key ("phi") :
 
258
         self._phi = keywords ["phi"]
 
259
      else :
 
260
         self._phi = None
 
261
      if keywords.has_key ("theta") :
 
262
         self._theta = keywords ["theta"]
 
263
      else :
 
264
         self._theta = None
 
265
      if keywords.has_key ("roll") :
 
266
         self._roll = keywords ["roll"]
 
267
      else :
 
268
         self._roll = None
 
269
      if keywords.has_key ("distance") :
 
270
         self._distance = keywords ["distance"]
 
271
      else :
 
272
         self._distance = 0
 
273
      if keywords.has_key ("z_scale") :
 
274
         self.z_scale = keywords ["z_scale"]
 
275
      else :
 
276
         self.z_scale = 1.0
 
277
      if keywords.has_key ("gnomon") :
 
278
         self._gnomon = keywords ["gnomon"]
 
279
      else :
 
280
         self._gnomon = None
 
281
      if keywords.has_key ( "color_card" ) :
 
282
         self._color_card = keywords ["color_card"]
 
283
      else :
 
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"]
 
288
      # Gist lighting
 
289
      self.lighting_dict = {}
 
290
      if keywords.has_key ("ambient") :
 
291
         self.lighting_dict ["ambient"] = keywords ["ambient"]
 
292
      else :
 
293
         self.lighting_dict ["ambient"] = None
 
294
      if keywords.has_key ("diffuse") :
 
295
         self.lighting_dict ["diffuse"] = keywords ["diffuse"]
 
296
      else :
 
297
         self.lighting_dict ["diffuse"] = None
 
298
      if keywords.has_key ("specular") :
 
299
         self.lighting_dict ["specular"] = keywords ["specular"]
 
300
      else :
 
301
         self.lighting_dict ["specular"] = None
 
302
      if keywords.has_key ("spower") :
 
303
         self.lighting_dict ["spower"] = keywords ["spower"]
 
304
      else :
 
305
         self.lighting_dict ["spower"] = None
 
306
      if keywords.has_key ("sdir") :
 
307
         self.lighting_dict ["sdir"] = keywords ["sdir"]
 
308
      else :
 
309
         self.lighting_dict ["sdir"] = None
 
310
      if keywords.has_key ("style") :
 
311
         self._style = keywords ["style"]
 
312
      else :
 
313
         self._style = "nobox.gs"
 
314
      if keywords.has_key ("grid_type") :
 
315
         self._grid_type = keywords ["grid_type"]
 
316
      else :
 
317
         self._grid_type = "none"
 
318
      if keywords.has_key ( "opt_3d" ) :
 
319
         self.opt_3d = keywords ["opt_3d"]
 
320
      else :
 
321
         self.opt_3d = "wm"
 
322
      if keywords.has_key ( "mask" ) :
 
323
         self.mask = keywords ["mask"]
 
324
      else :
 
325
         self.mask = "max"
 
326
      if keywords.has_key ( "split" ) :
 
327
         self._split = keywords ["split"]
 
328
      else :
 
329
         self._split = 0
 
330
      # Everything else is Graph generic:
 
331
      self._label_type = " "
 
332
      Graph.__init__ ( self , keywords )
 
333
 
 
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.
 
340
      """
 
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, \
 
349
          self._axis_limits
 
350
      self.__init__ ( surface_list , keywords )
 
351
      if self._plotter_list == [] :
 
352
         self._plotter_list = pl
 
353
         self._filename_list = fl
 
354
 
 
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.
 
359
      """
 
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)
 
366
 
 
367
   def delete ( self , n ) :
 
368
      """delete ( n ) deletes the nth surface from the Graph. Note
 
369
      that surfaces are numbered beginning with 1.
 
370
      """
 
371
      DeleteError = "DeleteError"
 
372
      if 1 <= n <= self._s_ln :
 
373
         self._s [n-1:n] = []
 
374
         self._s_ln = len (self._s)
 
375
      else :
 
376
         raise DeleteError , "There is no surface numbered " + `n` + \
 
377
            " in the current graph, which has only " + `self._s_ln` + \
 
378
            " surfaces."
 
379
 
 
380
   def set_surface_list (self, surface_list) :
 
381
      del self._s
 
382
      self._s = surface_list
 
383
      self._s_ln = len (surface_list)
 
384
      return
 
385
 
 
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
 
389
      beginning with 1.
 
390
      """
 
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
 
398
      else :
 
399
         raise ReplaceError , "There is no surface numbered " + `n` + \
 
400
            " in the surfacent graph, which has only " + `self._s_ln` + \
 
401
            " surfaces."
 
402
 
 
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.
 
412
      """
 
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"]
 
420
      else:
 
421
         send = 0
 
422
      self._send_coordinates = send
 
423
      self.plot ( )
 
424
      self._send_coordinates = 1
 
425
 
 
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.
 
435
      Or you can do both.
 
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
 
441
      change a surface.
 
442
      quick_plot will not work right for link'ed surfaces. Once the
 
443
      changes have been made, you will have to call plot.
 
444
      """
 
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"]
 
458
         self.mask_change = 1
 
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" ] ) :
 
465
            self._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"]
 
471
         else :
 
472
            self.n = keywords ["mesh"]
 
473
         self.color_card_change = 0
 
474
         self.opt_3d_change = 0
 
475
         self.mesh_type_change = 0
 
476
         self.mask_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
 
487
         else :
 
488
            raise ChangeError , "There is no surface numbered " + `n` + \
 
489
            " in the current graph, which has only " + `self._s_ln` + \
 
490
            " surfaces."
 
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"]
 
503
      else :
 
504
         self.lighting_dict ["ambient"] = None
 
505
      if keywords.has_key ("diffuse") :
 
506
         self.lighting_dict ["diffuse"] = keywords ["diffuse"]
 
507
      else :
 
508
         self.lighting_dict ["diffuse"] = None
 
509
      if keywords.has_key ("specular") :
 
510
         self.lighting_dict ["specular"] = keywords ["specular"]
 
511
      else :
 
512
         self.lighting_dict ["specular"] = None
 
513
      if keywords.has_key ("spower") :
 
514
         self.lighting_dict ["spower"] = keywords ["spower"]
 
515
      else :
 
516
         self.lighting_dict ["spower"] = None
 
517
      if keywords.has_key ("sdir") :
 
518
         self.lighting_dict ["sdir"] = keywords ["sdir"]
 
519
      else :
 
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]
 
524
         pl.quick_plot (self)
 
525
 
 
526
   def plot ( self ) :
 
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
 
530
       Graphics routine.
 
531
       """
 
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]
 
536
          pl.plot3d (self)
 
537
   
 
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.
 
544
       """
 
545
       if not keywords.has_key ("angle") and keywords.has_key ("nframes") :
 
546
          nframes = keywords ["nframes"]
 
547
          angle = 2 * pi / nframes
 
548
       else :
 
549
          if keywords.has_key ("nframes") :
 
550
             nframes = keywords ["nframes"]
 
551
          else :
 
552
             nframes = 30
 
553
          if keywords.has_key ("angle") :
 
554
             angle = keywords ["angle"]
 
555
          else :
 
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)
 
560
 
 
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.
 
567
       """
 
568
       if keywords.has_key ("axis") :
 
569
          axis = keywords ["axis"]
 
570
       else :
 
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
 
575
       else :
 
576
          if keywords.has_key ("nframes") :
 
577
             nframes = keywords ["nframes"]
 
578
          else :
 
579
             nframes = 30
 
580
          if keywords.has_key ("angle") :
 
581
             angle = pi * keywords ["angle"] / 180. # angle is degrees
 
582
          else :
 
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)