The VisAD Tutorial

Section 1 -  Introduction to VisAD

[Home] [Section 2]

This is the tutorial of VisAD, a Java Component Library for interactive analysis and visualization of numerical data.  We will start by describing how to write a simple VisAD program to visualize some points as a single line and will, in section 2, continuously extend the program to show how to use some VisAD features. In section 3 we will turn our attention to 2-D Sets, which are the basis of images and 3-D surfaces.
 

1.1 Overview

A VisAD application generally starts with the definition of Data objects, which will represent your data in the application. VisAD's Data classes can represent simple numbers, such as a temperature, simple text strings, such as the name of a weather station, vectors of simple values, such as all of the data collected by a weather station (temperature, air moisture, precipitation, wind), and arrays of values, such as a times series of temperatures. In fact, VisAD Data objects can be assembled in complex hierarchies, known as MathTypes, to represent virtually any numerical and text data.

VisAD's Displays classes help you to construct displays of those data. These may be 2-D or 3-D, they may be animated, and they may be interactive.

VisAD defines classes for computational Cells that can also be linked to Data objects via DataReference objects (see below).  Like Displays, Cells are updated whenever values of linked Data change.  Cells take their name from spread sheet cells, because of the way spread sheet cells update when input values change.

VisAD also defines a variety of classes for User Interface objects. Many of these are based on the Java Swing® user interface toolkit, and they can all be easily embedded in a Swing GUI (Display objects are also easily embedded in Swing GUIs). The VisAD user interface classes are designed to help you design user interfaces for interactive control of VisAD Displays.

VisAD also provides a helper class, called DataReference, that is used for linking a Data object to a Display object. Once Data are linked to a Display object, the display will update whenever Data values change. In fact, Displays and Cells both extend Action, the general class for objects whose actions are triggered by changing Data values.

To summarize, VisAD applications are constructed with the following objects:

  1. Data objects: these range from simple real number values,  text strings and vectors of real numbers,  to complex hierarchies of data, which are referred to as MathType.
  2. Display objects: these generate interactive depiction of data. Display objects are linked to data objects through the use of DataReference objects (see below). Displays may be two- or three-dimensional, and provide extensive controls and direct manipulation.
  3. Cell objects: these are computations that are invoked whenever their input Data objects change value. Cells take their name from cells of spread sheets and are, like displays, linked to Data objects by means of a DataReference object.
  4. User interface (UI) objects: the user can use the Java Foundation Classes UI components as data input interfaces. Nevertheless, VisAD provides a few specialized UI components. The VisAD UI components may also be linked to Data, so that Data values may be changed by them. UI objects may also link to Actions so that they update whenever Data object values change.
  5. DataReference objects: these are pointers to Data objects. DataReference objects are necessary to represent variable data, just as "x" represents 3 in "x = 3".
Before we move on to our first application we need to consider the nature of the data that is to be visualized.

The VisAD data model defines a set of classes that can be used to build any hierachical numerical data structure. These complex hierarchical Data objects reflect the structure of the actual data. The primitive Data classes are the subclasses of Scalar: Real and Text, which contain a Java double and a text string, respectively.  Data structure is achieved by using Tuple, Set and Function classes and their subclasses.

All Data objects have a MathType, which indicates the type of mathematical object that it approximates. Examples of MathTypes are: ScalarType (and its subclasses RealType or TextType), TupleType (and its subclass RealTupleType), SetType, and FunctionType.

Subclasses of Data are Scalar, Tuple, Set and Function. Subclasses of MathType are ScalarType, TupleType, SetType, and FunctionType. In a sense, the Data hierarchy reflects that of MathType.

Most applications include large Data objects that define some RealTypes as functions of other RealTypes. The starting point for any new application of VisAD is the definition of a set of MathTypes. For example, a simple function such as height = f(time), where time and height are RealTypes, is denoted in the VisAD documentation as

( time -> height )
and is defined with the FunctionType ( time -> height ). (Remeber: FunctionType is a subclass of MathType.)

A more complex data structure, like that of an image, might be defined with the MathType:

( ( row, column )  ->  ( red, green, blue ) )
The output of a weather model may be described using the MathType:
( time -> ( (latitude, longitude, altitude) -> (temperature, pressure, dew_point, wind_u, wind_v, wind _w ) ) )

