Perhaps you are running an experiment and monitoring a particular
parameter in the data with a histogram display. You don't know a
priori the lower and upper limits of the data but you want to
keep the entire distribution of values within the histogram limits;
no overflows or underflows. You could try to guess at the largest/smallest
values and make the limits slightly larger than that, but your guesses
may be wrong. Also, if the range is wider than neccesary, it can
result in a distribution packed into a just a few bins and thus
not provide much information on the shape of the distribution.
If you want to insure an ideal bin range, you will need to save
the data in an array that grows with the data and allows the histogram
to adapt its range to maintain the data within the display
limits. We created the HistogramAdapt
subclass of HistogramStat
to illustrate one way to do this (see also Besset).
It runs within the AdaptHistFillApplet
applet/application program shown below.
As with the timer demo, we use one
thread class (MakeData)
to create data for the histogram and a timer (which runs on its
own thread) to signal for updates of the histogram display. The
histogram object checks each data value when it is added (via the
void add(double
x) method) to the data array and to the histogram to determine
if it is outside the current range of the histogram. If so, then
a flag is set. The display update thread will check this flag and
if set, it will request that the histogram rebin the data before
redrawing the display.
To avoid a situation where new data points arrive during the rebinning,
the add()
and rebin()
methods in HistogramAdapt
are synchronized.
This prevents more than one thread from entering either of the two
methods simultaneously.
The size of the array to hold the data is first set in the constructor
for HistogramAdapt.
If more data is to be added beyond the size of this array, a new
larger array is created and the values in the old array copied into
the new one. We use the utility static method arraycopy()
in the System
class for this.
AdaptHistFillApplet.java
- similar
to TimerHistApplet
in the Timers section except
that it uses HistogramAdapt (see below). It also borrows
code from HistStatsApplet
in Chapter
7: Tech for displaying statistical info in a popup
frame.
As before,
a timer is used to indicate when an update to the plot
should occur. The data making is done in the thread object,
MakeData,
and the class implements Updateable
interface to provide for callbacks.
+ New class:
HistogramAdapt.java
- subclass of HistogramStat, which is in turn a subclass
of Histogram. Provides for dynamically extending the histogram
limits to fit all data values.
+ 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.
**/
public class HistogramAdapt extends HistogramStat
{
// 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 number of bins for the
histogram.
* @param lowest value of bin.
* @param highest value of bin.
* @param size of data array. Must
equal number of data points
* expected
to be entered into the histogram.
**/
public HistogramAdapt (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
equal number of data points
* expected
to be entered into the histogram.
**/
public HistogramAdapt (String title, String
x_label, int num_bins,
double lo, double hi, int num_data) {
super (title, x_label, num_bins,lo,hi);
if (num_data <= 0)
fAdapt =
false;
else
setData
(num_data);
} // ctor
/** Create a data array if needed. **/
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 dataNeeded
= fDataIndex + num_data + 1;
if (dataNeeded
> fData.length) {
//
Make a new data array
double
[] tmp = new double[dataNeeded];
//
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.**/
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;
}
/**
* 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 rebin the histogram.
* Synchronize so that
*
* @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");
}
}
addEntry (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.
addFromDataArray ();
fNeedToRebin = false;
} // rebin
/** Add a value to the histogram
and do the stats. **/
public void addEntry (double x) {
// Add entry
super.add (x);
// Then do moments if flag set.
if (!fDoDataStats)
return;
else {
fMoments[0]
+= 1;
fMoments[1]
+= x;
double x2
= x * x;
fMoments[2]
+= x2;
fMoments[3]
+= x2 * x;
fMoments[4]
+= x2 * x2;
}
} // addEntry
/** Use the data array to fill the
histogram **/
public void addFromDataArray () {
for (int i=0; i < fDataIndex; i++)
{
addEntry (fData[i]);
}
}
/** Return index to data array element pointer.
**/
int getDataIndex () {
return fDataIndex;
}
/** Turn on or off the rebin request
flag. **/
void setRebinFlag (boolean flag) {
fNeedToRebin = flag;
}
/** Find if rebin request turned
on. **/
boolean getRebinFlag () {
return fNeedToRebin;
}
} // class HistogramAdapt
|
import
javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* This program will run as an applet inside
* an application frame.
*
* The applet uses the HistPanel to display contents
of
* an instance of Histogram. HistFormat used by
HistPanel to
* format the scale values.
*
* 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 AdaptHistFillApplet extends JApplet
implements ActionListener, Updateable
{
// Use the HistPanel JPanel subclass here
HistPanel fOutputPanel;
HistogramAdapt fHistogram;
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 timer;
// A text field for input strings
JTextField fTextField;
// 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
/**
* Stop the filling if the browser
page unloaded.
*/
public void stop () {
fGoButton.setText ("Go");
fStatsButton.setEnabled (true);
fClearButton.setEnabled (true);
fMakingHist = false;
} // stop
/** Respond to buttons. **/
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");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
JTextArea area = new JTextArea ();
double [] stats = fHistogram.getDataStats
();
if (stats != null) {
area.append ("Number entries
= " +
fHistogram.getTotal
()+"\n");
String stat = PlotFormat.getFormatted
(
stats[HistogramStat.I_MEAN],
1000.0,0.001,3);
area.append ("Mean value
= "+ stat +" ");
stat = PlotFormat.getFormatted
(
stats[HistogramStat.I_MEAN_ERROR],
1000.0,0.001,3);
area.append (" +/- "+stat+"\n");
stat = PlotFormat.getFormatted
(
stats[HistogramStat.I_STD_DEV],
1000.0,0.001,3);
area.append ("Std. Dev.
= "+stat+"\n");
stat = PlotFormat.getFormatted
(
stats[HistogramStat.I_SKEWNESS],
1000.0,0.001,3);
area.append ("Skewness
= "+stat+"\n");
stat = PlotFormat.getFormatted
(
stats[HistogramStat.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 HistogramAdapt (
"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 data_thread = 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 ()
timer = new java.util.Timer ();
// Start the timer after 100ms and
then repeat calls
// to run in PaintHistTask object
every 250ms.
timer.schedule (new PaintHistTask
(), 100, 250);
// Now start the data filling.
data_thread.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
/**
* Invoked by MakeData instance
to indicate new data
* added to the histogram.
Repaint the histogram display
* and turn off the update
display flag. Check the histogram
* to rebin if necessary.
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.
timer.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");
fStatsButton.setEnabled (true);
fClearButton.setEnabled (true);
} // done
public static void main (String[] args) {
//
int frame_width=450;
int frame_height=300;
//
AdaptHistFillApplet applet = new AdaptHistFillApplet
();
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 AdaptHistFillApplet |
References
& Web Resources
Last update: Nov. 8, 2004
|