Home : Course Map : Chapter 8 : Java : Tech :
Refactoring Class Designs
JavaTech
Course Map
Chapter 8

Introduction
Threads Overview
  Demo 1   Demo 2
Stopping Threads
Multi-Processing
Thread Tasks
Animations
 
 Demo 3   Demo 4  
  Demo 5

Non-interacting
  Demo 6

Task Splitting
  Demo 7

Exclusivity
  Demo 8

Communicating
  Demo 9

Priority/Scheduling
More Thread

Exercises

    Supplements
Java2D Animation
  Demo 1 
Processor View
More Concurrency
Cloning
  Demo 2  Demo 3 

     About JavaTech
     Codes List
     Exercises
     Feedback
     References
     Resources
     Tips
     Topic Index
     Course Guide
     What's New

Here and in the coming chapters we would like to add more options to our histograms such as an option to store error values on each bin. We could continue to create a new subclass for every new feature that we discuss. This certainly offers step-by-step pedalogical clarity. However, this would not provide for a sensible class heirarchy.

In section Chapter 7: Tech and this chapter we have already created three subclasses of our Histogram base class:

  • HistogramStat - added calculation of the moments of the data.
  • HistogramAdapt - added the option of saving each data value in an array and dynamically recalculating the range of the histogram to include all data points.
  • HistogramMedian - calculated the median value of the histogram bins or directly from the data if saved.

Adding yet more subclasses will make the hierarchy of histogram classes increasingly complex and awkward. Perhaps there are features in the subclasses that logically belong in the base class or one of the superclasses. This situation often occurs in object oriented software development. As you think up new features and capabilities and continually create new subclasses, the original class design goals can become lost in a mess.

The alternative is to reorganize, or refactor as it is now called, the classes to make them more conceptually clear, modular, and understandable. (In Chapter 6: Tech : Hist Demo we refactored two early histogram classes into the current Histogram class.) Ideally, the refactoring of class structures will avoid, or at least minimize, external changes so that constructors and methods used by other classes will not "break" due to these alterations and need re-coding as well.

(Refactoring is a big aspect of the so-called Extreme Programming approach where it means to reorganize a class to improve the design while keeping its external behavior unchanged.)

Class design is still as much art as science. Knowing from the beginning of a software project how to design the classes to allow for straight-forward and elegant expansion of capabilities is quite a tough challenge. Techniques such as design patterns (see references) improve the situation but in the course of almost any large programming project, many refactorings of the classes will occur.

Revisions

One approach is simply to pack more and more methods and fields into a bigger and bigger base class. This, however, makes for unwieldy code that reduces modularity and conceptual clarity, defying the whole purpose of OOP. (A practical drawback also comes when instances of the class carry lots of fields that take up memory space but are not used.)

On the other hand, a new subclass for every new feature is not advisable either. Subclasses should offer unique capabilities for the particular situations that demand those capabilities. The base class should keep just those fields and methods needed in common by all the subclasses.

We could collapse all of the subclasses into one subclass but, in the code discussed in this chapter, the adaptive range subclass with its internal data array seems fundamentally different from the statistics subclass. So we will create one new base class and one new subclass, where the latter differs from the former in its adaptive ranging.

In normal practice we would modify, expand, and update Histogram and its subclasses and try to do this internally to avoid upsetting the programs that use these classes. And we would seek to keep the subclasses distinct and the number of them to a minimum. However, in this course we want to keep the class names unique to each particular version for pedalogical clarity. So rather than modify an existing class, we will create a new class but append R#, as in HistogramStatR1 for HistogramStat, Revision 1.

In our refactored Histogram heirarchy we will keep Histogram unchanged as our base class since it is compact and can provide basic services. We will create a new version of HistogramStat called HistogramStatR1. It is similar to the earlier version but now provides a median calculated from the histogram bins. Methods of particular interest include:

  • double [] getStats() - this method overrides the one in Histogram and provides an array of statistics , including median, calculated from the contents of the bins. Underflows and overflows are ignored.
  • double [] getDataStats() - returns an array of statistics, including median, calculated from the moments obtained during data entry. It uses all data values, including those outside the range of the histogram.
  • getMedian() - estimates the median from the histogram bin contents.
  • New methods to create and access errror values on the bins:
    • double [] getBinErrors()
    • double getBinError(int bin)
    • void makeBinErrors()
    • boolean packErrors(double [] errors)

