| Home | zeGraph lib | Lua lib | Custom lib | Tutorials | Notes | XML Script | C-Talk | Z-Script |
Lesson 1
Lesson 2
Lesson 3
Lesson 4
Lesson 5
Lesson 6
Lesson 7
Lesson 8
Lesson 9
Lesson 10
Lesson 11
Lesson 12
Lesson 13
Lesson 14
Lesson 15
Lesson 16
Lesson 17
Lesson 18
Lesson 19

Lesson 17 2D Plot

Understanding the characteristics of zePlot is creitical for using it for 2D plot: (1) It redefines the global coordinate for objects added to it; (2) Positioning its axes refers to the global coordinates; and (3) axes ranges of objects in the plot are determined by zePlot, not by axes, which are just objects for describing the meaning of data.

Since axes are place in the center of a plot by default (Fiugre 17.1), you usually have to use the set() function to relocate them. There are two way to position a axis. Using the anchor option of the set() function is introduced in Lessen 9. Sometimes, you may want to position a axis with the offset option. To place a x-axis at the bottom, the offset is calculate by

x_offset = - x_scale * image_height / 2

And to place a y-axis on the left, the offset is calculate by

y_offset = - y_scale * image_width / 2

Figure 17.1 2D plot.

Blending is recommended for producing smooth lines in a 2D plot. Of course, you need to set the smooth attibute of a line object to have the effect. Blending a smooth polygon usually makes it looks worse than without blending, especially with light effect enabled, because part of the polygon may be too transparent so that objects behind it or the background may be seen. The Window's implementation of OpenGL seems triangulates every type of polygon for rendering. Blending may reveal shared edges. Therefore we do not recommand blending with polygons.

If you want to have a 3D polygon in a plot, you should add it to the plot with the x, y, z coordinate, because the plot applies different x- y- and z-scales internally to all objects it contains and therefore may distort the shape to a unacceptable degree. Object added in such a way will not be scaled by the plot.

Line Plot

This is one the most frequently used plot type. Even if there is nothing special in drawing a line or connecting symbols with line segments, adding special effect to a line plot is not so trivial for many software. But with zeGraph, you can easily add images, pattern or color filled frames to the background. This example demonstrate using image in the background.

require("register")

render, scene, root, texture, plot, poly, xyz, st,
line, lxy, point, pxy, font
    = zeGrf.new("render", "scene", "node",
      "texture", "plot", "polygon",
      "vertex", "texcoord", "line",
      "vertex", "point", "vertex", "font")
    
render:add(scene)
scene:set{node = root}
root:add(plot)

plot:add(line, point, texture, poly)
plot:font(font)
plot:fontsize(12)
plot:scale(0.6, 0.4, 1)
plot:set{axis = "x", linewidth = 1.5, range = {0, 20},
         tickmarks = {0, 5, 1}, label = "X",
          offset = {0, -102, 0}}
plot:set{axis = "y", linewidth = 1.5, range = {0, 25},
         tickmarks = {0, 5, 1}, tickdigit = {1, false},
         label = "Y", offset= {-154, 0, 0}}

texture:set{image = "earth.png"}

xyz:add({0, 0, -10, 20, 0, -10, 20, 25, -10, 0, 25, -10})
st:add({.2, .2, .8, .2, .8, .8, .2, .8})
poly:set{vertex = xyz, texture_coord = st,
         type = "quads", color = {1, 1, 1, 1}}
line:set{vertex = lxy, color = {1, 0, 0, 1}, solid = 3}
point:set{vertex = pxy, color = {1, 1, 0, 1}, size = 5}

--Generate data for use by vertices of line and point objects.

x, y, xy = zeUtl.new("double", "double", "double")

x:range(1, 1, 19)
y:resize(19, 1)
xy:resize(19, 3)
xy:fill(0)

zeMath.rand(y)
y:add(x)
xy:setarr(0, x)
xy:setarr(1, y)

lxy:add(xy)
pxy:add(xy)

render:tofile("line_2d.png")

line_2d.png

The filled square symbols are drawn by using zePoint object. If its smooth() function is called, the symbols will appear in round shape. It is unfortunately however that the OpenGL implementation of Windows limits the maximal point size to 10. Well, this limitation is not a serious problem for zeGraph - the library lib2d.lua has functions for generating various symbols of any size. If the variety is not enough, you can always write your own symbol functions. Here is an example of drawing triangle symbols.

require("register")

render, scene, root, blend, plot, font
    = zeGrf.new("render", "scene", "node",
      "blend", "plot", "font")
    
render:add(scene)
scene:set{node = root}
root:add(blend, plot)

plot:font(font)
plot:fontsize(12)
plot:scale(0.6, 0.4, 1)
plot:set{axis = "x", linewidth = 1.5, range = {0, 1},
         tickmarks = {0, .2, 1}, label = "X",
         offset = {0, -102, 0}}
