July 31, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Adaptive Filtering in Java, Getting Started

  • September 20, 2005
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Run the Programs

I encourage you to copy, compile and run the program provided in Listing 32 below.  You will need some other classes in addition to the program in Listing 32.

I have provided the source code for the class named PlotALot05 in Listing 33.  You will need to go to the previous lesson entitled Plotting Large Quantities of Data using Java to get the source code for the other required PlotALot classes.

In addition, you will need to go to the lesson entitled Spectrum Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT Algorithm to get the source code for the class named ForwardRealToComplex01.

Have fun and learn

Above all, have fun and use this program to learn as much as you can about the basics of adaptive filtering.

Summary

In this lesson, I explained adaptive filtering using an LMS adaptive algorithm in a relatively simple scenario.

What's Next?

The next lesson will tackle a considerably more complicated scenario for adaptive filtering.  In the next lesson, I will teach you how to write a whitening filter program for the extraction of wide band signals corrupted by narrow band noise.

Following that, the lessons in the series will become somewhat more general.  I plan to publish lessons that explain and provide examples for the four common scenarios in which adaptive filtering is used:

  • System Identification
  • Inverse System Identification
  • Noise Cancellation
  • Prediction

Somewhere along the way I may publish a lesson that explains and illustrates the difference between least mean square (LMS) and recursive least squares (RLS) adaptive algorithms.

Complete Program Listings

Complete listings of two of the programs discussed in this lesson are provided in Listing 32 and Listing 33 below.

/*File Adapt01.java.java
Copyright 2005, R.G.Baldwin
This program illustrates one aspect of time-
adaptive signal processing.
Two sampled time series, chanA and chanB,
are presented to the an adaptive algorithm. Each
time series contains the same wide band signal
plus white noise that is uncorrelated between the
two channels.
The signal in chanB may be delayed or advanced by
up to 6 samples relative to the signal in chanA.
A 9-point convolution operator is developed
adaptively.  When the adaptive process converges
successflly, the time series produced by applying
the convolution operator to chanA matches the
signal on chanB.
The user provides the following information as
command line parameters:
timeShift - A negative value delays chanB
relative to chanA and a positive value advances
chanB relative to chanA.  If no command line
parameters are provided, a default timeShift
value of -4 is used.  This causes a four-sample
delay on chanB relative to chanA.  Because the
convolution operator has only nine points, time
shifts greater than plus or minus four samples
cannot be resolved and an adaptive solution will
not be found.  Time shifts greater than six
samples cause the program to terminate.
feedbackGain - Controls the convergence rate of
the adaptive process.  If the value is very low,
the process will take a long time to converge. 
If the value is too high, the process will become
unstable.  If no command line parameters are
provided, a feedbackGain value of 0.001 is used.
Depending on the random noise level, the process
appears to be stable for feedbackGain values as
large as 0.004, but goes unstable for a
feedbackGain value of 0.005.
noiseLevel - Controls the amount of uncorrelated
white noise that is added to the signal on each
of the channels.  If no command line parameters
are provided, the default noise level is 0.0  The
noise level is provided as a decimal fraction of
the signal level.  For example, a noise level
of 0.1 causes the level of the noise that is
added to each of the channels to be one tenth of
the signal level on that channel.
numberIterations - The number of adaptive
iterations performed before the adaptive process
terminates and all of the data that has been
saved is plotted.  If no command line parameters
are provided, the default is 100 iterations.
The following time series are plotted in color
showing the convergence of the adaptive
algorithm:
black: input to the filter
red: output from the filter
blue: adaptive target
green: error
In addition, the frequency response of the filter
at the beginning and at the end of every tenth
iteration is computed and displayed when the
adaptive process terminates.  Both the amplitude
and the phase response of the filter are computed
and plotted.  Also, the filter is plotted as a
time series on the same iterations that the
frequency response is computed.  Thus, the shape
of the filter can be compared with the frequency
response of the filter.
The filter is initialized with a single
coefficient value of 1 at the center and 0 for
all of the other coefficient values.  The ideal
solution is a single coefficient value of 1 at a
location in the filter that matches the time
shift between chanA and the target.  The value
of 1 can be seen to progress from the center of
the filter to the correct location in the filter
as the program iterates.  In addition, the phase
response can be seen to change appropriately as
the program iterates.
Tested using J2SE 5.0 and WinXP
J2SE 5.0 or later is required.
************************************************/
import static java.lang.Math.*;//J2SE 5.0 req
class Adapt01{
  public static void main(String[] args){
    //Default values
    int timeShift = -4;
    double feedbackGain = 0.001;
    double noiseLevel = 0.0;
    int numberIterations = 100;
   
    if(args.length != 4){
      System.out.println(
              "Usage: java Adapt01 " +
                "timeShift feedbackGain " +
                  "noiseLevel numberIterations");
      System.out.println(
                  "Negative timeShift is delay");
      System.out.println(
             "Using -4 sample shift by default");
      System.out.println(
          "Using 0.001 feedbackGain by default");
      System.out.println(
             "noiseLevel is a decimal fraction");
      System.out.println("Using 0.0 by default");
      System.out.println(
                   "numberIterations is an int");
      System.out.println("Using 100 by default");
    }else{//Command line params were provided.
      //Convert String to int
      timeShift = Integer.parseInt(args[0]);
      System.out.println(
                      "timeShift: " + timeShift);
      //Convert String to double
      feedbackGain = Double.parseDouble(args[1]);
      System.out.println(
                "feedbackGain: " + feedbackGain);
      //Convert String to double
      noiseLevel = Double.parseDouble(args[2]);
      System.out.println(
                    "noiseLevel: " + noiseLevel);
      //Convert String to int
      numberIterations =
                       Integer.parseInt(args[3]);
      System.out.println(
        "numberIterations: " + numberIterations);
    }//end else
   
    if(abs(timeShift) > 6){
      System.out.println(
        "Time shift magnitude > 6 not allowed.");
      System.out.println("Terminating");
      System.exit(0);
    }//end if
   
    //Instantiate an object of the class and
    // execute the adaptive algorithm using the
    // specified feedbackGain and other
    // parameters.
    new Adapt01().process(timeShift,
                          feedbackGain,
                          noiseLevel,
                          numberIterations);
  }//end main
  //-------------------------------------------//
 
