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

One last version of our histogram interface will allow for display of the histogram contents as points with error bars instead of vertical columns.

We use the class HistogramStatR1 (see Refactoring), which includes the following methods that deal with the error values for each bin:

  • double [] getBinErrors()- access to the bin error values for all bins
  • double getBinError(int bin) - access to the error value for a particular bin
  • void makeBinErrors() - create error values for each bin using error=sqrt(# entries)
  • boolean packErrors(double [] errors) - set the error values from an array.

The class HistErrPanel , a subclass of PlotPanel, provides for drawing either error bars or the usual columns bars to represent the histogram contents. (It also includes the option of drawing a function over the top of the histogram distribution but we will wait to discuss that in Chapter 8: Physics : Histogram Errors & Fits.

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


HistErrorApplet.java - uses the HistErrPanel to display contents of an instance of HistogramStatR1. It follows the same basic form as the Timers demo where instances of java.util.Timer and java.util.TimerTask update the display of the histogram during the filling of the histogram. Includes a "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 come from an entry in a text field.

+ New class:
HistErrPanel.java - Display the histogram data with the option of points and error bars in addition to the usual bin column type display. It is a subclass of PlotPanel.

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

Chapter 6:Tech: PlotPanel.java, PlotFormat.java

  

import java.awt.*;
import javax.swing.*;

/**
*  Display the histogram data. Subclass of PlotPanel that
*  provides the option of drawing error bars.
*/
public class HistErrPanel extends PlotPanel
{
  // Histogram access
  protected Histogram fHistogram;

  // Flags values to indicate type of bin display
  public final static int DRAW_BARS     = 0;
  public final static int DRAW_PTS      = 1;
  public final static int DRAW_PTS_ERRS = 2;
  int fDrawType = DRAW_BARS;

  // Color settings for the bin display
  Color fSymColor      = Color.BLUE;
  Color fErrorBarColor = Color.BLUE;

  protected Color fBarLineColor = Color.DARK_GRAY;
  protected Color fBarFillColor = Color.PINK;
  int gap = 2; // 2 pixels between bars

  // Include the option to draw a function on the panel
  DrawFunction [] fDrawFunctions = null;
  boolean fDrawFunctionFlag = false;

  // Fractional margin between highest bar and top frame
  double fTopMargin  = 0.05;
  // Fractional margnin between side bars and frame
  double fSideMargin = 0.01;

  // Arrays to hold the numbers for the axes scale.
  double [] fXScaleValue;
  double [] fYScaleValue;

  // Number of values to put on each axis.
  int fNumYScaleValues = 2;
  int fNumXScaleValues = 5;

  // Symbol types for plotting points
  public final static int RECT     = 0;
  public final static int RECTFILL = 1;
  public final static int OVAL     = 2;
  public final static int OVALFILL = 3;
  // Set default symbol to filled oval.
  int fSymbolType = OVALFILL;

  /**  Create the panel with the histogram. **/
  public HistErrPanel (Histogram histogram) {
    fHistogram = histogram;
    getScaling ();
  } // ctor

  /**
    * Create the panel with the histogram and pass array
    * of functions to draw over the histogram plot.
   **/
  public HistErrPanel (Histogram histogram,
                      DrawFunction [] fDrawFunctions) {
    fHistogram = histogram;
    getScaling ();

    // Option of drawing on top of the histogram.
    if ( fDrawFunctions != null){
        this.fDrawFunctions = fDrawFunctions;
        fDrawFunctionFlag = true;
    }
  } // ctor

  /** Switch to a new histogram. **/
  public void setHistogram (Histogram histogram) {
    fHistogram = histogram;
    getScaling ();
    repaint ();
  } // setHistogram

  /**
    * Get the values for putting scaling numbers on
    * the plot axes.
   **/
  void getScaling () {
    fYScaleValue = new double[fNumYScaleValues];
    // Use lowest value of 0;
    fYScaleValue[0] = 0.0;

    fXScaleValue = new double[fNumXScaleValues];
    // First get the low and high values;
    fXScaleValue[0] = fHistogram.getLo ();
    fXScaleValue[fNumXScaleValues-1] = fHistogram.getHi ();

    // Then calculate the difference between the values
    //  (assumes linear scale)
    double range = fXScaleValue[fNumXScaleValues-1] -
                        fXScaleValue[0];
    double dx = range/ (fNumXScaleValues-1);

    // Now set the intermediate scale values.
    for (int i=1; i <  (fNumXScaleValues-1); i++) {
        fXScaleValue[i] = i*dx + fXScaleValue[0];
    }
  } // getScaling

  /** Optional bar color settings. **/
  public void setBarColors (Color line, Color fill) {
    if ( line != null) fBarLineColor = line;
    if ( fill != null) fBarFillColor = fill;
  }

  /** Optional symbol color settings. **/
  public void setSymColors (Color color) {
    if ( color != null) fSymColor=color;
  }

  /**
    * Overrides the abstract method in PlotPanel superclass.
    * Draw the vertical bars that represent the bin contents.
    * Draw also the numbers for the scales on the axes.
   **/
  void paintContents (Graphics g) {
    // Get the histogram max value and bin data
    int maxDataValue = fHistogram.getMax ();

    // Ignore if no data in the histogram.
    // Assumes no negative contents.
    if (maxDataValue == 0) return;

    // Make vertical room for error bars
    if (fDrawType == DRAW_PTS_ERRS) {
        // Use estimate of the error bar for max value
        maxDataValue += Math.sqrt (maxDataValue);
    }

    // Get the histogram bin array.
    int [] bins = fHistogram.getBins ();

    // Remember foreground color for later
    Color oldColor = g.getColor ();

    // Choose to draw bins as either points with or without error bars
    // or vertical column bars.
    switch (fDrawType) {
      case DRAW_PTS:
      case DRAW_PTS_ERRS:
        drawPoints (g,bins,maxDataValue);
        break;

      case DRAW_BARS:
      default:
        drawBars (g,bins,maxDataValue);
        break;
    }

    // Draw the numbers along the axes
    drawAxesNumbers (g, fXScaleValue, fYScaleValue);

    // Draw the overlay functions
    if (fDrawFunctionFlag) {
        for (int i=0; i < fDrawFunctions.length; i++){
            fDrawFunctions[i].draw (
                          g,
                          fFrameX,     fFrameY,
                          fFrameWidth, fFrameHeight,
                          fXScaleValue,fYScaleValue);
        }
     }
    g.setColor (oldColor); //reset original color

  } // paintContents

  /**
    * Draw vertical bars to represent the histogram
    * bin contents.
    * @param g graphics context
    * @param bins histogram array
    * @param maxDataValue the large bin value. Used to set
    * the scale of the histogram.
   **/
  void drawBars (Graphics g, int [] bins, int max_data_value) {
    // Find the left margin width.
    int side_space =  (int)(fFrameWidth*fSideMargin);

    // Find the width of the frame within which to draw
    // the bars.
    // Leave room for gaps between frame and the sides of
    // the first and last bars.
    int draw_width= fFrameWidth - 2 * side_space -
                                  (bins.length-1) * gap;

    // Leave a gap between top of tallest bar and the frame.
    int draw_height=  (int)(fFrameHeight* (1.0 - fTopMargin));

    // Calculate step size between adjacent bars.
    // To avoid build up of roundoff errors, the bar
    // positions will be calculated from a FP value.
    float step_width= (float)draw_width/(float)bins.length;
    int bar_width = Math.round (step_width);
    step_width += gap;

    // Scale the bars to the maximum bin value.
    float scale_factor= (float)draw_height/max_data_value;

    // Starting horizontal point on left hand side.
    int start_x = fFrameX + side_space;
    // Starting vertical point on bottom left of frame
    int start_y = fFrameY + fFrameHeight;

    for (int i=0; i < bins.length; i++) {
        int bar_height =  (int) (bins[i] * scale_factor);

        int bar_x =  (int)(i*step_width) + start_x;

        // Bar drawn from top left corner
        int bar_y= start_y - bar_height;

        g.setColor (fBarLineColor);
        g.drawRect (bar_x, bar_y, bar_width, bar_height);

        g.setColor (fBarFillColor);
        g.fillRect (bar_x+1, bar_y+1, bar_width-2, bar_height-1);
    }

    // Find the scale value of the full frame height.
    fYScaleValue[fYScaleValue.length-1] =
      (double)(fFrameHeight/scale_factor);

  } // drawBars

  /**
    *  Draw points instead of column bars for the histogram contents.
    *  Option to include error bars.
   **/
  void drawPoints (Graphics g, int [] bins, int max_data_value) {
    // If error bars on the points requested, then
    // obtain the error array.
    double [] err_y = null;
    if (fDrawType == DRAW_PTS_ERRS) {
      // Get the histogram error array. If the histogram is
      // not the HistogramStatR1 subclass, an exception will occur
      // here.
      if (fHistogram instanceof HistogramStatR1){
          err_y =  ( (HistogramStatR1)fHistogram).getBinErrors ();
          if (err_y == null) return;
      } else
          return;
    }

    // Find the left margin width.
    int side_space =  (int) (fFrameWidth*fSideMargin);

    // For data symbols get size relative to the frame.
    int sym_dim =  (int) (fFrameWidth *.01);

    // Leave a gap between top of tallest bar and the frame.
    int draw_height=  (int) (fFrameHeight* (1.0 - fTopMargin));

    // Scale the data points to the maximum bin value.
    float scale_factor= (float)draw_height/max_data_value;

    // Calculate step size between adjacent points.
    // To avoid build up of roundoff errors, the point
    // positions will be calculated from a FP value.
    float step_width= (float)fFrameWidth/ (float)bins.length;

    // Calculate starting point horizontally. Put points
    // in middle of bin.
    int start_x = Math.round (step_width/2) + fFrameX;
    // Starting vertical point on bottom left of frame
    int start_y = fFrameY + fFrameHeight;

    // Use half the width of a bin as the x error bar.
    int x_error_bar = fFrameWidth/(2 * bins.length);

    for (int i=0; i < bins.length; i++) {
      int x =  (int) (i * step_width)+start_x;
      int y = start_y -  (int) (bins[i] * scale_factor);

      // Draw data point symbols
      g.setColor (fSymColor);
      switch  (fSymbolType) {
        case RECT :
            g.drawRect (x-sym_dim, y-sym_dim, 2*sym_dim, 2*sym_dim);
            break;

        case RECTFILL :
            g.fillRect (x-sym_dim, y-sym_dim, 2*sym_dim+1, 2*sym_dim+1);
            break;

        case OVAL :
            g.drawOval (x-sym_dim, y-sym_dim, 2*sym_dim, 2*sym_dim);
            break;

        case OVALFILL :
        default :
            g.fillOval (x-sym_dim, y-sym_dim, 2*sym_dim+1, 2*sym_dim+1);
            break;
      }

      g.setColor (fErrorBarColor);

      if (fDrawType == DRAW_PTS_ERRS) {
          // Draw x error bar as bin width.
          g.drawLine (x-x_error_bar,y,x+x_error_bar,y);

          if (err_y != null) {
              int y_bar =  (int)(err_y[i] * scale_factor/2.0);
              // Draw y error bars but keep within the
              // frame.
              int dy_up = y - y_bar;
              if (dy_up < fFrameY) dy_up = fFrameY;
              int dy_down = y + y_bar;
              if (dy_down > start_y) dy_down = start_y;
              g.drawLine (x, dy_up, x, dy_down);
          }
       }
    }
    // Find the scale value of the full frame height.
    fYScaleValue[fYScaleValue.length-1] =
       (double)(fFrameHeight/scale_factor);

  } // drawPoints

  // Methods overriding those in PlotPanel
  String getTitle ()
  {  return fHistogram.getTitle ();}

  String getXLabel ()
  {  return fHistogram.getXLabel ();}

  /**
    *  Set type of symbol to use for a point in the plot.
    *  @param type set to one of the class constants.
   **/
  void setSymbolType (int type) {
    if (type < 0 || type > 3) return;
    fSymbolType = type;
  }

  /** Set flag for drawing the error bars or not. **/
  void setDrawType (int drawType) {
     fDrawType = drawType;
  }

  /**
    *  Pass object to array of functions to draw over
    *  the histogram distributions.
   **/
  void setDrawFunction ( DrawFunction [] functions) {
     fDrawFunctions = functions;
  }

  /** Pass flag to draw a function overlay. **/
  void setDrawFunctionEnabled (boolean flag) {
     fDrawFunctionFlag = flag;
     // Don't enable if no function available.
     // Could throw an exception or a warning dialog here.
     //if ( drawFunction = null) fDrawFunctionFlag = false;

  } // setDrawFunctionEnabled

} // class HistErrPanel

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 HistErrPanel to display contents of
  *  an instance of HistogramStatR1.
  *
  * 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 HistErrorApplet extends JApplet
             implements ActionListener, Updateable
{
  // Use the HistPanel JPanel subclass here
  HistErrPanel fOutputPanel;

  HistogramStatR1 histogram;

  boolean fMakingHist    = false;
  boolean fUpdateDisplay = false;
  MakeData fMakeData;
  int fNumDataPoints=100;

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

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

  int fDrawType = HistErrPanel.DRAW_PTS_ERRS;

  // 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 HistErrPanel (histogram);
    fOutputPanel.setDrawType (fDrawType);

    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) {
      // Clear the histogram
      histogram.clear ();
      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 = histogram.getStats ();
    if (stats != null) {
      area.append ("Number entries = " + histogram.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 (histogram == null)
        histogram = new HistogramStatR1 ("Gaussian + Random Background",
                                "Data", 20,-10.0,10.0);

    // 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, histogram, 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 ()
    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.
    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 () {
       if (fDrawType == HistErrPanel.DRAW_PTS_ERRS)
           histogram.makeBinErrors ();
       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) {
         // 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 ();

    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;

    // Create an instance of the applet and display it on a frame.
    HistErrorApplet applet = new HistErrorApplet ();
    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 HistErrorApplet

 

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.