Thus the class gives us the option of the statistics from the bin distribution or from the moments calculated during data entry. We also added the new capability of errors on the bins. This will be convenient later for data fitting.

HistogramAdaptR1 combines HistogramAdapt and HistogramMedian. The key feature as before comes from the option of saving all of the original data values in an array. The new class includes many of the same methods as before. Methods of particular interest include:

  • double [] getDataStats() - same statistics as before except that now median is now calculated from the data array.
  • getMedian() - calculates the median from the data array.
  • void setAdaptEnabled(boolean flag) - can turn on or off the saving of data into an array. If turned off, the getStats() and getMedian() methods revert to the HistogramStatR1 overriden methods.

In the example below we use these two classes to create a similar applet as that in the Median section.


HistR1Applet.java
- displays an instance of HistogramAdaptR1 on a HistPanel. Generates data the same as in AdaptHistFillApplet for the Adaptable Histogram section.

+ New classes:
HistogramAdaptR1.java - This subclass of HistogramStatR1 provides histograms that offer the option to adapt the range to keep all points within the bin limits plus statistical information on the distribution. The class uses a data array to hold each individual entry. This class derives from a combination of HistogramAdapt and HistogramMedian.

HistogramStatR1.java - This class derives from a refactoring of Histogram, HistogramStats, HistogramAdapt, and HistogramMedian. It provides all of the methods and properties of those classes.

+ Previous classes:
Chapter 8:Tech:
MakeData.java, Updateable.java
Chapter 7:Tech:
HistogramStat.java,
Chapter 6:Tech: Histogram.java, HistPanel.java
Chapter 6:Tech: PlotPanel.java, PlotFormat.java

/**
  *  This class provides provides histograms that offer the option
  *  to adapt the range to keep all points within bin limits. This
  *  requires a data array to hold each individual entry.
  *  Requires an expandable data storage array to hold each data point.
  *  
  *  This class derives from a combination of HistogramAdapt and
  *  HistogramMedian.
**/
public class HistogramAdaptR1 extends HistogramStatR1
{
  // flag for adapting the limits to the data
  boolean fAdapt = true;

  // flag to indicate a value outside limits
  boolean fNeedToRebin = false;

  // data array.
  double [] fData ;

  // index pointing where next datum goes
  int fDataIndex = 0;

  /**
    * Constructor
    *
    * @param estmated number of data points.
    *   Negative to turn off adaptable binning.
    * @param numbins - number of bins for the histogram.
    * @param lo - lowest value of bin.
    * @param hi - highest value of bin.
    * @param numData - size of data array. Must be equal to or greater
    *        than the number of data points expected to be entered
    *        into the histogram.
   **/
  public HistogramAdaptR1 (int num_bins, double lo, double hi,
                        int num_data) {
    super (num_bins,lo,hi);
    if (num_data <= 0) fAdapt = false;
    else setData (num_data);
  } // ctor

  /**
    * Constructor with title and x axis label.
    *
    * @param title for histogram
    * @param estmated number of data points.
    *   Negative to turn off adaptable binning.
    * @param label for x axis of histogram.
    * @param inital number of bins for the histogram.
    * @param lowest value of bin.
    * @param highest value of bin.
    * @param size of data array. Must be equal to or greater
    *        than the number of data points expected to be entered
    *        into the histogram.
   **/
  public HistogramAdaptR1 (String title, String xLabel, int num_bins,
                       double lo, double hi, int num_data) {
    super (title, xLabel, num_bins,lo,hi);
    if (num_data <= 0 ) fAdapt = false;
    else setData (num_data);
  } // ctor

  /**
    *  Create a data array sufficient to include the data.
    *  @param number of data elements needed. If data already
    *  present in the current array, then the array will be big
    *  enough to hold the current data plus numData.
   **/
  void setData (int num_data) {
    // If previous data already present then
    // check if room for the new data. If not
    // then create a bigger data array.
    if (fDataIndex > 0) {
        int data_needed = fDataIndex + num_data + 1;
        if (data_needed > fData.length) {
            // Make a new data array
            double [] tmp = new double[data_needed];
            // Copy the old data into it
            System.arraycopy (fData,0,tmp,0,fData.length);
            // Move reference to new array
            fData = tmp;
        }
    }
    else // Create an initial or bigger data array if needed.
    if (fData == null || num_data > fData.length)
        fData = new double[num_data];
  } // setData