plot:set{axis = "y", linewidth = 1.5, range = {0, 1},
         tickmarks = {0, 0.2, 1}, label = "Y",
         offset= {-154, 0, 0}}

arr = zeUtl.new("double")
arr:resize(10, 2)
zeMath.rand(arr)

require("triangle_sym")
shape = triangle_sym(plot, 512, 512, arr, 15)

shape:set{color = {0, 1, 0, 1}, smooth = true}

root:translate(10, 10, 0)

render:tofile("triangle_symbol.png")

triangle_symbol.png

Wind Field

Making a good wind field plot takes time to practice. A NCEP wind dataset is used here to demonstrate the trick.

require("register")

data, spec, idx, uv, w, vec, tmp, clrtb
    = zeUtl.new("short", "uint", "uint", "double",
       "double", "double", "double", "double")
    
-- specify the block of data to read

spec:resize(3, 2)
spec:fill(0)
spec:setele(0, 1, 1)
spec:setele(1, 1, 73)
spec:setele(2, 1, 144)

-- process u-wind

nc = zeUtl.new("cdf")
nc:open("x:\\shared\\uwnd.sig995.1978.nc") -- data frome NOAA FTP
nc:getvar("uwnd", spec, data)

-- resize and re-arrange array from row major to column major;
-- and then scale the array.

w:resize(144 * 73, 1)
ptr, type = data:getptr()
w:setptr(ptr, type, 144 * 73)
w:reshape(144, 73)
w:transpose()
w:flip("r")

idx:range(0, 2, 72)
w:sample(idx, tmp)
idx:range(0, 2, 36)
tmp:sample(idx, w, "r")

w:mul(0.01)
w:add(225.45)

-- convert grid data to points

zeMake.grid2points(uv, 6, w)

-- process v-wind

nc:open("x:\\shared\\vwnd.sig995.1978.nc") -- data frome NOAA FTP
nc:getvar("vwnd", spec, data)
w:resize(144 * 73, 1)
ptr, type = data:getptr()
w:setptr(ptr, type, 144 * 73)
w:reshape(144, 73)
w:transpose()
w:flip("r")

idx:range(0, 2, 72)
w:sample(idx, tmp)
idx:range(0, 2, 36)
tmp:sample(idx, w, "r")

w:mul(0.01)
w:add(225.45)

zeMake.grid2points(vec, 6, w)
vec:getarr(2, w)
uv:insert(3, w)

zeMake.field(w, 5, uv)

clrtb:resize(3, 3)
clrtb:fill(0)
clrtb:setele(0, 2, 1)
clrtb:setele(1, 1, 1)
clrtb:setele(2, 0, 1)

w:getarr(2, vec)
zeMake.data2color(uv, vec, clrtb)
w:setarr(2, 0)

-- draw the wind field

render, scene, root, blend, shape, xyz, clr
    = zeGrf.new("render", "scene", "node",
      "blend", "line", "vertex", "color")

render:add(scene)
scene:set{node = root}
root:add(blend, shape)
shape:set{vertex = xyz, vertex_color = clr,
         type = "lines", solid = 1.5, smooth = true}
shape:translate(-216, -108, 0)
shape:scale(2, 2, 1)

xyz:add(w)
clr:add(uv)

render:tofile("field.png")

field.png

Height Map

In Lesson 12, You are shown how to make a map image from the earth relief data in etopo120.cdf. Actually, zeGraph has all the tools for you to make height map from such data. The product of the following code looks more informative than the one in Lesson 12.

require("register")

nc, rose, dat, arr, vec
    = zeUtl.new("cdf", "float", "double",
      "double", "double", "double")

nc:open("x:\\geo-data\\etopo120.cdf")
nc:getvar("ROSE", rose)

-- Convert data from float to double

ptr, type = rose:getptr()
nele, nvec = rose:size()
dat:setptr(ptr, type, nele * nvec)

dat:reshape(180, 90)
dat:transpose()

-- This lines rearrange the data so that the they
-- correspond to longitude of 1 to 359.

dat:shift(-10) 

-- Scale the data for coloring later

max = dat:max()
min = dat:min()
dat:sub(min)
dat:div(max-min)

render, scene, root, light, poly, xyz, nor, rgb
    = zeGrf.new("render", "scene", "node", "light",
      "polygon", "vertex", "vertex", "color")
    
render:add(scene)
scene:set{node = root}
root:add(light, poly)
light:set{position = {500, 500, 500}}
poly:set{vertex = xyz, vertex_color = rgb,
         vertex_normal = nor, type = "quads"} 

-- Convert grid data to x, y and z

zeMake.grid2quads(arr, 2, dat)
xyz:add(arr)

arr:getarr(2, vec)

-- Calculate normal

zeMake.normal4(dat, 20, arr)
nor:add(dat)

-- Gray scale coloring based on z-values

dat:setarr(0, vec)
dat:setarr(1, vec)
dat:setarr(2, vec)
dat:insert(3, 1)
rgb:add(dat)

poly:set{color = {0, 1, 1, 1}}