So we move on to talk about a few aspects to consider when designing a VisAD application.

1.2  Designing a Typical VisAD Application

When writing a VisAD application there are three main steps to take:
  1. Creation of the data you want to visualize,
  2. Creation of the display and other visualization objects and
  3. Adding interaction and functionality through the use of user interfaces, UI, or widgets.

Let us assume we have some data, and we want to build a VisAD application to visualize those data. For example, we have a cube, and we have calculated and/or measured the temperature inside it. So let us consider the above steps in more detail.

1.2.1  Creating Data

This is the first and most important step in designing a VisAD application. Although the display and its objects define to a great extent how data is to be drawn, the depiction also depends on the data structure. You might, for example, create a data structure, a MathType, to draw a one-dimensional function as a line:

(x -> y)
and then force the display to draw the individual points, rather than to connect them to make a line. On the other hand, your MathType might describe a set of (x,y) points indexed by some variable, or in VisAD notation:
( index -> (x,y) )
With the MathType above you're saying, that you have a set of disconnected points. In this case, there is no way to force the display to connect the points. You'd have to create a new MathType.

The Domain

The first step in creating data with VisAD Data Objects is to identify the basic quantities, or scalars. For example, if we have a cube, we identify three scalars: height, width and length. In VisAD, those would be ScalarTypes. As you know, ScalarTypes has two subclasses: RealType and TextType. The latter is for use with text, whereas the former is for use with "real" numbers ("real" in mathematical sense). So our three cube dimensions are RealTypes. RealTypes are static within a VisAD application. That means in practice, you can reuse them without the need to recreate them. They are generally constructed with a Java String, that is, their name, and with a VisAD unit. The units will be considered, for example, in calculations. Say, to create a RealType h, for "height", you do:

RealType height = RealType.getRealType("Height");
or you can do
RealType height = RealType.getRealType("Height", SI.meter, null);
to create a RealType with a unit, "meter". (Ignore the third argument for now; it defines the default Set of this RealType.) This static method of the RealType class will look for an already existing RealType called "height". If such RealType already exists, then you cannot use the constructors above. Use instead the static methods.

So we have identified our cube dimensions as RealTypes. Together, the three of them form a tuple, or, in VisAD, a RealTupleType. There are many ways to create a RealTupleType, and we are going to come across them later on in the tutorial. For now it suffices to say that we have created the basic "cube structure" with:

RealTupleType cubeTuple = new RealTupleType( height, width, length);

But what about the cube itself? How big is it? To answer these questions we have to know "how" you're defining your cube. Are you measuring height, width and length at constant intervals or not? Are you measuring the vertices only, or are you measuring some random points inside the cube? VisAD has a collection of data objects, Sets, to represent different kinds of samplings. We assume our cube is 1 meter high, 2 meters wide and 3 meters long. Furthermore we assume we measure height and width every 10 cm, but, for lazyness' sake, we measure length every 50 cm, only. And for freedom's sake, we decide to put the cube's width in the middle of our reference system. That is, the extreme values for the width are -1 and 1. (OK, to be precise about it, it's a parallelepiped rather than a cube, but let's call it "cube"). All this information, together with the tuple of the cube dimensions, are defined in a three-dimensional Set:

cubeSet = new Linear3DSet(cubeTuple,  // basic quantities given by height, width and length
                0.0,  1.0, 11,         // height starts at 0.0 m, ends at 1.0 m, and has 11 samples
                -1.0, 1.0, 21,         // width starts at -1.0 m, ends at 1.0 m, and has 21 samples
                0.0,  3.0,  7 );       // length starts at 0.0 m, ends at 3.0 m, and has 7 samples, one value every 50 cm

The word "Linear" means that sampling is regular. If the sampling is regular, and they occur at integer values, say, from 0 to N, than consider using an "Integer" set. If you were to sample the cube in a grid whose points are not regularly spaced, but they nevertheless form a grid, than you'd use a "Gridded" Set, and provide the Set with the individual height, width and length values. Should you know nothing about the topology of your sampling, that is, whether they form a grid or whether they are just randomly spread inside the cube, than you might want to use an "Irregular" Set, and let VisAD figure the topology out. you may have already guessed, that VisAD has 1-D and 2-D, as well an N-D and other Sets. Please refer to the VisAD Java Component Library Developers Guide for more details.