  void process(int timeShift,
               double feedbackGain,
               double noiseLevel,
               int numberIterations){
    //The process begins with a filter having
    // the following initial coefficients.
    double[] filter = {0,0,0,0,1,0,0,0,0};
   
    //Create array objects that will be used as
    // delay lines.
    double[] rawData = new double[13];
    double[] chanA = new double[9];
    double[] chanB = new double[9];
   
    //Instantiate a plotting object for four
    // data channels.  This object will be used
    // to plot the time series data.
    PlotALot05 plotObj = new PlotALot05(
                 "Time Series",398,250,25,5,4,4);
           
    //Instantiate a plotting object for two
    // channels of filter frequency response
    // data.  One channel is used to plot the
    // amplitude response in db and the other
    // channel is used to plot the phase on a
    // scale that extends from -180 degrees to
    // +180 degrees.
    PlotALot03 freqPlotObj =
         new PlotALot03("Freq",264,487,20,2,0,0);
   
    //Instantiate a plotting object to display
    // the filter as a short time series at
    // intervals during the adaptive  process.
    // Note that the minimum allowable width
    // for a Frame is 112 pixels under WinXP.
    // Therefore, the following display doesn't
    // synchronize properly for filter lengths
    // less than 25 coefficients.  However, the
    // code that feeds the filter data to the
    // plotting object later in the program
    // extends the length of the filter to
    // cause it to synchronize and to plot one
    // set of filter coefficients on each axis.
    PlotALot01 filterPlotObj = new PlotALot01(
                "Filter",(filter.length * 4) + 8,
                                   487,40,4,0,0);
   
    //Display frequency response of initial
    // filter computed at 128 points between zero
    // and the folding frequency.
    displayFreqResponse(filter,
                        freqPlotObj,
                        128,
                        filter.length - 5);
   
    //Display the initial filter as a time series
    // on the first axis.
    for(int cnt = 0;cnt < filter.length;cnt++){
      filterPlotObj.feedData(30*filter[cnt]);
    }//end for loop
    //Extend the filter with a value of 2.5 for
    // plotting to cause it to synchronize
    // properly with the plotting software.  See
    // earlier comment on this topic.  Note that
    // this will not cause the plot to
    // synchronize properly on an operating
    // system for which the sum of the left and
    // right insets on a Frame object are
    // different from 8 pixels.
    if(filter.length <= 26){
      for(int cnt = 0;cnt < (26 - filter.length);
                                          cnt++){
        filterPlotObj.feedData(2.5);
      }//end for loop
    }//end if
   
    //Declare and initialize variables used in
    // the adaptive process.
    double output = 0;
    double err = 0;
    double target = 0;
    double input = 0;
    double dataScale = 25;//Default data scale
   
    //Do the iterative adaptive process
    for(int cnt = 0;cnt < numberIterations;
                                          cnt++){
      //Add new input data to the delay line
      // containing the raw input data.
      flowLine(rawData,Math.random() - 0.5);
     
      //Extract the middle sample from the input
      // data delay line, add some random noise,
      // and insert it into the delay line
      // containing the data for chanA.
      flowLine(chanA,dataScale*rawData[6] +
              noiseLevel*dataScale*(Math.random()
                                         - 0.5));
     
      //Extract data with a time shift from the
      // input data delay line, add some random
      // noise, and insert it into the delay line
      // containing the data for chanB.
      flowLine(chanB,
            dataScale*rawData[6 + timeShift] +
              noiseLevel*dataScale*(Math.random()
                                         - 0.5));
      //Get the middle sample from the chanA
      // delay line for plotting.
      input = chanA[chanA.length/2];
     
      //Apply the current filter coefficients to
      // the chanA data contained in the delay
      // line.
      output = dotProduct(filter,chanA);
     
      //Get the middle sample from the chanB
      // delay line and use it as the adaptive
      // target.  In other words, the adaptive
      // process will attempt to cause the
      // filtered output to match the value in
      // the middle of the chanB delay line.
      target = chanB[chanB.length/2];
     
      //Compute the error between the current
      // filter output and the target.
      err = output - target;
     
      //Update the filter coefficients
      for(int ctr = 0;ctr < filter.length;ctr++){
        filter[ctr] -=
                     err*chanA[ctr]*feedbackGain;
      }//end for loop
      //This is the end of the adaptive process.
      // The code beyond this point is used to
      // display information about the adaptive
      // process.
      //Feed the time series data to the plotting
      // object.
      plotObj.feedData(input,output,target,err);
                 
      //Compute and plot the frequency response
      // and plot the filter as a time series
      // every 10 iterations.
      if(cnt%10 == 0){
        displayFreqResponse(filter,
                            freqPlotObj,
                            128,
                            filter.length - 5);
     
        //Plot the filter coefficient values.
        // Scale the coefficient values by 30
        // to make them compatible with the
        // plotting software.
        for(int ctr = 0;ctr < filter.length;
                                          ctr++){
          filterPlotObj.feedData(30*filter[ctr]);
        }//end for loop
        //Extend the filter with a value of 2.5
        // for plotting to cause it to
        // synchronize with one filter on each
        // axis.  See explanatory comment
        // earlier.
        if(filter.length <= 26){
          for(int count = 0;
                    count < (26 - filter.length);
                                        count++){
            filterPlotObj.feedData(2.5);
          }//end for loop
        }//end if
      }//end if on cnt%10
                 
    }//end for loop
   
    //Cause all the data to be plotted in the
    // screen locations specified.
    plotObj.plotData();
    freqPlotObj.plotData(0,201);
    filterPlotObj.plotData(265,201);
   
  }//end process
  //-------------------------------------------//
 
