|

Plotting Engineering and Scientific Data using Java
By Richard G. Baldwin
Go to page: 1 2 Next
Java Programming Notes # 1468
Preface
Excellent language for engineering computations
Because of its platform independence, Java provides an excellent programming
language for engineering and scientific computational experiments, particularly
where extreme execution speed is not a requirement. Programs developed for
such experiments on one platform can be successfully executed on a variety of
platforms without the need to rewrite or recompile.
A large Math library
Furthermore, because if its inherent simplicity, and the availability of a
large Math library, Java provides an excellent programming language for
engineers and scientists who want to do their own programming, but who have no
desire to become programming experts. The code required to conduct an
engineering or scientific computational experiment often consists of little more
than the most rudimentary application of arithmetic in loops using data stored
in arrays or read from disk files.
Now for the bad news
However, there is a downside to this happy story. When doing this sort of
work, it is often very important to see the results of the experiments in the
form of graphs or plots. Unfortunately, the programming required to
produce graphical output from simple engineering and scientific computational
experiments cannot be accomplished using rudimentary programming techniques.
Rather, to do that job right requires considerable expertise in Java
programming.
A generalized plotting program
This lesson develops a generalized plotting program, which is easy to connect
to other programs, (whether they are simple or complex), in order to
display the output from those programs in two-dimensional
Cartesian
coordinates. The plotting program is specifically designed to be
useful to persons having very little knowledge of Java programming.
(Actually, the lesson develops two very similar plotting programs each
designed to display the data in a different format.)
Viewing tip
You may find it useful to open another copy of this lesson in a separate
browser window. That will make it easier for you to scroll back and
forth among the different listings and figures while you are reading about
them.
Supplementary material
I recommend that you also study the other lessons in my extensive collection
of online Java tutorials. You will find those lessons published at
Gamelan.com.
However, as of the date of this writing, Gamelan doesn't maintain a consolidated
index of my Java tutorial lessons, and sometimes they are difficult to
locate there. You will find a consolidated index at
www.DickBaldwin.com.
Figure 1 shows a typical display produced by one of the plotting programs that I will
develop in this lesson. (The other program superimposes all of the
curves on the same set of axes instead of spacing them vertically as shown in
Figure 1.)

Figure 1 Sample Display
While the plotting program itself is quite complex, the code required to
produce the data to be plotted can be very simple. For example, because of the use of the Java Math library, only fourteen lines of
simple Java code were required to produce the data plotted in Figure 1.
The Graph01Demo program
The data displayed in Figure 1 was produced by a program named Graph01Demo.
A listing of that program is shown in Listing 37 near the end of the lesson.
I will explain that program in detail shortly.
A more substantive example
In addition, I will provide and discuss another sample program, which
produces and plots data having considerably more engineering and scientific
significance than the data shown in Figure 1. (This will be a digital
signal processing (DSP) example). Even in that case, you will see that
the program that produces the data is much less complex than the program used to
plot the data.
Using the plotting program with your data
During the course of this lesson, I will explain everything that you will
need to know to cause the output from your own engineering and scientific programs to be
displayed by the plotting program.
The Graph01 program
The graphical display of the data shown in Figure 1 was produced by my
generalized plotting program named Graph01. As you will see later,
this is a long and fairly complex program.
A listing of the plotting program is shown in Listing 39 near the end of the
lesson.
User needn't understand the plotting program
Fortunately, the user of the plotting program doesn't need to understand
anything about the code that makes up the plotting program. All the user
needs to understand is the interface to the program, which I will explain later.
However, for those of you who may be interested, I will also discuss and
explain the plotting
program later in this lesson.
Plotting format
As you can see in Figure 1, the plotting program allows for plotting up to
five independent functions stacked vertically, each with the same vertical and horizontal
axes. This vertical
stacking format makes it easy to compare up to five plots at the same points on
the horizontal axes.
If you need more than five functions, the number of functions can easily be
increased with a few minor changes to the program.
(I will also provide, but will not discuss, another version of the
program, named Graph02, which superimposes up to five plots on the same
coordinate system. In some cases, that is a more useful form of
display. You will find a complete listing of this program in Listing 40
near the end of the lesson.)
Plotting parameters
As you can also see in Figure 1, a set of text fields and a button on the
bottom of the frame make it possible for the user to modify the plotting
parameters and to re-plot the same data with an entirely new set of plotting
parameters.
(It is often true that important but subtle pieces of information can
only be exposed by viewing the same data with different sets of plotting
parameters.)
Same data, different parameters
Figure 2 shows the same data as in Figure 1, but plotted with a different set
of plotting parameters.