  /**
    * Clear the histogram and also reset the data array index
    * to the beginning.
   **/
  public void reset () {
    super.clear (); // Clear histogram arrys
    fDataIndex = 0; // reset data array pointer to beginning
  }

  /**
    *  Provide access to the data array. Returns
    *  null if the range adaptation turned off.
   **/
  double [] getData () {
    return fData;
  }

/**
   * Can enable or disable the range adaption.
   *
   * @param boolean to turn on or off the extending of the limits
   *   when data values outside current limits occur.
  **/
  void setAdaptEnabled (boolean flag) {
    fAdapt = flag;
    if (!fAdapt) fData = null;
  }

  /**  Return index to data array element pointer. **/
  int getDataIndex () {
    return fDataIndex;
  }

  /**  Find if rebin request turned on. **/
  boolean getRebinFlag () {
    return fNeedToRebin;
  }


/**
   * Add an entry to the histogram. Check if value outside current
   * limits of the histogram. If it is and the adaptation flag turned
   * on, then set rebin flag.
   * Synchronize so that this method and rebin () method do not interfere.
   *
   * @param non-zero length array of int values.
  **/
  public synchronized void add (double x) {
    if (fAdapt) {
        // Add new data point to array
        if (fDataIndex < fData.length) {
            fData[fDataIndex++] = x;
            // If data point outside range, set rebin flag.
            if ( x < fLo || x > fHi) fNeedToRebin = true;
        } else
        // Could throw an exception, open a warning
        // dialog, or use setData () to create a bigger data array.
        // However, we just do a simple console print.
        System.out.println ("Data overflow");
    }
    super.add (x);
  } // add


  /**
    *  Rebin the histogram using the data array.
    *  Synchronize to avoid interference with new data
    *  entering during this method.
   **/
  public synchronized void rebin () {
    if (fDataIndex <= 1) return;

    // Find new limits from the out of range datum,
    for (int i=0; i < fDataIndex; i++) {
        if (fData[i] < fLo) fLo = fData[i];
        if (fData[i] > fHi) fHi = fData[i];
    }

    // Set new limits
    fLo = Math.floor (fLo);
    fHi = Math.ceil (fHi);
    fRange = fHi - fLo;

    // Clear the histogram entries
    clear ();

    // Refill the histogram according to the new range.
    for (int i=0; i < fDataIndex; i++) {
        super.add (fData[i]);
    }

    fNeedToRebin = false;
  } // rebin


  /**
    *  If adaptation turned on, get the statistical measures of
    *  the distribution directly from the data.  (Uses a two pass
    *  approach reduced numerical errors compared to using the
    *  moments.)
    *  Otherwise, use the overriden method in HistogramR1.
    *  @return  values in double array correspond to
    *    1 - mean
    *    2 - std. dev
    *    3 - error on the mean
    *    4 - skewness
    *    5 - kurtosis
    *    6 - median
    *
    *  The median is calculated from the data.
    *
   **/
  public double [] getStats () {
    if (!fAdapt) {
        return super.getStats ();
    }

    if (fDataIndex == 0) {
       fStats[I_MEAN]       =  0.0;
       fStats[I_STD_DEV]    =  0.0;
       fStats[I_MEAN_ERROR] =  0.0;
       fStats[I_SKEWNESS]   =  0.0;
       fStats[I_KURTOSIS]   =  0.0;
       fStats[I_MEDIAN]     =  0.0;
       return fStats;
    }

    double sum = 0;

    // First find the mean.
    for (int i=0; i < fDataIndex; i++) {
        sum += fData[i];
    }

    fStats[I_MEAN] = sum/fDataIndex;

    // Now calculate the moments.

    double sumDev2 = 0.0;
    double sumSkew = 0.0;
    double sumKurt = 0.0;

    // Now make a second pass through the data.
    for (int i=0; i < fDataIndex; i++) {
        double dev = fData[i] - fStats[I_MEAN];
        double dev2 = dev * dev;

        sumDev2 += dev2;
        sumSkew += dev*dev2;
        sumKurt += dev2*dev2;
    }

    double variance = sumDev2;
    if (fDataIndex > 1)  variance /= (fDataIndex-1);

    fStats[I_STD_DEV] = Math.sqrt (variance);

    // Error on mean = s / sqrt (n)
    fStats[I_MEAN_ERROR] = fStats[I_STD_DEV]/Math.sqrt (fDataIndex);

    if (variance != 0) {
        double sigmaCube = fStats[I_STD_DEV] * variance;

        fStats[I_SKEWNESS] = sumSkew/ (fDataIndex*sigmaCube);
        fStats[I_KURTOSIS] = sumKurt/ (fDataIndex*variance*variance) - 3.0;
    } else {
        fStats[I_SKEWNESS]   =  0.0;
        fStats[I_KURTOSIS]   =  0.0;
    }
    fStats[I_MEDIAN] = getMedian ();
    return fStats;

  } // getStats

