Home : Course Map : Chapter 8 : Java : Tech :
Histogram with Adapting Range
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

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

              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.