Figure 2 Sample Display for Same Data with Different Plotting
Parameters
In the case of Figure 2, the origin was moved to the left, the total expanse of the horizontal
axis was increased, and the space between the tic marks on the vertical axis was
increased from 20 units to 50 units.
(It will help you to see the differences if you will position two
browser windows side-by-side while viewing one display in one browser window
and viewing the other display in the other browser window.)
Discussion
and Sample Code
Testing the plotting program I am assuming that you have accomplished the minimal steps required to get
the Java SDK that is available from Sun up and running.
To run the plotting program named Graph01 in self-test mode, do the
following:
- Copy the code in Listing 39 into a file named Graph01.java.
- Copy the code in Listing 1 into a file named GraphIntfc.java, and
put that file in the same directory as the file named Graph01.java
above.
- Compile the program named Graph01.java using the Java SDK. (Note, you must be using SDK version 1.4 or later.)
At this point, you should be able to execute the program named Graph01 in
self-test mode by entering the following command at the command prompt in the
same directory where you compiled the program:java Graph01
If everything has been done correctly up to this point, the display shown in
Figure 4 should appear on your screen.
Using the plotting program
To use the plotting program with your own data generator program, do the
following:
- Still working in the same directory, define and compile a data generator class that implements the interface named
GraphIntfc01, shown in Listing 1.
- Start the plotting program named Graph01 running by following the
instructions that I will provide below.
Plotting your data using Graph01
Assume that your data-generator class is named MyData, and that you
have successfully compiled it in the same directory as the compiled version of
Graph01.
The next step is to enter the following command at the command prompt in the
same directory. (Note that this command differs from the command given
earlier. This command provides the name of your class as a command-line
parameter following the name of the plotting program.)
java Graph01 MyData
When you do this, the plotting program should start pulling the
necessary data from your data-generator program and plotting that data in the
format shown in Figure 1.
Modifying plotting parameters
Once all the curves have been plotted, you can change any of the plotting parameter
values in the text fields at the bottom of the display and press the button
labeled Graph. When you press the button, the plotting program will
re-plot your data
using the new plotting parameters.
The plotting parameters
Here is the meaning of the plotting-parameter text fields shown in Figure 1:
- xMin and xMax - The values of the left and right ends of all horizontal
axes.
- yMin and yMax - The values of the bottom and top of the vertical axis in
each plotting area. (Note that the different plotting areas are
identified by alternating white and gray backgrounds.)
- xTicInt - The distance between tic marks on the x-axis.
- yTicInt - The distance between tic marks on the y-axis.
- xCalcInc - The distance between the points on the x-axis where values for
y are computed. (Unless your data-generator program is taking too
long to run, you should probably leave this set to 1.0 in order to get the
best quality plots.)
The labels on the axes
Each x-axis has a label at the left end and the right end. Similarly,
each y-axis has a label at the bottom and the top. These labels represent
the values at the extreme ends of the axes. For example in Figure 2, the
label 800 appears at the right end of each x-axis. This is value of the
x-axis where the axis intersects the border of the frame.
Keep the pixels in mind
When adjusting the plotting parameters, keep in mind that the
total width of each of the plotting areas is slightly less than 400 pixels.
(You can easily increase this to full screen width by changing one value
in the Graph01 program and recompiling the program).
While you can theoretically make the horizontal expanse of the x-axes as wide
as you wish, because of the pixel limitation, you cannot see details that
require a resolution of more than 400 points along the x-axis (unless you
modify the program as described above).
The interface named GraphIntfc01
Regardless of its simplicity or its complexity, there are only two requirements for your data-generator program to operate
successfully with the plotting program named Graph01:
- It must implement the interface named GraphIntfc01.
- It must have a constructor that doesn't require any parameters (the
default constructor will satisfy that requirement if you don't need another
constructor).
Implementing GraphIntfc01
All that is required to implement the interface is to define a class that
provides a concrete definition for the six methods declared in Listing 1.
public interface GraphIntfc01{
public int getNmbr();
public double f1(double x);
public double f2(double x);
public double f3(double x);
public double f4(double x);
public double f5(double x);
}//end GraphIntfc01
Listing 1
|
The getNmbr method
On several occasions, I have stated that the plotting program can plot up to
five functions. However, it doesn't have to plot all five functions.
The plotting program can be used to plot any number of functions from one to five.
The method named getNmbr must return an integer value between 1 and 5
that specifies the number of functions to be plotted. The plotting program
uses that value to divide the total plotting surface into the specified
number of plotting areas, and plots each of the functions named f1
through fn in one of those
plotting areas.
The methods named f1, f2, f3, f4, and f5
As you can see in Listing 1, each of these methods receives a double
value as an incoming parameter and returns a double value. In
essence, each of these methods receives a value for x and returns the
corresponding value for y.
One plotting area per method
Each of these methods provides the data to be plotted in one plotting area.
The method named f1 provides the data for the top plotting area, the
method named f2 provides the data for the first plotting area down from
the top, and so forth.
(For example, if the getNmbr method returns a value of 4, the
method named f5 will never be invoked. If getNmbr returns 5,
the method named f5 will be invoked to provide the data for the bottom
plotting area.)
How does it work?
Each plotting area contains a horizontal axis. The plotting program
moves across the horizontal axis in each plotting area one step at a time (moving in incremental
steps equal to the plotting parameter named xCalcInc).
At each step along the way, the plotting program invokes the method
associated with that plotting area, (f1, f2, etc.), passing the
horizontal position as a parameter to the method.
The value returned by the method is assumed to be the vertical value
associated with that horizontal position, and that is the vertical value that is
plotted for that horizontal position.
Doesn't know and doesn't care
The plotting program doesn't know, and doesn't care how the method decides on
the value to return for each value that it receives as an incoming parameter.
The plotting program simply invokes the methods to get the data, and then plots
the data.
Computed "on the fly"
For example, the returned values could be computed and returned "on the
fly," as is the case in the example named Graph01Demo, which we will look at shortly.
Returned from an array
On the other hand, the values could have been computed earlier and saved in
an array, as will be the case in the DSP example that we will look at later.
From a disk file, a database, the internet, etc.
The returned values could be read from a disk file, obtained from a database
on another computer, or obtained from any other source such as another computer
on the internet.
All that matters is that when the plotting program invokes one of the five
methods named f1 through f5, passing a double value as a
parameter, it expects to receive a double value as a return value, and it
will plot the value that it receives.
It is up to you
It is up to you, the author of the data-generator program, to decide how you
will implement the methods named f1 through f5. In some
cases, the implementation may be simple. In other cases, the
implementation may be more complex. The first case that we will examine is
very simple. A subsequent case involving DSP is not so simple.
The class named Graph01Demo
Although this is a very simple class definition, I am going to break it up
and discuss it in fragments in order to help you focus your attention on the
important points. A complete listing of the class definition is shown in
Listing 37 near the end of the lesson.
Defining data-generator classes
This class is used to demonstrate how to write data-generator classes that will operate successfully with the program named
Graph01.
(Figure 1 shows the display of the data produced by this class. You
might want to refer to that figure while examining the code in this class.)
Listing 2 shows the beginning of the class definition, which names the class,
and specifies that the class implements the interface named GraphIntfc01.
class Graph01Demo
implements GraphIntfc01{
Listing 2
|
The number of functions to plot
Listing 3 shows the entire listing of the method named getNmbr.
public int getNmbr(){
return 5;
}//end getNmbr
Listing 3
|
Recall from above that this method must return an integer value between 1 and
5, which tells the plotting program how many functions to plot.
This demonstration plots all five functions, as shown in Figure 1, so this method returns the value
5.
The topmost plotting area
Listing 4 shows the entire method named f1, whose output is plotted in
the topmost plotting area of the display in Figure 1. (This is the area
at the top with the white background.)
public double f1(double x){
return -(x*x)/200.0;
}//end f1
Listing 4
|
This method receives an incoming parameter known locally as x.
(In all five methods defined in this class, the computations are performed on
the fly.) The method computes and returns the negative square of the incoming parameter
(divided by 200). This produces the inverted bowl shape
at the top of Figure 1.
The top-most plotting area with a gray background
The curve plotted in the top-most plotting area with the gray background in Figure
1 is produced by the method named f2, shown in Listing 5.
public double f2(double x){
return -(x*x*x)/200.0;
}//end f2
Listing 5
|
As before, this function receives an incoming parameter known locally as x.
The function computes and returns the negative cube of the incoming parameter
(divided by 200). This produces the curve shown in the top-most
gray area of Figure 1.
The middle white plotting area
The method named f3, shown in Listing 6, produces the curve shown in
the center plotting area with the white background in Figure 1.
public double f3(double x){
return 100*Math.cos(x/10.0);
}//end f3
Listing 6
|
This is a simple cosine curve, which is computed on the fly. Each time
the method is invoked, the incoming parameter named x, is used to
calculate the cosine of an angle in radians given by one-tenth the value of x.
The cosine of that angle is multiplied by 100 and returned.
(Note that the cosine of the angle is computed using a static method of
the standard Java class named Math. This is the class that contains
the Java math library.)
The bottom-most gray plotting area
The curve shown in the bottom-most gray plotting area of Figure 1 is produced by
the method named f4, shown in Listing 7.
public double f4(double x){
return 100*Math.sin(x/20.0);
}//end f4
Listing 7
|
The body of f4 is similar to the body of f3, except that f4
computes and returns sine values instead of cosine values. Also, the value
of x is used differently so that the period of the curve produced by
f4 is twice the period of the curve produced by f3.
The bottom white plotting area
Finally, the bottom white plotting area in Figure 1 shows the output produced
by the method named f5, shown in Listing 8.
public double f5(double x){
return 100*(Math.sin(x/20.0)
*Math.cos(x/10.0));
}//end f5
}//end sample class Graph01Demo
Listing 8
|
This method computes and returns the product of sine and cosine functions
identical to those discussed above.
The end of the class definition
Listing 8 also shows the closing curly brace that signifies the end of the
class definition for the class named Graph01Demo.
That's all you need to know
That's really all that you need to know to be able to make effective use of
the generalized plotting program named Graph01. If you can define
the methods named f1 through f5, which will return the required
values for your computational experiment, then you can make use of this program
to plot your data.
A more substantive example
However, lest you go away believing that this is all too trivial to be
interesting, I am going to show you another example that is far from trivial.
In the next example, I will demonstrate two of the most important operations in
the field commonly referred to as digital signal processing, or DSP for short.
Because many of you are unlikely to be familiar with the techniques and
terminology involved, the discussion will of necessity be fairly shallow.
However, I do want to show at least one example of how you can perform
substantive computational experiments using this approach.
A DSP example
A DSP example showing convolution filtering and spectral analysis is shown in
Figure 3.