  /**
    *  This method overrrides the  method in HistogramR1 so as to
    *  calculate the median from the data array. If adaptation
    *  turned off, the overriden method used.
   **/
  public double getMedian () {

    if (fAdapt) { // then use data to get median
        double median = 0.0;

        if (fDataIndex > 1) {
           java.util.Arrays.sort (fData, 0, fDataIndex);
           int mid = fDataIndex/2;
           // fDataIndex points to element above last entry.
           if ((fDataIndex % 2) == 0) {
              // even number of entries
              return  (fData[mid] + fData[mid-1])/2.0;
           } else  {// odd number of entries
              return fData[mid];
           }
        } else
           return fData[0];
    } else // Otherwise, use the bin contents.
       return super.getMedian ();

  } // getMedian

} // class HistogramAdaptR1
/**
  * This class comes from a refactoring of Histogram,
  * HistogramStats, HistogramAdapt, and HistogramMedian.
  *
 **/
public class HistogramStatR1 extends Histogram
{
  protected double [] fBinErrors;
  protected boolean fDoDataStats = true;

  // These constants indicate for each element of the
  // array returned from the getStats () method the statistical
  // measure to which it corresponds.
  public final static int I_MEAN       = 0;
  public final static int I_STD_DEV    = 1;
  public final static int I_MEAN_ERROR = 2;
  public final static int I_SKEWNESS   = 3;
  public final static int I_KURTOSIS   = 4;
  public final static int I_MEDIAN     = 5;
  public final static int I_NUMSTATS   = 6;

  protected double [] fStats = new double[I_NUMSTATS];

  protected double [] fMoments= new double[5];

//--- Constructors -----------------------------------------
  /**
   * The constructor will create an array of a given
   * number of bins. The range of the histogram given
   * by the upper and lower limit values.
   *
   * @param numBins number of bins for the histogram.
   * @param lo lowest value of bin.
   * @param hi highest value of bin.
  **/
  public HistogramStatR1 (int num_bins, double lo, double hi) {
    super (num_bins,lo,hi);
  } // ctor

/**
   * Constructor with title and x axis label.
   *
   * @param title histogram title
   * @param xLabel label for x axis of histogram.
   * @param numBins number of bins for the histogram.
   * @param lo lowest value of bin.
   * @param hi highest value of bin.
  **/
  public HistogramStatR1 (String title, String xLabel, int num_bins,
                         double lo, double hi) {
    super (title, xLabel, num_bins,lo,hi);
  } // ctor


  /** Get the number of entries in the smallest bin. **/
  public int getMin () {
    int min = getMax ();

    for (int i=0; i < fNumBins;i++)
        if (min > fBins[i]) min = fBins[i];

    return min;
  }


//--- Bin  modification ------------------------------------

  /**
    * Add an entry to a bin. Include if value is in the range
    *    lo <= x < hi
   **/
  public void add (double x) {
    if (x >= fHi)
        fOverflows++;
    else  if ( x < fLo)
        fUnderflows++;
    else  {
        double val = x - fLo;

        // Casting to int will round off to lower
        // integer value.
        int bin =  (int) (fNumBins *  (val/fRange) );

        // Increment the corresponding bin.
        fBins[bin]++;
    }

    // Calculate raw moments from each data value.
    fMoments[0] += 1;
    fMoments[1] += x;
    double x2 = x * x;
    fMoments[2] += x2;
    fMoments[3] += x2 * x;
    fMoments[4] += x2 * x2;
  }

  /** Clear the histogram bins and the over and under flows. **/
  public void clear () {
    // Clear the bins, over and under flosts
    super.clear ();

    // Clear the bin errors if they exist.
    int i;
    for (i=0; i < 5; i++) fMoments[i] = 0.0;
    if (fBinErrors != null)
       for (i=0; i < fBinErrors.length; i++) fBinErrors[i] = 0.0;

  }


//--- Statistics methods -----------------------------------