  //This method simulates a tapped delay line.
  // It receives a reference to an array and
  // a value.  It discards the value at
  // index 0 of the array, moves all the other
  // values by one element toward 0, and
  // inserts the new value at the top of the
  // array.
  void flowLine(double[] line,double val){
    for(int cnt = 0;cnt < (line.length - 1);
                                          cnt++){
      line[cnt] = line[cnt+1];
    }//end for loop
    line[line.length - 1] = val;
  }//end flowLine
  //-------------------------------------------//
 
  //This method receives two arrays and treats
  // the first n elements in each array as a pair
  // of vectors.  It computes and returns the
  // vector dot product of the two vectors.  If
  // the length of one array is greater than the
  // length of the other array, it considers the
  // number of dimensions of the vectors to be
  // equal to the length of the smaller array.
  double dotProduct(double[] v1,double[] v2){
    double result = 0;
    if((v1.length) <= (v2.length)){
      for(int cnt = 0;cnt < v1.length;cnt++){
        result += v1[cnt]*v2[cnt];
      }//end for loop
      return result;
    }else{
      for(int cnt = 0;cnt < v2.length;cnt++){
        result += v1[cnt]*v2[cnt];
      }//end for loop
      return result;
    }//end else
  }//end dotProduct
  //-------------------------------------------//
 