Figure 3 A Digital Signal Processing (DSP) Example
In the field of DSP, the five individual plots shown in the plotting areas of
Figure 3 are commonly referred to as traces. I will use that terminology
in this discussion.
White random noise
The top trace in the area with the white background shows about 256 samples
of white random noise. When we get to the code, we will see that this data
was created using a Java pseudo-random number generator.
A convolution filter
The second trace from the top shows a 33-point narrow-band convolution
filter, which is simply a chunk taken out of a sinusoid whose frequency is
one-fourth the sampling frequency. In other words, the sinusoid is
represented by four samples per cycle.
The convolution filter output
The middle trace shows the result of applying the narrow-band convolution
filter to the white noise. The output from the convolution process was
amplified to bring it back into
an appropriate amplitude range for visual analysis.
If you compare the middle trace with the top trace, you will notice that much
of the high-frequency energy and much of the low-frequency energy has been
removed. Most of the energy in the middle trace appears to be about the
same frequency as the frequency of the convolution filter (which is what we
would expect).
Time-domain vs. frequency-domain
The top three traces represent information in the time domain. The
bottom two traces represent information in the frequency
domain.
(Think of the frequency domain as the information that is visible on many
audio systems, consisting of parallel vertical bars with lights that dance up
and down. These lights are often associated with a device referred to as a frequency equalizer. When the music contains a lot of
drums, or other sounds at the bass end, the lights at the low (usually left) end of the
frequency spectrum are very active. When the music contains a lot of
symbols, or sounds at the treble end, the lights at the high (right) end of the
frequency spectrum are very active. That is a form of real-time spectrum
analysis.)
Frequency spectrum analysis
The two bottom traces in Figure 3 result from performing frequency spectrum analysis on
the top trace and the middle trace respectively.
The white noise spectrum
The trace in the gray area immediately below the center is an estimate of the
spectral distribution of the white noise in the top trace. The spectrum
analysis was performed across the frequency range from zero frequency to the
sampling frequency.
While not perfectly flat, as would be the case for perfectly white
noise, you can see that the energy appears to be distributed across that entire
range.
(If we wanted to improve our estimate, we could capture and analyze a much
longer sample of the white noise.)
If you examine this trace carefully, you might notice that there is a point
of near symmetry in the middle. The values that you see above that point
are a mirror-image of the values that you see below that point. (I will
have more to say about this later.)
The filtered noise spectrum
The bottom trace shows an estimate of the spectral distribution of the
filtered noise in the center trace. Again, the spectrum analysis was
performed across the frequency range from zero frequency to the sampling
frequency. Again also, there is a
symmetry point in the middle with everything to the right of that point being a
mirror image of everything to the left of that point.
Two spectral peaks are visible
Unlike the spectral analysis of the white noise, this spectral analysis shows
two obvious peaks. One peak appears at one-fourth the sampling frequency,
and the other peak appears at three-fourths the sampling frequency.
In other words, as we concluded from examining the center trace, the
filtering process removed much of the energy above and below the design
frequency of the convolution filter.
(By changing the design frequency of the convolution filter, and repeating
the process, we could move this peak up or down along the frequency axis.)
What does the symmetry mean?
Without getting into a lot of detail at this point, the point of symmetry
that I identified above is known as the Nyquist folding frequency.
In order to be able to identify the frequency of a sine wave, you must have
at least two samples per cycle of the sine wave. The Nyquist folding
frequency is the frequency at which you have exactly two samples per cycle.
As the frequency of the sine wave continues to increase beyond that point,
without a corresponding change in the sampling frequency, it is impossible to
determine from the samples so obtained whether the frequency is increasing or
decreasing.
An ambiguity in the spectrum analysis
As a result, the spectrum analysis process was unable to determine if the
peak in the frequency spectrum was below or above the folding frequency.
Thus, the bottom trace in Figure 3 shows two peaks which are mirror images of
one another with the folding frequency being half way between the two peaks.
(As a practical matter, when doing spectrum analysis, there is no point in
computing the values above the folding frequency. I did that here just to
illustrate that there is a folding frequency, which is equal to one-half the
sampling frequency.)
Let's see some code
The class used to produce the data displayed in Figure 3 is named Dsp002.
A complete listing of this class definition is shown in Listing 38 near the end
of the lesson.
I will break this class up into fragments and briefly discuss it to show how
you can define significant classes and easily connect them to the generalized
plotting program named Graph01.
As before, having compiled the class named Dsp002, you would exercise it by
entering the following at a command prompt:
java Graph01 Dsp002
Different from the previous example class
This class differs from the class named Graph01Demo in one very
significant way. In that class, all the values returned by the methods
named f1 through f5 were computed on the fly as the methods were
called.
In this new class named Dsp002, all the data is generated and stored
in array objects when an object of the class named Dsp002 is
instantiated. When the methods named f1 through f5 are
invoked later, they simply retrieve the data from the array objects and return
that data to the plotting program.
Basic operation of the program
As mentioned earlier, this program applies a narrow-band convolution filter to white noise, and then computes the amplitude spectrum of the filtered
noise using a Discrete Fourier Transform (DFT) algorithm. The spectrum of the white noise is also computed.
All of the processing occurs when an object of the class is instantiated, and
the processed results are saved in arrays.
The input noise, the filter, the filtered output, and the two spectra are deposited in five arrays for later retrieval and display.
The data in the five arrays are returned by the methods named f1, f2, f3, f4, and f5 respectively.
The values that are returned by the methods are scaled for appropriate display in the plotting areas provided by the program named
Graph01.
Data and filter lengths
The code in Listing 9 establishes the data lengths for the white noise, the
convolution filter, the filtered output, and the spectrum.
class Dsp002 implements GraphIntfc01{
int operatorLen = 33;
int dataLen = 256+operatorLen;
int outputLen =
dataLen - operatorLen;
int spectrumPts = outputLen;
Listing 9
|
Create data arrays
The code in Listing 10 creates the array objects that will be used to store
the data until it is retrieved by the methods named f1 through f5.
double[] data = new double[dataLen];
double[] operator =
new double[operatorLen];
double[] output =
new double[outputLen];
double[] spectrumA =
new double[spectrumPts];
double[] spectrumB =
new double[spectrumPts];
Listing 10
|
Generate and save the white noise
Most of the hard work is done by the constructor or by methods called by the
constructor.
The code in Listing 11 generates and saves the white noise in the array
object named data.
public Dsp002(){//constructor
Random generator = new Random(
new Date().getTime());
for(int cnt=0;cnt < data.length;
cnt++){
//Get data, scale it, remove the
// dc offset, and save it.
data[cnt] = 100*generator.
nextDouble()-50;
}//end for loop
Listing 11
|
The random noise generator seed
Note that by virtue of the way this white noise is being generated, a
different seed is passed to the constructor for the Random class each
time an object of the Dsp002 class is instantiated. Thus, each new
object presents different random noise.
(In some cases, this may not be
desirable and it may be preferable to use the same seed each time an object is
instantiated.)
Create the convolution operator
The code in Listing 12 creates the 33-point convolution operator, as a
segment of a cosine wave, and saves it in the designated array.
for(int cnt = 0; cnt < operatorLen;
cnt++){
operator[cnt] = Math.cos(
cnt*2*Math.PI/4);
}//end for loop
Listing 12
|
Note that the constant value of 4 in the denominator of the argument to the
cos method specifies the frequency of the cosine wave relative to the
sampling frequency. (In this case, the frequency of the cosine wave is
one-fourth the sampling frequency.)
Apply the convolution operator
The code in Listing 13 invokes a static method named convolve in a
class named Convolve01 to apply the convolution operator to the white
noise and save the filtered result in the appropriate array. I will
briefly discuss this method later.
Convolve01.convolve(data,dataLen,
operator,operatorLen,output);
Listing 13
|
Compute the spectrum of the two traces
The code in Listing 14 invokes a static method named dft of a class
named Dft01 twice in succession to compute the spectra for the white
noise and the filtered noise, and to save those spectra in the appropriate
arrays.
Dft01.dft(data,spectrumPts,
spectrumA);
Dft01.dft(output,spectrumPts,
spectrumB);
}//end constructor
Listing 14
|
All results have been computed and saved
That is the end of the constructor. At this point, all the results have been computed and saved in the appropriate
arrays for later retrieval by the methods named f1 through f5.
The object is simply setting in
memory waiting to have its encapsulated data retrieved and plotted.
The getNmbr method
The getNmbr method for this class is exactly the same as for the class
discussed earlier. As before, it returns the integer value 5, telling the
plotting program that there are five plots to be generated.
public int getNmbr(){
return 5;
}//end getNmbr
Listing 15
|
The method named f1
The method named f1 is representative of all five of the methods named
f1 through f5 in this class. Therefore, I will discuss only
the first of the five methods in detail.
public double f1(double x){
int index = (int)Math.round(x);
if(index < 0 ||
index > data.length-1){
return 0;
}else{
return data[index];
}//end else
}//end f1
Listing 16
|
In all five cases, the purpose of the method is to fetch and return a value
from an array, where the incoming parameter will be converted to an array index.
Convert incoming parameter to an index
The incoming parameter is received as type double. However, an
array must be indexed using type int. The first statement in the
method invokes the round method of the Math class to convert the
double value to the nearest integer. That integer will be used as
an array index.
Stay within array bounds
Following this, the method applies some logic to confirm that the index value
is within the bounds of the array. If not, the method returns the value 0.
If the index is within the array bounds, the method retrieves and returns the
value stored at that index location in the array.
And that's all there is to it.
Methods f2 through f5
Except for the scale factors applied to the data before returning it, the
behavior of the methods named f2 through f5, is essentially the
same as the behavior of the method named f1. In each case, the
method retrieves, scales, and returns a value previously stored in an array.
Therefore, I won't discuss these other methods. You can view them in
Listing 38 near the end of the lesson.
And that ends the definition of the class named Dsp002.
The class named Convolve01
The entire class named Convolve01 is shown in Listing 17.
If you already understand convolution, you will probably find the code in
this class straightforward. If not, the code will probably still be
straightforward, but the reason for the code may be obscure.
class Convolve01{
public static void convolve(
double[] data,
int dataLen,
double[] operator,
int operatorLen,
double[] output){
//Apply the operator to the data,
// dealing with the index
// reversal required by
// convolution.
for(int i=0;
i < dataLen-operatorLen;i++){
output[i] = 0;
for(int j=operatorLen-1;j>=0;
j--){
output[i] +=
data[i+j]*operator[j];
}//end inner loop
}//end outer loop
}//end convolve method
}//end Class Convolve01
Listing 17
|
Making a long story short
To make a long story short, the class named Convolve01 provides a
static method named convolve, which applies an incoming convolution
operator to an incoming set of data and deposits the filtered data in an output
array whose reference is received as an incoming parameter.
This class could easily be broken out and put in a library as a stand-alone
class, or the convolve method could be added to a class containing a
variety of DSP methods.
The discrete Fourier transform (DFT)
The entire class named Dft01 is shown in Listing
18.
As with convolution, if you already understand the discrete Fourier
transform, you will probably find the code in this class to be straightforward.
If not, the code will probably still be straightforward, but the reasons for the
code may be obscure. Since the purpose of this lesson is not to explain
digital signal processing concepts, I won't attempt to provide a detailed
explanation for
the code in this method.
class Dft01{
public static void dft(
double[] data,
int dataLen,
double[] spectrum){
//Set the frequency increment to
// the reciprocal of the data
// length. This is convenience
// only, and is not a requirement
// of the DFT algorithm.
double delF = 1.0/dataLen;
//Outer loop iterates on frequency
// values.
for(int i=0; i < dataLen;i++){
double freq = i*delF;
double real = 0;
double imag = 0;
//Inner loop iterates on time-
// series points.
for(int j=0; j < dataLen; j++){
real += data[j]*Math.cos(
2*Math.PI*freq*j);
imag += data[j]*Math.sin(
2*Math.PI*freq*j);
spectrum[i] = Math.sqrt(
real*real + imag*imag);
}//end inner loop
}//end outer loop
}//end dft
Listing 18
|
Brief explanation
Once again, to make a long story short, this class provides a static method named
dft, which computes and returns the amplitude spectrum of an incoming time series.
The amplitude spectrum is computed as the square root of the sum of the squares of the real and imaginary parts.
A DFT algorithm can compute any number of points in the frequency domain.
In this case, the number of points computed in the frequency domain is equal to the number of samples in the incoming time series,
which is a fairly common practice.
The method deposits the frequency data in an array whose reference is received as an incoming parameter.
As with convolution, this class could easily be broken out and put in a library as a stand-alone class, or the
dft method could be added to a class containing a variety of DSP methods.
The plotting programs
Now that you have examined the examples, some of you may be interested in an explanation of the plotting program itself.
If you are interested only in how to use the plotting programs, and are not
interested in the details of the plotting programs, skip ahead to the section
entitled Run the Program.
If you are interested in learning how the plotting programs do what they do,
keep reading.
Two plotting programs
Two very similar plotting programs are shown in the listings near the end of
the lesson. The program named Graph01, shown in Listing 39, can be used to plot
as many as five separate functions, each in its own plotting area.
Examples of the display produce by this program are shown in Figures 1,
2, 3, and 4. I will briefly discuss this program in the paragraphs that
follow.
The program named Graph02, shown in Listing 40, can also be used to plot
as many as five
separate functions. In this case, however, the graphs produced by the functions are
superimposed in the same plotting area. This is simply an alternative
display format. I won't discuss any of the
particulars of this program, but if you understand the program named Graph01,
you will have no difficulty understanding this program named Graph02 well.
The program named Graph01
This program is designed to access a class file that implements the interface
named GraphIntfc01, and to plot up to five functions defined in that
class file.
The methods in the class corresponding to the functions to be plotted are named
f1, f2, f3, f4, and f5.
As you learned in the earlier discussion, the class containing the functions must
also define a static method named
getNmbr. This method takes no parameters
and returns the number of functions to
be plotted. If this method returns a
value greater than 5, a NoSuchMethodException will be thrown.
Separate plotting areas
The overall plotting surface is divided into the required number of equally sized plotting areas.
One function is plotted on
Cartesian coordinates in
each plotting area.
A noarg constructor is required
The constructor for the class
that implements GraphIntfc01 must not
require any parameters. This is because the newInstance method of the
Class class is used to instantiate an object, based on a String
provided as a command-line parameter. The newInstance method can
only create objects using a noarg constructor.
Some methods may not be invoked
If the getNmbr method returns a value less
than 5, then the methods that will not be invoked begin with f5 and work down toward f1. For example, if the
value returned by getNmbr is 3, then the program will invoke the methods named f1, f2,
and f3. While the methods named f4 and f5 must exist
in order to satisfy the interface, they won't be invoked. Therefore, it
doesn't matter what those methods return as long as it is type double.
The visual appearance
As shown in Figure 1, the plotting areas have alternating
white and gray backgrounds to make them
easy to separate visually.
All curves are plotted in black. A Cartesian coordinate system with axes,
tic marks, and labels is drawn in red
in each plotting area.
The Cartesian coordinate system in each
plotting area has the same horizontal
and vertical scale, as well as the
same tic marks and labels on the axes.
The labels displayed on the axes,
correspond to the values of the extreme
edges of the plotting area.
A test class
The program also compiles a test
class named junk, which contains the five required
methods plus the method named getNmbr.
This makes it easy to compile and test the program in a stand-alone mode.
Usage instructions
At runtime, the name of the class that
implements the GraphIntfc01 interface
must be provided as a command-line
parameter.
If the command-line parameter is
missing, the program instantiates an
object from the internal test class named
junk and plots the data provided by
that object. Thus, you can test the program by running it with no
command-line parameter. This will produce the display shown in Figure 4.

Figure 4 Graphic Display for Self-Test Class
If the command-line parameter is provided, the program instantiates an object
of the class whose name matches the parameter, and plots the data provided by
that object.
Plotting parameters
This program provides the following
text fields for user input, along with
a button labeled Graph. This allows
the user to adjust the parameters and
re-plot the graph as many times as needed with as
many different plotting scales as may be needed:
- xMin = minimum x-axis value
- xMax = maximum x-axis value
- yMin = minimum y-axis value
- yMax = maximum y-axis value
- xTicInt = tic interval on x-axis
- yTicInt = tic interval on y-axis
- xCalcInc = calculation interval
The user can modify any of these
parameters and then press the Graph
button to cause the five functions
to be re-plotted according to the
new parameters.
A new object
Whenever the Graph button is pressed,
the event handler for that button instantiates a new
object of the class that implements
the GraphIntfc01 interface.
Depending
on the nature of that class, this may
be redundant in some cases. However,
it is useful in those cases where it
is necessary to refresh the values of
instance variables defined in the
class (such as a counter, for example).
(I will show you how to eliminate this feature from the plotting program
if you decide that it is unnecessary for your data.)
Requires Java SDK 1.4 or later
This program uses constants that were
first defined in the Color class of
v1.4.0. Therefore, the program
requires v1.4.0 or later to compile and execute correctly.
Will discuss in fragments
I will discuss this program in fragments. As mentioned earlier, a
complete listing of the program is provided in Listing 39 near the end of the lesson.
You should be able to copy and paste that code into your Java source file, and
then compile and execute it successfully.
The class named Graph01
The entire class, including the main method is shown in Listing 19.
class Graph01{
public static void main(
String[] args)
throws NoSuchMethodException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException{
if(args.length == 1){
//pass command-line parameter
new GUI(args[0]);
}else{
//no command-line parameter given
new GUI(null);
}//end else
}// end main
}//end class Graph01 definition
Listing 19
|
The primary purpose of main method is to instantiate an object of the
class named GUI.
In addition, the main method checks to see if the user provided a
command-line parameter, and if so, passes it along to the constructor for the
GUI class.
The class named GUI
The beginning of the GUI class is shown in Listing 20.
class GUI extends JFrame
implements ActionListener{
//Define plotting parameters and
// their default values.
double xMin = -10.0;
double xMax = 256.0;
double yMin = -100.0;
double yMax = 100.0;
//Tic mark intervals
double xTicInt = 16.0;
double yTicInt = 20.0;
//Tic mark lengths. If too small
// on x-axis, a default value is
// used later.
double xTicLen = (yMax-yMin)/50;
double yTicLen = (xMax-xMin)/50;
//Calculation interval along x-axis
double xCalcInc = 1.0;
//Text fields for plotting parameters
JTextField xMinTxt =
new JTextField("" + xMin);
JTextField xMaxTxt =
new JTextField("" + xMax);
JTextField yMinTxt =
new JTextField("" + yMin);
JTextField yMaxTxt =
new JTextField("" + yMax);
JTextField xTicIntTxt =
new JTextField("" + xTicInt);
JTextField yTicIntTxt =
new JTextField("" + yTicInt);
JTextField xCalcIncTxt =
new JTextField("" + xCalcInc);
//Panels to contain a label and a
// text field
JPanel pan0 = new JPanel();
JPanel pan1 = new JPanel();
JPanel pan2 = new JPanel();
JPanel pan3 = new JPanel();
JPanel pan4 = new JPanel();
JPanel pan5 = new JPanel();
JPanel pan6 = new JPanel();
//Misc instance variables
int frmWidth = 400;
int frmHeight = 430;
int width;
int height;
int number;
GraphIntfc01 data;
String args = null;
//Plots are drawn on the canvases
// in this array.
Canvas[] canvases;
Listing 20
|
The code in Listing 20 declares and in some cases initializes several instance variables that are required later to support the plotting process.
The comments and the names of the variables generally indicate the purpose of
those variables.
The constructor for the GUI class
The beginning of the constructor for the GUI class is shown in Listing 21.
GUI(String args)throws
NoSuchMethodException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException{
if(args != null){
//Save for use later in the
// ActionEvent handler
this.args = args;
//Instantiate an object of the
// target class using the String
// name of the class.
data = (GraphIntfc01)
Class.forName(args).
newInstance();
}else{
//Instantiate an object of the
// test class named junk.
data = new junk();
}//end else
Listing 21
|
The main purpose of the code in Listing 21 is to instantiated the object that
will provide the data to be plotted. If the user provided the name of a
class as a command-line argument, an attempt will be made to create a
newInstance of that class.
(In case you are unfamiliar with this approach, this is one way to create an object
of a class whose name is specified as a String at runtime.)
Otherwise, the code in Listing 21 will instantiate an object of the test
class named junk (to be discussed later).
Array to hold Canvas objects
Each of the separate plotting areas in Figure 1 is an object of a class that
extends the Canvas class. The code in Listing 22 invokes the
getNmbr method on the new object to determine how many functions are to be
plotted. Then it creates an array object to hold the requisite number of
Canvas objects.
//Create array to hold correct
// number of Canvas objects.
canvases =
new Canvas[data.getNmbr()];
//Throw exception if number of
// functions is greater than 5.
number = data.getNmbr();
if(number > 5){
throw new NoSuchMethodException(
"Too many functions. "
+ "Only 5 allowed.");
}//end if
Listing 22
|
Although the limit could easily be increased, this program is
currently limited to plotting the output from five functions. The code in Listing 22
checks this limit and throws an exception if an attempt is made to plot
more than five functions.
Routine GUI construction code
Although somewhat long and rather tedious, the code in Listing 23 is
comp |