  /**
    * Get the average and standard deviation from the
    * distribution of the bins rather than from the
    * individual data inputs as in getStats () method.
    * Overflows and underflows ignored.


    * If histogram empty, all values set to zero.
   **/
  public double [] getStats () {

    int total = getTotal ();

    if (total == 0) {
       fStats[I_MEAN]       =  0.0;
       fStats[I_STD_DEV]    =  0.0;
       fStats[I_MEAN_ERROR] =  0.0;
       fStats[I_SKEWNESS]   =  0.0;
       fStats[I_KURTOSIS]   =  0.0;
       fStats[I_MEDIAN]     =  0.0;
       return fStats;
    }

    double wt_total = 0;
    double bin_width = fRange/fNumBins;

    double bin_mid = 0.5 * bin_width + fLo;

    // First get the average using the
    // middle of the bin as the position
    // and weighted by the bin contents.
    for (int i=0; i < fNumBins;i++) {
        total += fBins[i];
        bin_mid += bin_width;
        wt_total  += fBins[i] * bin_mid;
    }

    fStats[I_MEAN] = wt_total/total;

    // Now calculate the moments.
    bin_mid = 0.5 * bin_width + fLo;

    double sum_dev2 = 0.0;
    double sum_skew = 0.0;
    double sum_kurt = 0.0;

    for (int i=0; i < fNumBins; i++) {
        double dev = bin_mid - fStats[I_MEAN];
        double dev2 = dev * dev;

        sum_dev2 += dev2;
        sum_skew += dev  * dev2;
        sum_kurt += dev2 * dev2;
    }

    double variance = sum_dev2;
    if (total > 1) variance /= (total-1);

    fStats[I_STD_DEV] = Math.sqrt (variance);
    // Error on mean = s / sqrt (n)
    fStats[I_MEAN_ERROR] = fStats[I_STD_DEV]/Math.sqrt (total);

    if (variance != 0) {
        double sigmaCube = fStats[I_STD_DEV]  * variance;
        fStats[I_SKEWNESS] = sum_skew/ (total * sigmaCube);
        fStats[I_KURTOSIS] = sum_kurt/ (total * variance*variance) - 3.0;
    } else {
        fStats[I_SKEWNESS]   =  0.0;
        fStats[I_KURTOSIS]   =  0.0;
    }

    fStats[I_MEDIAN] = getMedian ();

    return fStats;

  } // getStats

  /**
    *  Get the statistical measures of the distribution calculated
    *  from the individual entry values  (except the median).
    *  @return  values in double array correspond to
    *    1 - mean
    *    2 - std. dev
    *    3 - error on the mean
    *    4 - skewness
    *    5 - kurtosis
    *    6 - median
    *
    *  The median is calculated from the bin distribution.
    *
   **/
  public double [] getDataStats () {

    double n = fMoments[0];

    // Average value = 1/n * sum[x]
    double mean = fMoments[1]/n;

    // Use running mean.
    fStats[I_MEAN] = mean;
    double mean_sq = mean*mean;

    // Check on minimum number of entries.
    if ( n < 2) return fStats;

    // Convert power sums to central moments
    double m2 = fMoments[2]/n;
    double cm2 = m2 - mean * mean;
    double m3 = fMoments[3]/n;
    double cm3 = 2.0 * mean * mean_sq - 3.0 * mean * m2 + m3;
    double m4 = fMoments[4]/n;
    double cm4 = -3.0 * mean_sq * mean_sq + 6.0 * mean_sq * m2
                     -4.0 * mean * m3 + m4;

    // variance = N/ (N-1) m2
    double variance = cm2 * (n/ (n-1.0));

    // Std. Deviation s = sqrt (variance)
    fStats[I_STD_DEV] = Math.sqrt (variance);

    // Error on mean = s / sqrt (N)
    fStats[I_MEAN_ERROR] = fStats[1]/Math.sqrt (n);

    // Skewness = n^2/ (n-1) (n-2) *  cm3/s^3
    fStats[I_SKEWNESS] =  ( n/ ( (n-1) * (n-2)) ) * n * cm3 /
                                     (variance*fStats[I_STD_DEV]);

    // Kurtosis = n (n+1)/ (n-1) (n-2) (n-3) * cm4/s^4 - 3 (n-1)^2 / (n-2) (n-3)
    double factor1 =  ( n * (n+1.0))/ (  (n-1.0) * (n-2.0) * (n-3.0) );
    double factor2 =  ( 3.0 * (n-1.0) * (n-1.0) )/ ( (n-2.0) * (n-3.0));
    fStats[I_KURTOSIS] = factor1 * cm4 * n/ (variance * variance) - factor2;

    fStats[I_MEDIAN]  = getMedian ();

    return fStats;

  } // getDataStats