  //This method receives a reference to a double
  // array containing a convolution filter
  // along with a reference to a plotting object
  // capable of plotting two channels of data.
  // It also receives a value specifying the
  // number of frequencies at which a DFT is
  // to be performed on the filter, along with
  // the sample number that represents the zero
  // time location in the filter.  The method
  // uses this information to perform a DFT on
  // the filter from zero to the folding
  // frequency.  It feeds the amplitude spectrum
  // and the phase spectrum to the plotting
  // object for plotting.
  void displayFreqResponse(double[] filter,
                           PlotALot03 plot,
                           int len,
                           int zeroTime){
    //Create the arrays required by the Fourier
    // Transform.
    double[] timeDataIn = new double[len];
    double[] realSpect = new double[len];
    double[] imagSpect = new double[len];
    double[] angle = new double[len];
    double[] magnitude = new double[len];
   
    //Copy the filter into the timeDataIn array.
    System.arraycopy(filter,0,timeDataIn,0,
                                  filter.length);
    //Compute DFT of the filter from zero to the
    // folding frequency and save it in the
    // output arrays.
    ForwardRealToComplex01.transform(timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude,
                                     zeroTime,
                                     0.0,
                                     0.5);
                                    
    //Plot the magnitude data.  Convert to
    // normalized decibels before plotting.
   
    //Eliminate or change all values that are
    // incompatible with log10 method.
    for(int cnt = 0;cnt < magnitude.length;
                                          cnt++){
      if((magnitude[cnt] == Double.NaN) ||
                          (magnitude[cnt] <= 0)){
        magnitude[cnt] = 0.0000001;
      }else if(magnitude[cnt] ==
                       Double.POSITIVE_INFINITY){
        magnitude[cnt] = 9999999999.0;
      }//end else if
    }//end for loop
   
    //Now convert magnitude data to log base 10
    for(int cnt = 0;cnt < magnitude.length;
                                          cnt++){
      magnitude[cnt] = log10(magnitude[cnt]);
    }//end for loop
   
    //Note that from this point forward, all
    // references to magnitude are referring to
    // log base 10 data, which can be thought of
    // as scaled decibels.
    //Find the absolute peak value
    double peak = -9999999999.0;
    for(int cnt = 0;cnt < magnitude.length;
                                          cnt++){
      if(peak < abs(magnitude[cnt])){
        peak = abs(magnitude[cnt]);
      }//end if
    }//end for loop
    //Normalize to 50 times the peak value and
    // shift up the screen by 50 units to make
    // the values compatible with the plotting
    // program.  Recall that adding a constant to
    // log values is equivalent to scaling the
    // original data.
    for(int cnt = 0;cnt < magnitude.length;
                                          cnt++){
      magnitude[cnt] =
                     50*magnitude[cnt]/peak + 50;
    }//end for loop
    //Now feed the normalized decibel data to the
    // plotting object.  The angle data ranges
    // from -180 to +180.  Scale it down by a
    // factor of 20 to make it compatible with
    // the plotting format being used.
    for(int cnt = 0;cnt < magnitude.length;
                                          cnt++){
      plot.feedData(
                   magnitude[cnt],angle[cnt]/20);
    }//end for loop
   
  }//end displayFreqResponse
  //-------------------------------------------//
}//end class Adapt01