The Range

Ok, we've already got some data, and we could create a display and add the cube to it. But most certainly you are trying to visualize how some quantity (or quantities), a RealType (or a RealTupleType) vary according to some other quantity. Like in maths, you have one or more dependent variables as functions of independent variables. What we do in VisAD is precisely that. We create the independent variable(s), create the dependent variable(s) and then use an object to establish the mathematical function between them. In VisAD one often refers to the independent variables as "domain" and to the dependent variables as "range". So far, we've created the domain. Our domain is the cube given by the RealTupleType and its set is the Linear3DSet, which we call "domain set". So what about the range? We assume we are measuring the temperature inside the cube. We need to create the corresponding RealType:

RealType temperature = new RealType("Temperature");
Of course you might want to measure temperature and some other quantity, in which case you'd need another RealType, and you'd have a range composed by a RealTupleType. We will do this later in the tutorial, for now we want to show how you create a function:
cubeTempFunc = new FunctionType(cubeTuple, temperature);

That is, "temperature" is a function of height, width and length. You create a FunctionType with two MathTypes (remember, MathType is the superclass of RealTupleType, RealTupleType, FunctionType, etc). The first MathType is the domain, and the second, the range.

The FunctionType creates the relation between the MathTypes of the domain and the range, but it says nothing about "the data" itself. Furthermore, how do you link the cube given by the Linear3DSet, with the function given by the FunctionType, and those with the "temperature" values, which you are measuring and/or computing? The answer is a Field object, or more especifically a FlatField.

A FlatField is a subclass of Field, which is a subclass of Function (but not of FunctionType!), which is a subclass of Data, and thus a Visad data object. A Field represents a mathematical function. Inside it there's information about the domain, the domain set, the range and the range values. A FlatField is an extension of Field, and has been designed with computational efficiency in mind. Inside a FlatField you pack the FunctionType, the domain Set and then you "feed" it with with range ("temperature") values. The FlatField has quite a few useful methods and in the first few tutorial chapters we're going to make good use of it.

A FlatField is created with

FlatField tempInCube_ff = new FlatField( cubeTempFunc, cubeSet)

That is, the first parameter is the FunctionType and the second parameter is the domain Set. We have called our FlatField tempInCubeFF, note the "ff" at the end to denote its type. Of course, you don't need to do so, but it'll be done throughout this tutorial. As said, the FlatField holds not only the "temperature" values you'll provide, it also includes the FunctionType, with its domain and range types, and the domain Set. That is, quite a few things to fit in a short name, so therefore the "ff" at the end. In the tutorial, whenever you come across an object with "ff" at the end, remember, it's a FlatField and expect a lot from it.

Well, we are almost done with the creation of a not-so-simple data object. The only two things missing is to feed the FlatField with actual "temperature" values and to add the whole data to a display. Note that the FlatField above will be waiting for an array of floats (or doubles) with the shape float[ range_dimension ][ number_of_samples ]. The first dimension corresponds to the dimension of the range, in our case it's 1, as we have "temperature", only. The second dimension is the total number of temperature values, which is 10 x 20 x 6, as given by the domain Set. You'd set the values with a call

FlatField.setSamples( float[][] temperValues );

Now we've got some complex data ready to be displayed, so we move on to consider the display of data.

1.2.2 Displaying Data

The first question you might ask yourself is whether to use of a 2D or of a 3D display. Luckily, in VisAD the choice of display is independent of the data. Whether it makes sense to use a 2D or 3D is up to you to decide. There are a variety of displays constructors and display renderers. In the tutorial, we will come across some of them. For now it's important to understand how VisAD displays data.

When building up the data structure, we identified basic quantities as RealTypes. When thinking of the cube of the previous example, it would be obvious to map each one of the dimensions to an axis of a 3D display. The object responsible for the mapping is a ScalarMap. When creating a ScalarMap you consider two things: 1. Which ScalarType will you map and 2. Where will you map it to. The first point is clear, but remember that ScalarType is the superclass of RealType and of TextType. The "where will you map it to" implies not only the axes of a display, but also color, animation, iso-contours, text, shape and many others. These are known in VisAD as DisplayRealTypes, and they define how RealTypes are to be displayed. Let us look closer at such a ScalarMap by constructing a few:

ScalarMap heightZMap = new ScalarMap( height, Display.ZAxis );

ScalarMap widthXMap = new ScalarMap( width, Display.XAxis );

ScalarMap lengthYMap = new ScalarMap( length, Display.YAxis );

This should be pretty clear: we are mapping "height" to the z-axis, width to the x-axis and length to the y-axis. We haven't said anything yet about the display. If the display has such a map, then it'll map "height" data to the z-axis. Suppose we do the same for the other cube dimensions, then we have the whole cube in a 3D display.

To color the cube according to temperature values, you'd do

ScalarMap temperRgbMap = new ScalarMap( temperature, Display.RGB );

and you'd obtain a cube colored according to the "temperature" values. (The actual color table is predefined, but you can redefine it. See Section 4 for some examples of colored cubes und user-defined color-tables.)

ScalarMaps have a boring but helpful relative, the ConstantMaps. ConstantMaps extend ScalarMaps, but take no ScalarType as a parameter in the constructor. Instead, they take a (constant) Java double or a VisAD Real (Real is a subclass of Data). With a ConstantMap map you may add a constant shade of red, say 40%, to a display:

ConstantMap constRedMap = new ScalarMap( 0.4, Display.Red );

or you can put some data at some constant place in a display and/or give it a constant color. For example, you could give a constant green color to a line, or put a surface at some z-value.

After choosing how to depict your data by choosing the right types of ScalarMaps and ConstantMaps, you add them to the display (you will see in the next section how this is done).

Having added all ScalarMaps of your choice, you have to tell the display which data to draw. For that, you use a DataReference. You feed a DataReference with the data you want, like, for example, with a FlatField, and then you add the DataReference to the display. At this step, you might add the data with an array of ConstantMaps, to give your data some different properties.

1.2.3 Interacting with Data

This step might not seem so important as the previous two, but, in fact, you only reach the desired usability of your application with a proper user interface. Apart from the standard Java UI, which you can use in your application, VisAD provides a number of special UIs. Interaction in VisAD generally occurs with the help of a Control. Control is a class which is implemented by GraphicsModeControl, ColorControl, AnimationControl and others. In particular, you'll find that most VisAD Controls have a corresponding UI. The choice of UIs depends not only on your data, but also on how interactive your application can and should be. One thing to notice, though, is that in VisAD the display is the main user interface. Not only does it provide the user with information about the data, but it supports interactive rotation, pan and zoom. It can also have, for example, DirectManipulationRenderers, so that user input occurs directly through the display. By using widgets not only can you change data depiction, but you can also change data values. This might trigger calculations, which, in turn, might change data. As we move along the tutorial, we'll get to know the VisAD widgets.

1.2.4  Summary

Before we start with our first VisAD application, we recap the main steps. When building the data structure, identify the basic quantities as ScalarTypes, that is RealTypes or TextTypes. Pack RealTypes in a RealTupleType. Use a Set (1D, 2D, 3D or N-D and Linear, Gridded, Integer, Irregular or other) as the domain Set. Build the range with the RealTypes identified as the independent variables. If there are more than one RealType, create a RealTupleType for the range. Create a FunctionType with the domain and the range. Create a FlatField based on the function and on the domain set. Put the range values in the FlatField.

For visualization, start with a display. Create the ScalarMaps you find necessary and add them to the display. Create a DataReference, feed it with data, add it to display. The display will be added to a Java Frame or other Java Component to be shown.

Use Controls to set parameters and customize your display. Refine your application with widgets.

We are then ready to write our first VisAD application, which will have a very simple MathType (just a RealType, called height, as a function of the RealType time, that is a FunctionType ( time -> height )), as well as a Field and a Set, a DataReference and a Display.

1.3  Our First VisAD Application

In this section we will plot a simple function, height = f(time), whose MathType reads:
( time -> height )
We assume time to be our independent variable and are given some values for height. We define time and height as RealTypes. Data for time is organized in an Integer1DSet (a subclass of Set, which is a subclass of Data). This Set is our domain Set. As the name says, this Set is a one-dimensional set of (consecutive) integers. We will also need a FunctionType (function our height = f(time) ), a FlatField (another Data object), a DataReference (to link our Data to the display), a 2D display and two ScalarMaps to be included in the display.