  /**
    *  The median is calculated from the bin distribution.
    *  Overflows and underflows ignored.
   **/
  public double getMedian () {

    int half_total_entries = getTotal ()/2;
    int sum_bin_entries = 0;
    int sum = 0;

    double bin_width = fRange/fNumBins;
    double median = 0.0;

    // Sum bins up to half total of entries
    for ( int i=0; i < fNumBins; i++) {
      sum = sum_bin_entries + fBins[i];

      // Check if bin crosses halfTotal point
      if (sum >= half_total_entries ) {
          // Scale linearly across the bin
          int dif = half_total_entries - sum_bin_entries;
          double frac = 0.0;
          if (fBins[i] > 0) {
              frac =  ((double)dif)/(double)fBins[i];
          }
          median = (i + frac) * bin_width + fLo;
          // Finished
          break;
      }
      // Not reached median yet.
      sum_bin_entries = sum;
    }
    return median;
  } // getMedian

//--- Bin error methods -----------------------------------

  /**
    * Provide an array of error values for all bins.
    * @return array of double values.
   **/
  public double [] getBinErrors () {
    return fBinErrors;
  }

  /**
    * Return the error for a particular bin.
    * @param from 0 to highest bin value
   **/
  public double getBinError (int bin) {
    if (bin>= 0 && bin < fBins.length)
      return fBinErrors[bin];
    else
      return -1.0;
  } // getBinError

  /**
    * Calculate the error on each bin according to the
    * sqrt (bin contents) from std. dev. of Poisson distribution.
   **/
  public void makeBinErrors () {
    if (fBinErrors == null ||
        fBinErrors.length != fBins.length)
        fBinErrors = new double [fBins.length];

    for ( int i = 0; i < fNumBins; i++)
    {
      fBinErrors[i] = Math.sqrt (fBins[i]);
    }
  } // makeBinErrors