Listing 32

 

/*File PlotALot05.java 
Copyright 2005, R.G.Baldwin
This program is an update to the program named 
PlotALot04 for the purpose of plotting four
data channels.  See PlotALot04 for descriptive
comments.  Otherwise, the comments in this
program have not been updated to reflect this
update.
The program was tested using J2SE 5.0 and WinXP.
Requires J2SE 5.0 to support generics.
************************************************/
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class PlotALot05{
  //This main method is provided so that the
  // class can be run as an application to test
  // itself.
  public static void main(String[] args){
    //Instantiate a plotting object using the
    // version of the constructor that allows for
    // controlling the plotting parameters.
    PlotALot05 plotObjectA = 
            new PlotALot05("A",158,250,25,5,4,4);
    
    //Feed quadruplets of data values to the 
    // plotting object.
    for(int cnt = 0;cnt < 115;cnt++){
      //Plot some white random noise. Note that
      // fifteen of the values for each time
      // series are not random.  See the opening
      // comments for a discussion of the reasons
      // why.
      double valBlack = (Math.random() - 0.5)*25;
      double valRed = valBlack;
      double valBlue = valBlack;
      double valGreen = valBlack;
      //Feed quadruplets of values to the
      // plotting object by invoking the feedData
      // method once for each quadruplet of data
      // values.
      if(cnt == 57){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 58){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 59){
        plotObjectA.feedData(25,25,25,25);
      }else if(cnt == 60){
        plotObjectA.feedData(-25,-25,-25,-25);
      }else if(cnt == 61){
        plotObjectA.feedData(25,25,25,25);
      }else if(cnt == 62){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 63){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 26){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 27){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 28){
        plotObjectA.feedData(20,20,20,20);
      }else if(cnt == 29){
        plotObjectA.feedData(20,20,20,20);
      }else if(cnt == 30){
        plotObjectA.feedData(-20,-20,-20,-20);
      }else if(cnt == 31){
        plotObjectA.feedData(-20,-20,-20,-20);
      }else if(cnt == 32){
        plotObjectA.feedData(0,0,0,0);
      }else if(cnt == 33){
        plotObjectA.feedData(0,0,0,0);
      }else{
        plotObjectA.feedData(valBlack,
                             valRed,
                             valBlue,
                             valGreen);
      }//end else
    }//end for loop
    //Cause the data to be plotted in the default
    // screen location.
    plotObjectA.plotData();
  }//end main
  //-------------------------------------------//
  String title;
  int frameWidth;
  int frameHeight;
  int traceSpacing;//pixels between traces
  int sampSpacing;//pixels between samples
  int ovalWidth;//width of sample marking oval
  int ovalHeight;//height of sample marking oval
  
  int tracesPerPage;
  int samplesPerPage;
  int pageCounter = 0;
  int sampleCounter = 0;
  ArrayList <Page> pageLinks = 
                           new ArrayList<Page>();
  
  //There are two overloaded versions of the
  // constructor for this class.  This
  // overloaded version accepts several incoming
  // parameters allowing the user to control
  // various aspects of the plotting format. A
  // different overloaded version accepts a title
  // string only and sets all of the plotting
  // parameters to default values.
  PlotALot05(String title,//Frame title
             int frameWidth,//in pixels
             int frameHeight,//in pixels
             int traceSpacing,//in pixels
             int sampSpace,//in pixels per sample
             int ovalWidth,//sample marker width
             int ovalHeight)//sample marker hite
  {//constructor
    //Specify sampSpace as pixels per sample.
    // Should never be less than 1.  Convert to
    // pixels between samples for purposes of
    // computation.
    this.title = title;
    this.frameWidth = frameWidth;
    this.frameHeight = frameHeight;
    this.traceSpacing = traceSpacing;
    //Convert to pixels between samples.
    this.sampSpacing = sampSpace - 1;
    this.ovalWidth = ovalWidth;
    this.ovalHeight = ovalHeight;
    //The following object is instantiated solely
    // to provide information about the width and
    // height of the canvas. This information is
    // used to compute a variety of other
    // important values.
    Page tempPage = new Page(title);
    int canvasWidth = tempPage.canvas.getWidth();
    int canvasHeight = 
                     tempPage.canvas.getHeight();
    //Display information about this plotting
    // object.
    System.out.println("nTitle: " + title);
    System.out.println(
          "Frame width: " + tempPage.getWidth());
    System.out.println(
        "Frame height: " + tempPage.getHeight());
    System.out.println(
                   "Page width: " + canvasWidth);
    System.out.println(
                 "Page height: " + canvasHeight);
    System.out.println(
               "Trace spacing: " + traceSpacing);
    System.out.println(
         "Sample spacing: " + (sampSpacing + 1));
    if(sampSpacing < 0){
      System.out.println("Terminating");
      System.exit(0);
    }//end if
    //Get rid of this temporary page.
    tempPage.dispose();
    //Now compute the remaining important values.
    tracesPerPage = 
                 (canvasHeight - traceSpacing/2)/
                                    traceSpacing;
    System.out.println("Traces per page: "
                                + tracesPerPage);
    if((tracesPerPage == 0) || 
                        (tracesPerPage%4 != 0) ){
      System.out.println("Terminating program");
      System.exit(0);
    }//end if
    samplesPerPage = canvasWidth * tracesPerPage/
                             (sampSpacing + 1)/4;
    System.out.println("Samples per page: "
                               + samplesPerPage);
    //Now instantiate the first usable Page
    // object and store its reference in the
    // list.
    pageLinks.add(new Page(title));
  }//end constructor
  //-------------------------------------------//
  
  PlotALot05(String title){
    //Invoke the other overloaded constructor
    // passing default values for all but the
    // title.
    this(title,400,410,50,2,2,2);
  }//end overloaded constructor
  //-------------------------------------------//
  
  //Invoke this method once for each quadruplet
  // of data values to be plotted.
  void feedData(double valBlack,
                double valRed,
                double valBlue,
                double valGreen){
    if((sampleCounter) == samplesPerPage){
      //if the page is full, increment the page
      // counter, create a new empty page, and
      // reset the sample counter.
      pageCounter++;
      sampleCounter = 0;
      pageLinks.add(new Page(title));
    }//end if
    //Store the sample values in the MyCanvas
    // object to be used later to paint the
    // screen.  Then increment the sample
    // counter.  The sample values pass through
    // the page object into the current MyCanvas
    // object.
    pageLinks.get(pageCounter).putData(
                                  valBlack,
                                  valRed,
                                  valBlue,
                                  valGreen,
                                  sampleCounter);
    sampleCounter++;
  }//end feedData
  //-------------------------------------------//
  
  //There are two overloaded versions of the
  // plotData method.  One version allows the
  // user to specify the location on the screen
  // where the stack of plotted pages will
  // appear.  The other version places the stack
  // in the upper left corner of the screen.
  
  //Invoke one of the overloaded versions of
  // this method once when all data has been fed
  // to the plotting object in order to rearrange
  // the order of the pages with page 0 at the
  // top of the stack on the screen.
  
  //For this overloaded version, specify xCoor
  // and yCoor to control the location of the
  // stack on the screen.  Values of 0,0 will
  // place the stack at the upper left corner of
  // the screen.  Also see the other overloaded
  // version, which places the stack at the upper
  // left corner of the screen by default.
  void plotData(int xCoor,int yCoor){
    Page lastPage = 
             pageLinks.get(pageLinks.size() - 1);
    //Delay until last page becomes visible.
    while(!lastPage.isVisible()){
      //Loop until last page becomes visible
    }//end while loop
    
    Page tempPage = null;
    //Make all pages invisible
    for(int cnt = 0;cnt < (pageLinks.size());
                                          cnt++){
      tempPage = pageLinks.get(cnt);
      tempPage.setVisible(false);
    }//end for loop
    
    //Now make all pages visible in reverse order
    // so that page 0 will be on top of the
    // stack on the screen.
    for(int cnt = pageLinks.size() - 1;cnt >= 0;
                                          cnt--){
      tempPage = pageLinks.get(cnt);
      tempPage.setLocation(xCoor,yCoor);
      tempPage.setVisible(true);
    }//end for loop
  }//end plotData(int xCoor,int yCoor)
  //-------------------------------------------//
  
  //This overloaded version of the method causes
  // the stack to be located in the upper left
  // corner of the screen by default
  void plotData(){
    plotData(0,0);//invoke overloaded version
  }//end plotData()
  //-------------------------------------------//
  //Inner class.  A PlotALot05 object may
  // have as many Page objects as are required
  // to plot all of the data values.  The 
  // reference to each Page object is stored
  // in an ArrayList object belonging to the
  // PlotALot05 object.
  class Page extends Frame{
    MyCanvas canvas;
    int sampleCounter;
    Page(String title){//constructor
      canvas = new MyCanvas();
      add(canvas);
      setSize(frameWidth,frameHeight);    
      setTitle(title + " Page: " + pageCounter);
      setVisible(true);
      
      //---------------------------------------//
      //Anonymous inner class to terminate the
      // program when the user clicks the close
      // button on the Frame.
      addWindowListener(
        new WindowAdapter(){
          public void windowClosing(
                                  WindowEvent e){
            System.exit(0);//terminate program
          }//end windowClosing()
        }//end WindowAdapter
      );//end addWindowListener
      //---------------------------------------//
    }//end constructor
    //=========================================//
  
    //This method receives a quadruplet of sample
    // values of type double and stores each of
    // them in a separate array object belonging
    // to the MyCanvas object.
    void putData(double valBlack,
                 double valRed,
                 double valBlue,
                 double valGreen,
                 int sampleCounter){
      canvas.blackData[sampleCounter] = valBlack;
      canvas.redData[sampleCounter] = valRed;
      canvas.blueData[sampleCounter] = valBlue;
      canvas.greenData[sampleCounter] = valGreen;
      //Save the sample counter in an instance
      // variable to make it available to the
      // overridden paint method. This value is
      // needed by the paint method so it will
      // know how many samples to plot on the
      // final page which probably won't be full.
      this.sampleCounter = sampleCounter;
    }//end putData
    
    //=========================================//
    //Inner class
    class MyCanvas extends Canvas{
      double [] blackData = 
                      new double[samplesPerPage];
      double [] redData = 
                      new double[samplesPerPage];
      double [] blueData = 
                      new double[samplesPerPage];
      double [] greenData = 
                      new double[samplesPerPage];
                      
      //Override the paint method
      public void paint(Graphics g){
        //Draw horizontal axes, one for each
        // trace.
        for(int cnt = 0;cnt < tracesPerPage;
                                          cnt++){
          g.drawLine(0,
                     (cnt+1)*traceSpacing,
                     this.getWidth(),
                     (cnt+1)*traceSpacing);
        }//end for loop
        
        //Plot the points if there are any to be
        // plotted.
        if(sampleCounter > 0){
          for(int cnt = 0;cnt <= sampleCounter;
                                          cnt++){
                                            
            //Begin by plotting the values from
            // the blackData array object.
            g.setColor(Color.BLACK);
            
            //Compute a vertical offset to locate
            // the black data on every third axis
            // on the page.
            int yOffset = 
               ((1 + cnt*(sampSpacing + 1)/
                this.getWidth())*4*traceSpacing)
                                - 3*traceSpacing;
            //Draw an oval centered on the sample
            // value to mark the sample in the
            // plot. It is best if the dimensions
            // of the oval are evenly divisable
            // by 2 for  centering purposes.
            //Reverse the sign of the sample
            // value to cause positive sample
            // values to be plotted above the
            // axis.
            g.drawOval(cnt*(sampSpacing + 1)%
                   this.getWidth() - ovalWidth/2,
              yOffset - (int)blackData[cnt] 
                                  - ovalHeight/2,
              ovalWidth,
              ovalHeight);
            
            //Connect the sample values with
            // straight lines.  Do not draw a
            // line connecting the last sample in
            // one trace to the first sample in
            // the next trace.
            if(cnt*(sampSpacing + 1)%
                               this.getWidth() >=
                                sampSpacing + 1){
              g.drawLine(
                (cnt - 1)*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)blackData[cnt-1],
                cnt*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)blackData[cnt]);
            }//end if
            //Now plot the data stored in the
            // redData array object.
            g.setColor(Color.RED);
            //Compute a vertical offset to locate
            // the red data on every third axis
            // on the page.
            yOffset = (1 + cnt*(sampSpacing + 1)/
                  this.getWidth())*4*traceSpacing
                                - 2*traceSpacing;
            
            //Draw the ovals as described above.
            g.drawOval(cnt*(sampSpacing + 1)%
                   this.getWidth() - ovalWidth/2,
              yOffset - (int)redData[cnt] 
                                  - ovalHeight/2,
              ovalWidth,
              ovalHeight);
            
            //Connect the sample values with
            // straight lines as described above.
            if(cnt*(sampSpacing + 1)%
                               this.getWidth() >=
                                sampSpacing + 1){
              g.drawLine(
                (cnt - 1)*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)redData[cnt-1],
                cnt*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)redData[cnt]);
                
            }//end if
          
            //Now plot the data stored in the
            // blueData array object.
            g.setColor(Color.BLUE);
            //Compute a vertical offset to locate
            // the blue data on every third axis
            // on the page.
            yOffset = (1 + cnt*(sampSpacing + 1)/
                 this.getWidth())*4*traceSpacing 
                                   -traceSpacing;
            
            //Draw the ovals as described above.
            g.drawOval(cnt*(sampSpacing + 1)%
                   this.getWidth() - ovalWidth/2,
              yOffset - (int)blueData[cnt] 
                                  - ovalHeight/2,
              ovalWidth,
              ovalHeight);
            
            //Connect the sample values with
            // straight lines as described above.
            if(cnt*(sampSpacing + 1)%
                               this.getWidth() >=
                                sampSpacing + 1){
              g.drawLine(
                (cnt - 1)*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)blueData[cnt-1],
                cnt*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)blueData[cnt]);
            }//end if
            
            
            //Now plot the data stored in the
            // greenData array object.
            g.setColor(Color.GREEN);
            //Compute a vertical offset to locate
            // the green data on every third axis
            // on the page.
            yOffset = (1 + cnt*(sampSpacing + 1)/
                 this.getWidth())*4*traceSpacing;
            
            //Draw the ovals as described above.
            g.drawOval(cnt*(sampSpacing + 1)%
                   this.getWidth() - ovalWidth/2,
              yOffset - (int)greenData[cnt] 
                                  - ovalHeight/2,
              ovalWidth,
              ovalHeight);
            
            //Connect the sample values with
            // straight lines as described above.
            if(cnt*(sampSpacing + 1)%
                               this.getWidth() >=
                                sampSpacing + 1){
              g.drawLine(
                (cnt - 1)*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)greenData[cnt-1],
                cnt*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)greenData[cnt]);
            }//end if
          }//end for loop
        }//end if for sampleCounter > 0
      }//end overridden paint method
    }//end inner class MyCanvas
  }//end inner class Page
}//end class PlotALot05
//=============================================//

Listing 33

 


Copyright 2005, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP).  His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments.  (TI is still a world leader in DSP.)  In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel