[Top] [Prev] [Next] [Bottom]

5.2 Graph3d Objects


from graph3d import *
g3 = Graph3d ( <object list>, <keylist>)


A Graph3d is a container for one or more three dimensional geometric objects (Surfaces, Mesh3ds, and/or Slices) as well as global information about the graph. It will accept one or a list of Plotter objects or Plotter identifiers, or will try to complete generic connection(s) of its own if asked to plot without having been given a plotter specification.

<object list> is one or a sequence of 3d geometric objects. It makes sense sometimes to graph several such objects on one plot. By means of linking two or more objects (see description below), it is possible though somewhat difficult in PyNarcisse to plot two or more objects with different 3d/4d options, palettes, etc. on the same graph. PyGist does not allow this; however, in mesh plots which mix isosurfaces and plane slices, PyGist allows a split palette option, which shades the isosurfaces as if from a light source, but colors plane slices according to the specified function.

A list of keyword arguments accepted by Graph3d is:

plotter, filename, display, titles, title_colors, grid_type, axis_labels, x_axis_label, y_axis_label, z_axis_label, c_axis_label, yr_axis_label, axis_limits, x_axis_limits, y_axis_limits, z_axis_limits, c_axis_limits, yr_axis_limits, axis_scales, x_factor, y_factor, x_axis_scale, y_axis_scale, z_axis_scale, c_axis_scale, yr_axis_scale, text, text_color, text_size,text_pos, phi, theta, roll, distance, link, connect, sync, ambient, diffuse, specular, spower, sdir, color_bar, color_bar_pos

Graph3d objects inherit from base class Graph, as does Graph2d. The following methods are inherited from Graph: add_file, delete_file, add_plotter, delete_plotter, and change. See "Description" on page 75 for details. In addition, a Graph3d has the following methods which, except where noted, are similar to the Graph2d methods with the same names: new, add, delete, replace, change_plot, quick_plot, and plot.


3d Animation Methods

Finally, Graph3d has two methods which have to do with real time animation of 3d plots. These methods are as follows:

move_light_source (<keylist>) The keyword arguments are:
nframes (default 30): the number of frames in the proposed movie.
angle (default 360 / nframes): the angle (in degrees) through which the light source rotates for each frame.
This method is not yet implemented in PyNarcisse.
rotate (<keylist>) The keyword arguments are:
axis (default [-1., 1., 0.]): the direction numbers of the axis about which the graph is rotated.
nframes (default 30): the number of frames in the proposed movie.
angle (default 360 / nframes): the angle (in degrees) through which the graph is rotated for each frame.
This method is not yet implemented in PyNarcisse.

Keyword Arguments

The following keywords inherited from Graph have exactly the same behavior as described under Graph2d (See "Keyword Arguments" on page 77):

plotter, filename, display, titles, title_colors, grid_type (PyNarcisse only), text, text_color, text_size, text_pos, color_bar, color_bar_pos

Up to four axes are possible in 3d and 4d plots : x, y, z or c (depending on whether we chose the option of switching z and c), and the right y axis (when the left and right sides of the plot have different y axis scales, with some objects plotted on one and some on another), so the specifications of axis characteristics are different from those for Graph2d. The axis characteristic keywords (primarily applicable to PyNarcisse, but see the next paragraph) are:

axis_labels, axis_limits, axis_scales

Each should be specified as a list of up to five items, in the order x, y, z, c, yr; items omitted from the right will be defaulted. axis_labels are strings; axis_limits are pairs of floats; and axis_scales are one of the two strings "lin" or "log".

PyGist does not currently support full axes display in 3d. Instead, it is capable of displaying a gnomon in the lower left corner of a 3d plot, i. e., a small representation showing the orientation of the three coordinate axes, with the labels in reverse video if they are pointed ``into'' the plane of the plot. These labels default to ``X'', ``Y'', and ``Z'', but the defaults can be overruled by the axis_labels keyword. PyGist will only use the first letter of each specified label, if longer than one letter. The keyword gnomon (if set to nonzero) turns on the gnomon display.

Another peculiarity of PyGist is its tendency to stretch the plotted surface so that it extends from edge to edge of the plotting area. The keywords x_factor and y_factor can be used to force the display to appear in proper perspective; in most cases leave x_factor alone, and set y_factor to 2.0. Both keywoirds default to 1.0.

Other keywords which are peculiar to Graph3d objects are:

phi = <integer value> specifies the angle that the line from the view point to the origin makes with the positive z axis. The angle is in degrees.
theta = <integer value> specifies the angle made by the projection of the line of view on the xy plane with the positive x axis. The angle is in degrees.
roll = <integer value> specifies the angle of rotation of the graph around the line from the origin to the view point. The angle is measured in the positive direction, i. e., if your right thumb is aligned outwards along the line from the origin to the view point, then your fingers curl in the positive direction. The angle is in degrees. (This keyword is not available in PyGist, and will be ignored if supplied.)
distance = <integer value> specifies the distance of the view point from the origin. This is an integer between 0 and 20. 0 makes the distance infinite; otherwise the smaller the number, the closer you are. This number does not affect the size of the graph, but rather the amount of distortion in the picture (the closer you are, the more distortion).

