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
|