poly:translate(-180, -90, 0)

render:tofile("heightmap.png")

heightmap.png

Contour

Contour plot is probably the most frequently used 2D plot for 3D data. For randomly sampled data, you can make contour plot with the help of Delaunay triangulation, as shown Lesson 16. Making contours for grid data is very simple as shown bellow. Here We omitted procedures to draw axes and colorbar so that the code is short and therefore easier to understand. In practice, the grid data even do not have to cover a rectangle region. The contour4() function finds contour lines for every four x-y-z data; and therefore you can make contour for any arbitrary area. This feature is really useful - just image to make contour for seawater temperature, for instance.

require("register")

-- Read the air temperature data

nc = zeUtl.new("cdf")
nc:open("x:\\shared\\air.sig995.1978.nc") -- data from NOAA FTP

air, spec = zeUtl.new("short", "uint")
spec:resize(3, 2)
spec:fill(0)
spec:setele(0, 1, 1)
spec:setele(1, 1, 73)
spec:setele(2, 1, 144)
nc:getvar("air", spec, air)

dat = zeUtl.new("double")
dat:resize(144, 73)
ptr, type, nb = air:getptr()
dat:setptr(ptr, type, 144 * 73)
dat:reshape(144, 73)
dat:transpose()
dat:flip("r")
dat:mul(0.01)
dat:add(512.81)

-- Grid to quad shape

air = zeUtl.new("double")
zeMake.grid2quads(air, 1, dat)

-- Make color according to z-values

clrtb = zeUtl.new("double")
clrtb:resize(3, 3)
clrtb:fill(0)
clrtb:setele(0, 2, 1)
clrtb:setele(1, 1, 1)
clrtb:setele(2, 0, 1)

clr, vec = zeUtl.new("double", "double")
air:getarr(2, vec)
zeMake.data2color(clr, vec, clrtb)

-- Find contour lines.

iso = zeUtl.new("double")
iso:range(245, 5, 11)
zeMake.contour4(dat, iso, 0, air)

-- Construct scene

render, scene, root, blend, poly, pxyz, prgb, line, lxyz
    = zeGrf.new("render", "scene", "node", "blend", "polygon",
                "vertex", "color", "line", "vertex")

render:add(scene)
scene:set{node = root}
root:add(blend, poly, line)

poly:set{vertex = pxyz, vertex_color = prgb, type = "quads"}
line:set{vertex = lxyz, color = {1, 1, 1, 1}, type = "lines",
         smooth = true}

n = dat:size()
vec:resize(n, 1)
vec:fill(10)
dat:setarr(2, vec)    -- make contour line z-values all 10

lxyz:add(dat)

n = air:size()
vec:resize(n, 1)
vec:fill(0)
air:setarr(2, vec)    -- make polygon z-values all zero

pxyz:add(air)
prgb:add(clr)

root:translate(-72, -36, 0)
root:scale(3, 3, 1)

render:tofile("contour_line.png")

contour_line.png

This is often desirable to make discretely filled contour. Slightly modifying the previous example, you can achieve that effect, like this:

require("register")

-- Read the air temperature data

nc = zeUtl.new("cdf")
nc:open("x:\\shared\\air.sig995.1978.nc")

air, spec = zeUtl.new("short", "uint")
spec:resize(3, 2)
spec:fill(0)
spec:setele(0, 1, 1)
spec:setele(1, 1, 73)
spec:setele(2, 1, 144)
nc:getvar("air", spec, air)

dat = zeUtl.new("double")
dat:resize(144, 73)
ptr, type, nb = air:getptr()
dat:setptr(ptr, type, 144 * 73)
dat:reshape(144, 73)
dat:transpose()
dat:flip("r")
dat:mul(0.01)
dat:add(512.81)

-- Grid to quad shape

air = zeUtl.new("double")
zeMake.grid2quads(air, 1, dat)

-- Find contour triangles.

iso = zeUtl.new("double")
iso:range(245, 5, 11)
zeMake.contour4(dat, iso, 1, air)

-- Make color according to z-values

clrtb = zeUtl.new("double")
clrtb:resize(3, 3)
clrtb:fill(0)
clrtb:setele(0, 2, 1)
clrtb:setele(1, 1, 1)
clrtb:setele(2, 0, 1)

clr, vec = zeUtl.new("double", "double")
dat:getarr(2, vec)
zeMake.data2color(clr, vec, clrtb)

vec:fill(0)
dat:setarr(2, vec)      -- make all z-values zero

-- Construct scene

render, scene, root, poly, pxyz, prgb
    = zeGrf.new("render", "scene", "node",
     "polygon", "vertex", "color")

render:add(scene)
scene:set{node = root}
root:add(poly)

poly:set{vertex = pxyz, vertex_color = prgb, type = "triangles"}
pxyz:add(dat)
prgb:add(clr)

root:translate(-72, -36, 0)
root:scale(3, 3, 1)

render:tofile("contour_fill.png")

contour_fill.png