The following keywords are applicable only in PyNarcisse:

link = 0 or 1: Used to link surfaces of different 3d options. normally all surfaces in a graph will have the same 3d options. This value should be set to 1 if you want to graph two or more surfaces with different 3d options. otherwise multiple surface graphs will appear with the options of the last surface specified. This may not always work as expected, since successive objects in a linked Graph are plotted on top of whatever is in the current window. That may not be where they are positioned; e. g., it would be easy to have an object that is really behind another be drawn on top of it.
connect = 0 or 1: set to 1 for graphs of more than one 3d object to provide better hidden line removal. Must not be used with link.
sync = 0 or 1: set to 1 to synchronize with Narcisse before plotting the next graph. Keeps graphs sent in rapid succession from becoming garbled. Defaults to 1; set it to 0 if you don't have a timing problem.

The following lighting keywords are applicable only in PyGist:

ambient = <value> is a light level (arbitrary units) that is added to every surface independently of its orientation. High values of this argument cause the surface to appear to glow with its own light, making it so bright as to lose contrast. Low values of this argument mean that reflected and diffuse light are more important in visualizing the surface.
diffuse = <value> is a light level which is proportional to cos (theta), where theta is the angle between the surface normal and the viewing direction, so that surfaces directly facing the viewer are bright, while surfaces viewed edge on are unlit (and surfaces facing away, if drawn, are shaded as if they faced the viewer, so that if we are looking at the inside of a surface, it will look properly three-dimensional).
specular = <value>
spower = <value>
sdir = <value>
specular = S_LEVEL is a light level proportional to a high power spower = N of 1 + cos (alpha), where alpha is the angle between the specular reflection angle and the viewing direction. The light source for the calculation of alpha lies in the direction sdir = XYZ (a 3 element vector) in the viewer's coordinate system at infinite distance. You can have ns light sources by making S_LEVEL, N, and XYZ (or any combination) be vectors of length ns (ns-by-3 in the case of XYZ).
The four parameters ambient, diffuse, specular, and spower act together to produce interesting effects. if diffuse and specular are both 0, then the surface will not be reflective, and all three dimensional appearance will be lost. specular and spower together determine how reflective the surface is; large spower with specular not 0 gives small, bright highlights with most of the surface appearing black. As spower decreases, the highlights become somewhat larger and darker portions of the surface become lighter. If diffuse is not zero but specular and ambient are zero, then the surface will appear shaded gently, brighter on the side(s) toward the light source(s), but not highly reflective. The user is encouraged to experiment to find the desired effect.
split = 0 or 1 (default 1) If 1, causes the palette to be split when both planes and isosurfaces are present in a graph, so that isosurfaces are shaded according to current light settings, while plane sections of the mesh are colored according to a specified function. (The lower half of the palette is grey scale, and the upper half is (usually) rainbow.

Example 1. Surface plots.

All of the plots illustrated in this example are of the following surface; it is an interesting symmetric surface with a peak and a valley.

x = span (-1, 1, 64, 64)
y = transpose (x)
z = (x + y) * exp (-6.*(x*x+y*y))
s1 = Surface (z = z, opt_3d = "wm", mask = "sort")

In each case, the title describes how the surface is displayed. We have set the y_factor keyword to 2.0 so that the surface will show in proper perspective; otherwise it would be stretched out from border to border in the vertical direction.

g1 = Graph3d (s1, color_card = "gray.gp",
titles = "opaque wire mesh", y_factor = 2.)
g1.plot ()
paws ()
s1.set (mask = "none")
g1.change (titles = "transparent wire mesh")
g1.plot ()
paws ()
s1.set (ecolor = "red")
g1.change (titles = "transparent wire mesh in red")
g1.plot ()
paws ()
s1.set (mask = "sort", shade = 1)
g1.change (titles = "opaque shaded mesh with red lines")
g1.plot ()
paws ()
s1.set (opt_3d = "none")
g1.change (titles = "opaque shaded mesh with no lines")
g1.plot ()
paws ()

The next example is interesting in that it shows a back-lit surface.

g1.change (titles = "same with different lighting")
g1.quick_plot (diffuse=.1, specular = 1.,
paws ()

Example 2. Plane cross sections of imploding sphere.

The user may recall this example. An imploding sphere has been decomposed into an unstructured (but hexahedral) mesh. The data is read in from a pdb file as follows:

f = PR ('./bills_plot')
n_nodes = f.NumNodes
n_z = f.NodesOnZones
x = f.XNodeCoords
y = f.YNodeCoords
z = f.ZNodeCoords
c = f.ZNodeVelocity
n_zones = f.NumZones

Now we build a Mesh3d object from the data:

m1 = Mesh3d (x = x, y = y, z = z, c = c, avs = 1,
hex = [n_zones, n_z])

Create three Plane objects with which to perform cross sections:

pyz = Plane (array ([1., 0., 0.], Float ),
array ( [0.0001, 0., 0.], Float))
pxz = Plane (array ([0., 1., 0.], Float ),
array ( [0., 0.0001, 0.], Float))
p2 = Plane (array ([1., 0., 0.], Float ),
array ( [0.35, 0., 0.], Float))

Slice the mesh three times:

s2 = sslice (m1, pyz, varno = 1, opt_3d = ["wm", "s4"])
s22 = sslice (m1, p2, varno = 1, opt_3d = ["wm", "s4"])
s23 = sslice (m1, pxz, varno = 1, opt_3d = ["wm", "s4"])
g1 = Graph3d( [s2, s22, s23], color_card = "rainbow.gp",
opt_3d = ["wm", "s4"], mask = "min", color_bar = 1,
split = 0, hardcopy = "talk.ps")
g1.plot ()

The resulting graph is shown below.

Example 3. Moving light source on surface.

In this example, we will illustrate how to set up a graph with a moving light source. The light source will apparently move over the surface in real time. You will have to take our word for this; the next two figures show different views of the surface as the light progresses.

s1 = Surface (z = z, opt_3d = "none", mask = "sort",
shade = 1) # Same surface as Example 1
g1 = Graph3d (s1, ambient = 0.2, diffuse = .2, specular = 1.,
color_card = "gray.gp", titles = "moving light source",
y_factor = 2.)
g1.move_light_source ()

Imagine the light source as moving from right to left just behind the viewer.

Example 4. Rotating isosurfaces and cutting plane.

We cannot show you the actual rotation in these pages, but we shall show you a couple of different snapshots of the rotating surface. This example consists of a couple of isosurfaces in a mesh, each sliced horizontally and vertically, with parts discarded so that we can see inside the figure, and a portion of one of the slicing planes. The isosurfaces are shaded in greyscale as if by a light shining over the viewer's right shoulder, and the polygons of the portion of the slicing plane are colored using the rainbow palette by the values of the same function that was used to perform the isosurface slicing. This figure illustrates the so-called ``split palette'', where half of the palette is set to greyscale colors and is used to shade isosurfaces, while the other half is set to colors used to plot function values on plane slices.

The edges of the polygons on the plane slice are also shown. The figure and the code generating it begin below.

The following code computes the coordinates of the mesh, the function c defined on it, and then creates the Mesh3d object.

nx = 20
ny = 20
nz = 20
xyz = zeros ( (3, nx, ny, nz), Float)
xyz [0] = multiply.outer ( span (-1, 1, nx),
ones ( (ny, nz), Float))
xyz [1] = multiply.outer ( ones (nx, Float),
multiply.outer ( span (-1, 1, ny), ones (nz, Float)))
xyz [2] = multiply.outer ( ones ( (nx, ny), Float),
span (-1, 1, nz))
r = sqrt (xyz [0] ** 2 + xyz [1] **2 + xyz [2] **2)
theta = arccos (xyz [2] / r)
phi = arctan2 (xyz [1] , xyz [0] + logical_not (r))
y32 = sin (theta) ** 2 * cos (theta) * cos (2 * phi)
m1 = Mesh3d (x = span (-1, 1, nx), y = span (-1, 1, ny),
z = span (-1, 1, nz), c = r * (1. + y32))

The following code sequence performs the slicing. We do not specify opt_3d for the isosurfaces, since with the split palette option they will automatically be shaded.

s1 = sslice (m1, .50, varno = 1) # (inner isosurface)
s2 = sslice (m1, 1.0, varno = 1) # (outer isosurface)
pxy = Plane (array ([0., 0., 1.], Float ), zeros (3, Float))
pyz = Plane (array ([1., 0., 0.], Float ), zeros (3, Float))
# create a pseudo-colored plane slice, then cut it in half
# and save only the front half. "f4" specifies that the
# cells be colored by the function assigned to the c
# keyword of the mesh m1. "wm" (wire monochrome) causes the
# edges of the cells to be shown.
s3 = sslice (m1, pyz, opt_3d = ["wm", "f4"])
s3 = sslice (s3, pxy, nslices = 1, opt_3d = ["wm", "f4"])
# cut the inner isosurface in half so that we can slice the
# top off one of the halves and discard it:
[s1, s4] = sslice (s1, pxy, nslices = 2)
# Note the use of - pyz to keep the "bottom" slice:
s1 = sslice (s1, - pyz)
# do the same with the outer isosurface:
[s2, s5] = sslice (s2, pxy, nslices = 2)
s2 = sslice (s2, - pyz)
# Create Graph object with split palette (rainbow/greyscale)
g1 = Graph3d ([s3, s1, s4, s2, s5], gnomon = 1,
color_card = "rainbow.gp", diffuse = .2, specular = 1,
mask = "min", split = 1)
g1.plot ()

The code which generates the rotating figure is given below. We change the x_factor and y_factor of g1 so that the figure will appear smaller.

g1.change (x_factor = 2., y_factor = 2.)
g1.rotate ()

Snapshots of the rotating figure are shown on the next page.

[Top] [Prev] [Next] [Bottom]

Copyright © 1997,Regents of the University of California. All rights reserved.