ScalarMaps are objects which determine how Data objects are depicted. They define mappings from RealTypes (such as our time and height) to DisplayRealTypes, which are, for example, the x-, y- and z-axis, or the color components, or animation, etc.

We will then use a Java Frame to show our display.


// Import needed classes

import visad.*;
import visad.java2d.DisplayImplJ2D;
import java.rmi.RemoteException;
import java.awt.*;
import javax.swing.*;

/**
  Java Tutorial Example 1_01
  The first tutorial example. A function height = f(time), represented by the
  MathType ( time -> height ), is plotted as a simple line.
  this function is actually the parabola height = 45 - 5 * time^2,
  We have the height values and time is the continuous independent variable, with
  data values given by a Set.
  Run program with "java P1_01"
 */


public class P1_01{

  // Declare variables
  // The quantities to be displayed in x- and y-axis

  private RealType time, height;


  // The function height = f(time), represented by ( time -> height )

  private FunctionType func_time_height;


  // Our Data values for time are represented by the set

  private Set time_set;


  // The Data class FlatField, which will hold time and height data.
  // time data are implicitely given by the Set time_set

  private FlatField vals_ff;


  // The DataReference from the data to display

  private DataReferenceImpl data_ref;


  // The 2D display, and its the maps

  private DisplayImpl display;
  private ScalarMap timeMap, heightMap;

  // The constructor for our example class

  public P1_01 (String []args)
    throws RemoteException, VisADException {

    // Create the quantities
    // Use RealType(String name)

    time = new RealType("time");
    height = new RealType("height");


    // Create a FunctionType, that is the class which represents our function
    // This is the MathType ( time -> height )
    // Use FunctionType(MathType domain, MathType range)

    func_time_height = new FunctionType(time, height);


    // Create the time_set, with 5 integer values, ranging from 0 to 4.
    // That means, that there should be 5 values for height.
    // Use Integer1DSet(MathType type, int length)

    time_set = new Integer1DSet(time, 5);


    // Those are our actual height values
    // Note the dimensions of the array:
    //   float[ number_of_range_components ][ number_of_range_samples]

    float[][] h_vals = new float[][]{{0.0f, 33.75f, 45.0f, 33.75f, 0.0f,} };


    // Create a FlatField, that is the class for the samples
    // Use FlatField(FunctionType type, Set domain_set)

    vals_ff = new FlatField( func_time_height, time_set);


    // and put the height values above in it

    vals_ff.setSamples( h_vals );


    // Create Display and its maps

    // A 2D display

    display = new DisplayImplJ2D("display1");


    // Create the ScalarMaps: quantity time is to be displayed along x-axis
    // and height along y-axis
    // Use ScalarMap(ScalarType scalar, DisplayRealType display_scalar)

    timeMap = new ScalarMap( time, Display.XAxis );
    heightMap = new ScalarMap( height, Display.YAxis );


    // Add maps to display

    display.addMap( timeMap );
    display.addMap( heightMap );


    // Create a data reference and set the FlatField as our data

    data_ref = new DataReferenceImpl("data_ref");

    data_ref.setData( vals_ff );


    // Add reference to display

    display.addReference( data_ref );


    // Create application window, put display into it

    JFrame jframe = new JFrame("My first VisAD application");
    jframe.getContentPane().add(display.getComponent());


    // Set window size and make it visible

    jframe.setSize(300, 300);
    jframe.setVisible(true);


  }


  public static void main(String[] args)
    throws RemoteException, VisADException
  {
    new P1_01(args);
  }

}

The source code is available here. If you compile it with "javac P1_01.java" and run with "java tutorial.s1.P1_01" you should be able to see the following window.

By pressing and dragging with the left mouse button on the display you can move the graph around. By shift-clicking and moving the mouse up and down you can zoom in and out.  Pressing and dragging the middle mouse button (on two-button mouse emulated by simultaneously clicking both buttons) shows a cross cursor that moves with the mouse. The values of the RealTypes at the cursor's position are shown on the upper left corner of the display. 

In the following section we will look further into 2D graphs and show how to control color, axes properties, and show how a different MathType structure can lead to a different rendering.

[Top] [Tutorial Home] [Back]



[Section 2]
[VisAD Tutorial Home]