| 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 |