  /**
   * Pack the errors on each bin.
   * @param false if array size doesn't
   *        match number of bins.
   */
  public boolean packErrors (double [] errors) {
    if (errors.length != fNumBins) return false;
    if (fBinErrors == null || fBinErrors.length != fNumBins)  {
        fBinErrors = new double[errors.length];
    }

    for (int i = 0; i < fNumBins; i++)  {
        fBinErrors[i] = errors[i];
    }
    return true;
  } //packErrors

} // class HistogramStatR1
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/**
* The applet uses the HistPanel to display contents of
* an instance of HistogramAdaptR1.
*
* The java.util.Timer and java.util.TimerTask are used
* to update the display of the histogram during the filling
* of the histogram.
*
* Includes "Go" button to initiate the filling of the histogram.
* To simulate data taking, a combination of a Gaussian and random
* background values are generated.
*
* The number of values taken from
* entry in a JTextField. "Clear"  button clears the histogram.
* In standalone mode, the Exit button closes the program.
*
*/
public class HistR1Applet extends JApplet
             implements ActionListener, Updateable
{
  // Use the HistPanel JPanel subclass here
  PlotPanel fOutputPanel = null;

  HistogramAdaptR1 fHistogram = null;
  int fNumDataPoints = 100;

  boolean fMakingHist = false;
  boolean fUpdateDisplay = false;
  MakeData fMakeData;

  // Use the java.util Timer and TimerTask combo
  // for timing events.
  java.util.Timer fTimer;

  // A text field for input strings
  JTextField fTextField = null;

  // Flag for whether the applet is in a browser
  // or running via the main () below.
  boolean fInBrowser=true;

  //Buttons
  JButton fGoButton;
  JButton fStatsButton;
  JButton fClearButton;
  JButton fExitButton;

  /**
    * Create a User Interface with a histogram and a Go button
    * to initiate processing and a Clear button to clear the .
    * histogram. In application mode, the Exit button stops the
    * program. Add a stats button to open a frame window to show
    * statistical measures.
   **/
  public void init () {

    Container content_pane = getContentPane ();

    JPanel panel = new JPanel (new BorderLayout ());

    // Use a textfield for an input parameter.
    fTextField =
      new JTextField (Integer.toString (fNumDataPoints), 10);

    // If return hit after entering text, the
    // actionPerformed will be invoked.
    fTextField.addActionListener (this);

    fGoButton = new JButton ("Go");
    fGoButton.addActionListener (this);

    fStatsButton = new JButton ("Stats");
    fStatsButton.addActionListener (this);

    fClearButton = new JButton ("Clear");
    fClearButton.addActionListener (this);

    fExitButton = new JButton ("Exit");
    fExitButton.addActionListener (this);

    JPanel control_panel = new JPanel ();

    control_panel.add (fTextField);
    control_panel.add (fGoButton);
    control_panel.add (fStatsButton);
    control_panel.add (fClearButton);
    control_panel.add (fExitButton);

    // Create a histogram and start filling it.
    makeHist ();
    fGoButton.setText ("Stop");
    fClearButton.setEnabled (false);
    fStatsButton.setEnabled (false);
    if (fInBrowser) fExitButton.setEnabled (false);

    // JPanel subclass here.
    fOutputPanel = new HistPanel (fHistogram);

    panel.add (fOutputPanel,"Center");
    panel.add (control_panel,"South");

    // Add text area with scrolling to the contentPane.
    content_pane.add (panel);
  } // init

  public void actionPerformed (ActionEvent e) {
    Object source = e.getSource ();
    if (source == fGoButton || source == fTextField) {
        String strNumDataPoints = fTextField.getText ();
        try {
          fNumDataPoints = Integer.parseInt (strNumDataPoints);
        }
        catch (NumberFormatException ex) {
          // Could open an error dialog here but just
          // display a message on the browser status line.
          showStatus ("Bad input value");
          return;
        }
        if (!fMakingHist) {
           makeHist ();
           fGoButton.setText ("Stop");
           fStatsButton.setEnabled (false);
           fClearButton.setEnabled (false);
        } else {
           // Stop button has been pushed
           fGoButton.setText ("Go");
           fStatsButton.setEnabled (true);
           fClearButton.setEnabled (true);
           fMakingHist = false;
        }
    } else if (source == fStatsButton) {
        displayStats ();
    } else if ( source == fClearButton) {
        // Use reset instead of clear so as to
        // set data array pointer to zero.
        fHistogram.reset ();
        repaint ();
    } else if (!fInBrowser)
        System.exit (0);

  } // actionPerformed.

  /** Create a frame to display the distribution statistics. **/
  void displayStats () {
    JFrame frame =
        new JFrame ("Histogram Distributions Statistics");

    // Create a listener to close the frame
    WindowListener listener =
      new WindowAdapter ()
      {
        public void windowClosing (WindowEvent e)
        {
          JFrame f =  (JFrame) (e.getSource ());
          f.dispose ();
        }
      };
    frame.addWindowListener (listener);

    JTextArea area = new JTextArea ();

    double [] stats = fHistogram.getStats ();
    if (stats != null)  {
      area.append ("Number entries = "+fHistogram.getTotal ()+"\n");

      String stat = PlotFormat.getFormatted (
                   stats[HistogramStatR1.I_MEDIAN],
                     1000.0,0.001,3);
      area.append ("Median value = "+ stat +" "+"\n");

      stat = PlotFormat.getFormatted (
                   stats[HistogramStatR1.I_MEAN],
                     1000.0,0.001,3);
      area.append ("Mean value = "+ stat +" ");

      stat = PlotFormat.getFormatted (
                   stats[HistogramStatR1.I_MEAN_ERROR],
                     1000.0,0.001,3);
      area.append (" +/- "+stat+"\n");

      stat = PlotFormat.getFormatted (
                   stats[HistogramStatR1.I_STD_DEV],
                     1000.0,0.001,3);
      area.append ("Std. Dev. = "+stat+"\n");

      stat = PlotFormat.getFormatted (
                   stats[HistogramStatR1.I_SKEWNESS],
                     1000.0,0.001,3);
      area.append ("Skewness = "+stat+"\n");

      stat = PlotFormat.getFormatted (
                   stats[HistogramStatR1.I_KURTOSIS],
                     1000.0,0.001,3);
      area.append ("Kurtosis = "+stat+"\n");
    } else {
       area.setText ("No statistical information available");
    }

    frame.getContentPane ().add (area);
    frame.setSize (200,200);
    frame.setVisible (true);

  } // displayStats

  /**
    *  Create the histogram, create the MakeData instance and
    *  spin it off in a thread. Set up a java.util.Timer to
    *  run the PaintHistTask periodically.
   **/
  void makeHist () {
    if ( fMakingHist) return; // only fill one hist at a time.
        fMakingHist = true;

    // Create an instance of the histogram class.
    // Make it wide enough enough to include the data.
    if (fHistogram == null)
        fHistogram = new HistogramAdaptR1 ("Gaussian + Random Background",
                                "Data",
                                20,-2.0,2.0,
                                fNumDataPoints);
    else
        fHistogram.setData (fNumDataPoints);

    // Create the runnable object to fill the histogram
    // Center signal at 3.0 and create background between
    // -10 and 10. The fraction of the data due to the
    // Gaussian will be 0.60. The maximum delay between
    // data poins will be 500msecs.
    fMakeData =
      new MakeData (this, fHistogram, fNumDataPoints,
                   3.0, 0.60, -10.0, 10.0, 500);
    Thread dataThread = new Thread (fMakeData);

    // Before starting the filling, create the timer task
    // that will cause the histogram display to update
    // during the filling.
    // Create a timer. TimerTask created in MakeHist ()
    fTimer = new java.util.Timer ();

    // Start the timer after 100ms and then repeat calls
    // to run in PaintHistTask object every 250ms.
    fTimer.schedule (new PaintHistTask (), 100, 250);

    // Now start the data filling.
    dataThread.start ();

  } // makeHist

  /**
    * Use the inner class technique to define the
    * TimerTask subclass for signalling that the display
    * should be updated.
   **/
  class PaintHistTask extends java.util.TimerTask
  {
    public void run () {
       fUpdateDisplay = true;
    }
  } // class PaintHistTask

  /**
    *  Repaint the histogram display. If called by
    *  the MakeData object, it will turn off the
    *  update display flag and return the fMakingHist flag
    *  to indicate if updating should continue.
   **/
  public boolean update (Object obj) {

    // Don't update the display until the timer
    // turns on the fUpdateDisplay flag.
    if ( fUpdateDisplay)  {
      // Possible this method called before fOutputPanel
      // created in the init (). So check if it exists
      // before attempting to repaint.
      if (fOutputPanel != null) {
        // Check if binning needs changing before
        // redrawing histogram.
        if ( fHistogram.getRebinFlag () ) fHistogram.rebin ();

        // Now rescale and draw.
        fOutputPanel.getScaling ();
        fOutputPanel.repaint ();
      }
      fUpdateDisplay = false;
    }
    return fMakingHist;
  } // Update

  /**  Called when the histogram filling is finished. **/
  public void done () {
    fMakingHist = false;

    // Stop the histogram display updates.
    fTimer.cancel ();

    // Do a final check if binning needs changing and to
    // redraw histogram.
    if (fHistogram.getRebinFlag () ) fHistogram.rebin ();
    fOutputPanel.getScaling ();
    fOutputPanel.repaint ();

    // Reset the buttons.
    fGoButton.setText ("Go");

    fGoButton.setEnabled (true);
    fStatsButton.setEnabled (true);
    fClearButton.setEnabled (true);

  } // done

  public static void main (String[] args) {
    //
    int frame_width=450;
    int frame_height=300;

    //
    HistR1Applet applet = new HistR1Applet ();
    applet.fInBrowser = false;
    applet.init ();

    // Following anonymous class used to close window & exit program
    JFrame f = new JFrame ("Demo");
    f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);

    // Add applet to the frame
    f.getContentPane ().add ( applet);
    f.setSize (new Dimension (frame_width,frame_height));
    f.setVisible (true);
  } // main

} // class HistR1Applet

 

References & Web Resources

Last update: Nov. 8, 2004

              Tech
Timers
  Demo 1
Hist. Adapt Range
  Demo 2
Sorting in Java
  Demo 3
Histogram Median
  Demo 4
Refactoring
  Demo 5
Error Bars
  Demo 6
Exercises

           Physics
Least Squares Fit
  Demo 1
Fit to Polynomial
  Demo 2
Fit Hist Errors
  Demo 3
Discretization
  Demo 4
Timing
  Demo 5
Exercises

  Part I Part II Part III
Java Core 1  2  3  4  5  6  7  8  9  10  11  12 13 14 15 16 17
18 19 20
21
22 23 24
Supplements

1  2  3  4  5  6  7  8  9  10  11  12

Tech 1  2  3  4  5  6  7  8  9  10  11  12
Physics 1  2  3  4  5  6  7  8  9  10  11  12

Java is a trademark of Sun Microsystems, Inc.