/**
 *  EDataset stores and plots (x,y) points.
 *  EDataset is Drawable and can be rendered on a DrawingPanel. EDataset
 *  extends AbstractTableModel and can be rendered in a JTable.
 *
 *  Modified January, 2004 by Max Perkins and Dr. J. Hasbun 
 *
 * @author     Joshua Gould
 * @author     Wolfgang Christian
 * @created    February 13, 2002
 * @version    1.0
 */

/* This class was modified by Max Perkins and Dr. J. Hasbun in February 2004.
 * It was modified to allow for cell editing and "blank" character storage in
 * the data table. Two functions were edited and one was added. getValueAt() 
 * was edited to return a blank for the x value if it is Double.NaN (the blank
 * char in data table) as opposed to just the y value. removeBadData() was edited
 * to allow us to keep Double.NaN in the dataset. setValueAt() was added to allow
 * us to edit the data set.
 * 
 * Max Perkins was sponsored by the NASA Space Grant Consortium through Dr. Ben de Mayo 
 * of the Physics Department at the State University of West Georgia.
 */
package org.opensourcephysics.jahasbun.QE.display;

import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import org.opensourcephysics.display.*;

public class EDataset extends AbstractTableModel implements Measurable {

  /** Field NO_MARKER */
  public final static int NO_MARKER     = 0;  // no marker

  /** Field CIRCLE */
  public final static int CIRCLE        = 1;  // marker type

  /**  Field SQUARE */
  public final static int SQUARE        = 2;  // marker type

  /** Field AREA           */
  public final static int AREA   = 5;  // marker type

  /** Field PIXEL           */
  public final static int PIXEL         = 6;  // marker type

  /** Field BAR           */
  public final static int BAR         = 7;  // marker type

  /** Field POST           */
  public final static int POST         = 8;  // marker type

  protected double[]      xpoints;
  // array of x points
  protected double[]      ypoints;
  // array of y points\
  protected GeneralPath   generalPath;
  // path used to draw line plots
  protected double        xmax;
  // the maximum x value in the dataset
  protected double        ymax;
  // the maximum y value in the dataset
  protected double        xmin;
  // the minimum x value in the dataset
  protected double        ymin;
  // the minimum y value in the dataset
  protected int           index;
  // the current index of the array
  protected boolean       sorted = false;
  // sort the data by increasing x
  private int             initialSize;
  // the initial size of the points array
  private int             markerSize;
  // the size in pixels of the marker
  private int             markerShape = SQUARE;
  // the type of marker
  private Color           lineColor;
  // the color of the line
  private Color           fillColor;
  // the fill color of the marker
  private Color           edgeColor;
  // the edge color of the marker
  private Color           errorBarColor;
  // the error bar color of the marker
  private boolean         connected;
  // whether the points are connected
  private String          xColumnName;
  // the name of the x data
  private String          yColumnName;
  // the name of the y data
  private boolean[]       visible   = new boolean[2];
  // column visibilites for table view
  private int             stride    = 1;
  // stride for table view
  protected int           maxPoints = 0xffff;
  // the maximum number of points that will be saved in a dataset
  protected ArrayList     errorBars = new ArrayList();
  
  /**
   *  EDataset contructor.
   */
  public EDataset() {
    this(Color.black, Color.black, false);
  }

  /**
   *  EDataset contructor specifying the marker color.
   *
   * @param  _markerColor
   */
  public EDataset(Color _markerColor) {
    this(_markerColor, Color.black, false);
  }

  /**
   *  EDataset contructor specifying the marker color, line color, and whether
   *  points are connected.
   *
   * @param  _markerColor
   * @param  _lineColor
   * @param  _connected
   */
  public EDataset(Color markerColor, Color _lineColor, boolean _connected) {
    fillColor = markerColor;
    edgeColor = markerColor;
    errorBarColor=markerColor;
    lineColor   = _lineColor;
    connected   = _connected;
    markerSize  = 2;
    initialSize = 10;
    xColumnName = "x";
    yColumnName = "y";
    generalPath = new GeneralPath();
    index       = 0;
    java.util.Arrays.fill(visible, true);
    clear();
  }

  /**
   *  Sets the sorted flag. Data is sorted by increasing x.
   *
   * @param  _sorted  <code>true<\code> to sort
   *
   */
  public void setSorted(boolean _sorted) {
    sorted = _sorted;
    if(sorted) {
      insertionSort();
    }
  }

  /**
   *  Sets the data connected flag. Points are connected by straight lines.
   *
   * @param  _connected  <code>true<\code> if points are connected
   *
   */
  public void setConnected(boolean _connected) {
    connected = _connected;
    if(connected) recalculatePath();
  }

  /**
   *  Sets the data point fill, edge, and error bar colors to the same color.
   *
   * @param  _markerColor
   */
  public void setMarkerColor(Color markerColor) {
    fillColor = markerColor;
    edgeColor = markerColor;
    errorBarColor=markerColor;
  }

  /**
   * Sets the data point marker colors.
   *
   * The error bar color is set equal to the edge color.
   *
   * @param  _fillColor
   * @param  _edgeColor
   */
  public void setMarkerColor(Color _fillColor, Color _edgeColor) {
    fillColor = _fillColor;
    edgeColor = _edgeColor;
    errorBarColor=_edgeColor;
  }

  /**
   * Sets the data point marker colors.
   *
   * @param  _fillColor
   * @param  _edgeColor
   * @param  _errorBarColor
   */
  public void setMarkerColor(Color _fillColor, Color _edgeColor, Color _errorBarColor) {
    fillColor = _fillColor;
    edgeColor = _edgeColor;
    errorBarColor=_errorBarColor;
  }

  /**
   *  Sets the data point marker shape. Shapes are: NO_MARKER, CIRCLE, SQUARE,
   *  AREA, PIXEL, BAR, POST
   *
   * @param  _markerShape
   */
  public void setMarkerShape(int _markerShape) {
    markerShape = _markerShape;
  }

  /**
   *  Sets the half-width of the data point marker.
   *
   * @param  _markerSize  in pixels
   */
  public void setMarkerSize(int _markerSize) {
    markerSize = _markerSize;
  }

  /**
   *  Sets the color of the lines connecting data points.
   *
   * @param  _lineColor
   */
  public void setLineColor(Color _lineColor) {
    lineColor = _lineColor;
  }

  /**
   *  Sets the column names when rendering this dataset in a JTable.
   *
   * @param  _xColumnName
   * @param  _yColumnName
   */
  public void setXYColumnNames(String _xColumnName, String _yColumnName) {
    xColumnName = _xColumnName;
    yColumnName = _yColumnName;
  }

  /**
   *  Gets the valid measure flag. The measure is valid if the min and max values
   *  have been set.
   *
   * @return    <code>true<\code> if measure is valid
   *
   */
  public boolean isMeasured() {
    // changed by D.Brown
    return ymin < Double.MAX_VALUE;
    //return index >= 1;
  }

  /**
   *  Gets the x world coordinate for the left hand side of the panel.
   *
   * @return    xmin
   */
  public double getXMin() {
    return xmin;
  }

  /**
   *  Gets the x world coordinate for the right hand side of the panel.
   *
   * @return    xmax
   */
  public double getXMax() {
    return xmax;
  }

  /**
   *  Gets y world coordinate for the bottom of the panel.
   *
   * @return    ymin
   */
  public double getYMin() {
    return ymin;
  }

  /**
   *  Gets y world coordinate for the top of the panel.
   *
   * @return    ymax
   */
  public double getYMax() {
    return ymax;
  }

  /**
   *  Gets a copy of the xpoints array.
   *
   * @return    xpoints[]
   */
  public double[] getXPoints() {
    double[] temp = new double[index];
    System.arraycopy(xpoints, 0, temp, 0, index);
    return temp;
  }

  /**
   *  Gets a copy of the ypoints array.
   *
   * @return    ypoints[]
   */
  public double[] getYPoints() {
    double[] temp = new double[index];
    System.arraycopy(ypoints, 0, temp, 0, index);
    return temp;
  }

  /**
   *  Gets the sorted flag.
   *
   * @return    <code>true<\code> if the data is sorted
   *
   */
  public boolean isSorted() {
    return sorted;
  }

  /**
   *  Gets the data connected flag.
   *
   * @return    <code>true<\code> if points are connected
   *
   */
  public boolean isConnected() {
    return connected;
  }

  /**
   *  Gets the number of columns for rendering in a JTable.
   *
   * @return    the count
   */
  public int getColumnCount() {
    return EDataset.countColumnsVisible(visible);
  }

  /**
   *  Gets the current index of the array.
   *
   *  The index is equal to the number of data points that are currently stored.
   *  When data is appended, it will fill the xpoints and ypoints arrays starting
   *  at the current index.
   *
   * @return    the count
   */
  public int getIndex() {
    return index;
  }

  /**
   *  Gets the number of rows for rendering in a JTable.
   *
   * @return    the count
   */
  public int getRowCount() {
    return index / stride;
  }

  /**
   *  Gets the name of the colummn for rendering in a JTable
   *
   * @param  columnIndex
   * @return              the name
   */
  public String getColumnName(int columnIndex) {
    //System.out.println("columnIndex before " + columnIndex);
    columnIndex = EDataset.convertTableColumnIndex(visible, columnIndex);
    //System.out.println("columnIndex after " + columnIndex);
    if(columnIndex == 0) {
      return xColumnName;
    } else {
      return yColumnName;
    }
  }

  /**
   *  Gets an x or y value for rendering in a JTable.
   *
   *  modified by Max Perkins and Dr. J. Hasbun  
   *
   * @param  rowIndex
   * @param  columnIndex
   * @return              the datum
   */
  public Object getValueAt(int rowIndex, int columnIndex) {
    columnIndex = EDataset.convertTableColumnIndex(visible, columnIndex);
    rowIndex    = rowIndex * stride;
    if(columnIndex == 0) {
      if(Double.isNaN(xpoints[rowIndex])) // added by Max Perkins and Dr. J. Hasbun
        return null;
      return new Double(xpoints[rowIndex]);
    } else {
      // changed by D.Brown
      if (Double.isNaN(ypoints[rowIndex])) 
          return null;
      return new Double(ypoints[rowIndex]);
    }
  }

  /**
   * Sets the value at (row, col) to value
   *
   *@param value
   *@param rowIndex
   *@param columnIndex
   *
   * added by Max Perkins and Dr. J. Hasbun
   *
   */
  public void setValueAt(Object value, int rowIndex, int columnIndex) {    
    if(columnIndex == 1) {
      xpoints[rowIndex] = Double.parseDouble(value.toString());
    } else {
      ypoints[rowIndex] = Double.parseDouble(value.toString());
    }
    removeBadData();
  }

  /**
   *  Gets the type of object for JTable entry.
   *
   * @param  columnIndex
   * @return              the class
   */
  public Class getColumnClass(int columnIndex) {
    return Double.class;
  }

  /**
   *  Appends a data point and its uncertainty to the EDataset.
   *
   * @param  x
   * @param  y
   * @param  delx
   * @param  dely
   */
  public void append(double x, double y, double delx, double dely) {
    errorBars.add(new ErrorBar( x, y, delx, dely));
    append( x,  y);
  }

  /**
   * Appends an (x,y) datum to the EDataset. A y value of Double.NaN
   * is treated as null in plots and tables.
   *
   * @param  x
   * @param  y
   */
  public void append(double x, double y) {
    // changed by D.Brown
    if(Double.isInfinite(x) || Double.isInfinite(y)) {
      return;
    }
    if(index >= xpoints.length) {
      increaseCapacity(xpoints.length * 2);
    }
    xpoints[index] = x;
    ypoints[index] = y;
    // generalPath.append(new Rectangle2D.Double(x, y, 0, 0), true);
    if (!Double.isNaN(y)) {
      Point2D curPt = generalPath.getCurrentPoint();
      if(curPt == null) {
        generalPath.moveTo((float) x, (float) y);
      } else {
        generalPath.lineTo((float) x, (float) y);
      }
      ymax = Math.max(y, ymax);
      ymin = Math.min(y, ymin);
    }
    xmax = Math.max(x, xmax);
    xmin = Math.min(x, xmin);
    index++;
    // move the new datum if x is less than the last value.
    if(sorted && (index > 1) && (x < xpoints[index - 2])) {
      moveDatum(index - 1);
      // the new datum is out of place so move it
      recalculatePath();
      //System.out.println("data moved");
    }
  }

  /**
   *  Appends arrays of data points and uncertainties to the EDataset.
   *
   * @param  xpoints
   * @param  ypoints
   * @param  delx
   * @param  dely
   */
  public void append(double[] xpoints, double[] ypoints, double[] delx, double[] dely) {
    for(int i=0, n=xpoints.length; i<n; i++){
      errorBars.add(new ErrorBar( xpoints[i], ypoints[i], delx[i], dely[i]));
    }
    append( xpoints,  ypoints);
  }

  /**
   * Appends (x,y) arrays to the EDataset. Any y value of Double.NaN
   * is treated as null in plots and tables.
   *
   * @param  _xpoints
   * @param  _ypoints
   */
  public void append(double[] _xpoints, double[] _ypoints) {
    // changed by D.Brown
    boolean badData = false;
    for(int i = 0; i < _xpoints.length; i++) {
      if(Double.isInfinite(_xpoints[i])
          || Double.isInfinite(_ypoints[i])) {
        badData = true;
        continue;
      }
      xmax = Math.max(_xpoints[i], xmax);
      xmin = Math.min(_xpoints[i], xmin);
      if (!Double.isNaN(_ypoints[i])) {
        ymax = Math.max(_ypoints[i], ymax);
        ymin = Math.min(_ypoints[i], ymin);
        Point2D curPt = generalPath.getCurrentPoint();
        if(curPt == null) {
          generalPath.moveTo((float) _xpoints[i], (float) _ypoints[i]);
        } else {
          generalPath.lineTo((float) _xpoints[i], (float) _ypoints[i]);
        }
      }
    }
    int pointsAdded    = _xpoints.length;
    int availableSpots = xpoints.length - index;
    if(pointsAdded > availableSpots) {
      increaseCapacity(xpoints.length + pointsAdded);
      // fix
    }
    System.arraycopy(_xpoints, 0, xpoints, index, pointsAdded);
    System.arraycopy(_ypoints, 0, ypoints, index, pointsAdded);
    index += pointsAdded;
    if(badData) {
      removeBadData();
    }
    if(sorted) {
      insertionSort();
    }
  }

  /**
   *    Reads a file and appends the data contained in the file to this
   *    EDataset. The format of the file is x and y coordinates separated by tabs.
   *    Lines beginning with # are ignored.
   * @param inputFile
   */
  public void read(String inputFile) {
    java.io.BufferedReader reader = null;
    try {
      reader = new java.io.BufferedReader(new java.io.FileReader(inputFile));
    } catch(java.io.FileNotFoundException fnfe) {
      System.err.println("File " + inputFile + " not found.");
    }
    String s = null;
    try {
      while((s = reader.readLine()) != null) {
        s = s.trim();
        if(s.charAt(0) == '#') {  // ignore lines beginning with #
          continue;
        }
        try {
          java.util.StringTokenizer st = new java.util.StringTokenizer(s, "\t");
          append(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken()));
        } catch(java.util.NoSuchElementException nsee) {}
        catch(NumberFormatException nfe) {}
      }
    } catch(java.io.IOException ioe) {
      System.err.println("Error reading file.");
    }
  }

  /**
   *  Draw this EDataset in the drawing panel.
   *
   * @param  drawingPanel
   * @param  g
   */
  public void draw(DrawingPanel drawingPanel, Graphics g) {
    try {
      Graphics2D g2 = (Graphics2D) g;
      if(markerShape != NO_MARKER) {
        drawScatterPlot(drawingPanel, g2);
      }
      if(connected) {
       drawLinePlot(drawingPanel, g2);
      }
    } catch(Exception ex) {}  // abort drawing if we have bad data
  }

  /**
   *  Clear all data from this EDataset.
   */
  public void clear() {
    index   = 0;
    xpoints = new double[initialSize];
    ypoints = new double[initialSize];
    generalPath.reset();
    errorBars.clear();
    resetXYMinMax();
  }

  /**
   *  Creates a string representation of the data.
   *
   * @return    the data
   */
  public String toString() {      
    if(index == 0) {
      return "No data in dataset.";
    }
    String       s = xpoints[0] + "\t" + ypoints[0] + "\n";
    StringBuffer b = new StringBuffer(index * s.length());
    for(int i = 0; i < index; i++) {
      b.append(xpoints[i]);
      b.append('\t');
      // changed by D.Brown
      if (Double.isNaN(ypoints[i])) b.append("null");
      else b.append(ypoints[i]);
      b.append('\n');
      // s += xpoints[i] + "\t" + ypoints[i] + "\n";
    }
    return b.toString();      
  }

  /**
   *  Counts the number of columns visible
   *
   * @param  visible  array of column visibilities
   * @return          number of visible columns
   */
  public static int countColumnsVisible(boolean visible[]) {
    int count = 0;
    for(int i = 0; i < visible.length; i++) {
      if(visible[i]) {
        count++;
      }
    }
    return count;
  }

  /**
   *  Converts a table column in a table model to the appropriate table column.
   *  For example, if the x points are hidden in a EDataset, and the column index
   *  is 0, then this method will return 1.
   *
   * @param  visible      array of column visibilities
   * @param  columnIndex  table column index to convert
   * @return              converted table column index
   */
  public static int convertTableColumnIndex(boolean visible[], int columnIndex) {
    if (columnIndex == 0 && !visible[0]) columnIndex++;
    else if (columnIndex == 1 && !visible[1]) columnIndex--;
    return columnIndex;
  }

  /**
   * Sets the visibility of the x column of this EDataset in a table view.
   * @param b new visibility
   */
  public void setXColumnVisible(boolean b) {
    visible[0] = b;
  }

  /**
   * Sets the visibility of the y column of this EDataset in a table view.
   * @param b new visibility
   */
  public void setYColumnVisible(boolean b) {
    visible[1] = b;
  }

  /**
   * Sets the stride of this EDataset in a table view.
   * @param _stride the stride
   */
  public void setStride(int _stride) {
    stride = _stride;
  }

  /**
   * Gets the visibility of the x column of this EDataset in a table view.
   * @return the x column visibility
   */
  public boolean isXColumnVisible() {
    return visible[0];
  }

  /**
   * Gets the visibility of the y column of this EDataset in a table view.
   * @return the x column visibility
   */
  public boolean isYColumnVisible() {
    return visible[1];
  }


  /**
   *  Perform an insertion sort of the data set. Since data will be partially
   *  sorted this should be fast. Added by W. Christian.
   */
  protected void insertionSort() {
    boolean dataChanged = false;
    if(index < 2) {
      return;
      // need at least two points to sort.
    }
    for(int i = 1; i < index; i++) {
      if(xpoints[i] < xpoints[i - 1]) {
        // is the i-th datum smaller?
        dataChanged = true;
        moveDatum(i);
      }
    }
    if(dataChanged) {
      recalculatePath();
    }
  }

  /**
   *  Recalculate the general path.
   */
  protected void recalculatePath() {
    generalPath.reset();
    if(index < 1) {
      return;
    }
    // changed by D.Brown
    int i = 0;
    for(; i < index; i++) {
      if (!Double.isNaN(ypoints[i])) {
        generalPath.moveTo((float) xpoints[i], (float) ypoints[i]);
        break;
      }
    }
    for(int j = i+1; j < index; j++) {
      if (!Double.isNaN(ypoints[j])) {
        generalPath.lineTo((float) xpoints[j], (float) ypoints[j]);
      }
    }
  }

  /**
   *  Move an out-of-place datum into its correct position.
   *
   * @param  loc  the datum
   */
  protected void moveDatum(int loc) {
    if(loc < 1) {
      return;
      // zero-th point cannot be out-of-place
    }
    double x = xpoints[loc];
    // save the old values
    double y = ypoints[loc];
    for(int i = 0; i < index; i++) {
      if(xpoints[i] > x) {
        // find the insertion point
        System.arraycopy(xpoints, i, xpoints, i + 1, loc - i);
        xpoints[i] = x;
        System.arraycopy(ypoints, i, ypoints, i + 1, loc - i);
        ypoints[i] = y;
        return;
      }
    }
  }

  /**
   *  Draw the lines connecting the data points.
   *
   * @param  drawingPanel
   * @param  g2
   */
  protected void drawLinePlot(DrawingPanel drawingPanel, Graphics2D g2) {
    // changed by D.Brown
    // check that at least one ypoints element is a number
    boolean noNumbers = true;
    for (int i = 0; i < index; i++) {
      noNumbers = Double.isNaN(ypoints[i]);
      if (!noNumbers) break;
    }
    if (noNumbers) return;
    AffineTransform at = drawingPanel.getPixelTransform();
    Shape           s  = generalPath.createTransformedShape(at);
    g2.setColor(lineColor);
    g2.draw(s);
  }

  /**
   *  Fills the line connecting the data points.
   *
   * @param  drawingPanel
   * @param  g2
   */
  protected void drawFilledPlot(DrawingPanel drawingPanel, Graphics2D g2) {
    // changed by D.Brown
    // check that at least one ypoints element is a number
    boolean noNumbers = true;
    for (int i = 0; i < index; i++) {
      noNumbers = Double.isNaN(ypoints[i]);
      if (!noNumbers) break;
    }
    if (noNumbers) return;
    AffineTransform at = drawingPanel.getPixelTransform();
    Shape           s  = generalPath.createTransformedShape(at);
    g2.setColor(fillColor);
    g2.fill(s);
    g2.setColor(edgeColor);
    g2.draw(s);
  }

  /**
   *  Draw the markers at the data points.
   *
   * @param  drawingPanel
   * @param  g2
   */
  protected void drawScatterPlot(DrawingPanel drawingPanel, Graphics2D g2) {
    if(markerShape == AREA) {
      this.drawFilledPlot(drawingPanel, g2);
      return;
    }
    double xp    = 0;
    double yp    = 0;
    Shape  shape = null;
    int size = markerSize * 2 + 1;
    Shape clipShape=g2.getClip();
    Rectangle rect=clipShape.getBounds();
    // increase the clip so as to include the entire marker
    g2.clipRect(drawingPanel.getLeftGutter()-markerSize-1,drawingPanel.getTopGutter()-markerSize-1,
      drawingPanel.getWidth()- drawingPanel.getLeftGutter() - drawingPanel.getRightGutter() +2 +2*markerSize,
      drawingPanel.getHeight() - drawingPanel.getBottomGutter() - drawingPanel.getTopGutter() +2 +2*markerSize);
/*
    Rectangle viewRect=drawingPanel.getViewRect();
    if (viewRect != null) {
      g2.clipRect(viewRect.x, viewRect.y,
                  viewRect.x + viewRect.width,
                  viewRect.y + viewRect.height);
    }
*/

    for(int i = 0; i < index; i++) {
      if (Double.isNaN(ypoints[i])) continue;
      xp = drawingPanel.xToPix(xpoints[i]);
      yp = drawingPanel.yToPix(ypoints[i]);
      switch(markerShape) {
        case BAR :  // draw a bar graph.
          double bottom=Math.min(drawingPanel.yToPix(0),drawingPanel.yToPix(drawingPanel.getYMin()));
          double barHeight=bottom-yp;
          if(barHeight>0)
            shape = new Rectangle2D.Double(xp - markerSize, yp, size, barHeight);
          else shape = new Rectangle2D.Double(xp - markerSize, bottom, size, -barHeight);
          g2.setColor(fillColor);
          g2.fill(shape);
          g2.setColor(edgeColor);
          g2.draw(shape);
          break;
        case POST :
          bottom=Math.min(drawingPanel.yToPix(0),drawingPanel.yToPix(drawingPanel.getYMin()));
          shape = new Rectangle2D.Double(xp - markerSize, yp - markerSize, size, size);
          g2.setColor(edgeColor);
          g2.drawLine((int)xp,(int)yp,(int)xp,(int)bottom);
          g2.setColor(fillColor);
          g2.fill(shape);
          g2.setColor(edgeColor);
          g2.draw(shape);
          break;
        case SQUARE :
          shape = new Rectangle2D.Double(xp - markerSize, yp - markerSize, size, size);
          g2.setColor(fillColor);
          g2.fill(shape);
          g2.setColor(edgeColor);
          g2.draw(shape);
          break;
        case CIRCLE :
          shape = new Ellipse2D.Double(xp - markerSize, yp - markerSize, size, size);
          g2.setColor(fillColor);
          g2.fill(shape);
          g2.setColor(edgeColor);
          g2.draw(shape);
          break;
        case PIXEL :
          shape = new Rectangle2D.Double(xp, yp, 0, 0);  // this produces a one pixel shape
          g2.draw(shape);
          // draw and center the point
          break;
        default :
          shape = new Rectangle2D.Double(xp - markerSize, yp - markerSize, size, size);
          g2.setColor(fillColor);
          g2.fill(shape);
          g2.setColor(edgeColor);
          g2.draw(shape);
          break;
      }
    }
    Iterator  it      = errorBars.iterator();
    while(it.hasNext()) {  // copy only the obejcts of the correct type
      ((ErrorBar)it.next()).draw(drawingPanel,g2);
    }
    g2.setClip(clipShape);// restore the original clipping
  }

  /**
   *  Removes infinities from the dataset.
   *
   * modified by Max Perkins and Dr. J. Hasbun
   */
  private void removeBadData() {        
    for(int i = 0; i < index; i++) {
      //Double.isNaN(xpoints[i]) ||  <--- Max Perkins and Dr. J. Hasbun
      if(Double.isInfinite(xpoints[i]) || Double.isInfinite(ypoints[i])){          
          if((index == 1)|| (i == index - 1)) {
          // we only have one point and it is a bad point!
          index--;
          break;
          // exit the loop
        }        
        System.arraycopy(xpoints, i + 1, xpoints, i, index - i - 1);
        System.arraycopy(ypoints, i + 1, ypoints, i, index - i - 1);        
        index--;        
      }
    }
  }

  /**
   *  Increase the array size up to a maximum size.
   *
   * @param  newCapacity
   */
  private synchronized void increaseCapacity(int newCapacity) {
    newCapacity = Math.min(newCapacity, maxPoints);  // do not let the number of data points exceed maxPoints
    int      newIndex = Math.min(index, (3 * newCapacity) / 4);  // drop 1/4 of the old data if the capacity is no longer increasing
    double[] tempx    = xpoints;
    xpoints = new double[newCapacity];
    System.arraycopy(tempx, index - newIndex, xpoints, 0, newIndex);
    double[] tempy = ypoints;
    ypoints = new double[newCapacity];
    System.arraycopy(tempy, index - newIndex, ypoints, 0, newIndex);
    index = newIndex;
  }

  /**
   *  Reset the minimum and maximum values.
   */
  private void resetXYMinMax() {
    // changed by W. Christian
    xmax = -Double.MAX_VALUE;
    ymax = -Double.MAX_VALUE;
    xmin = Double.MAX_VALUE;
    ymin = Double.MAX_VALUE;
    for(int i = 0; i < index; i++) {
      if(Double.isNaN(xpoints[i]) || Double.isInfinite(xpoints[i])
          || Double.isInfinite(ypoints[i])) {
        continue;
      }
      xmax = Math.max(xpoints[i], xmax);
      xmin = Math.min(xpoints[i], xmin);
      // changed by D.Brown
      if (!Double.isNaN(ypoints[i])) {
        ymax = Math.max(ypoints[i], ymax);
        ymin = Math.min(ypoints[i], ymin);
      }
    }
  }

  /**
   * ErrorBar for datapoints.
   */
  class ErrorBar implements Drawable{
    double x,y,delx,dely;  // the position and uncertainty of the data point
    int tick=3;

    ErrorBar(double _x, double _y, double _delx, double _dely){
      x=_x;
      y=_y;
      delx=_delx;
      dely= _dely;
    }

    /**
    * Draws the error bars for a data point.
    *
    * @param panel
    * @param g
    */
   public void draw (DrawingPanel panel, Graphics g) {
      // changed by D.Brown
      if (Double.isNaN(y)) return;
      int xpix = panel.xToPix (x);
      int xpix1 = panel.xToPix (x-delx);
      int xpix2 = panel.xToPix (x+delx);
      int ypix = panel.yToPix (y);
      int ypix1 = panel.yToPix (y-dely);
      int ypix2 = panel.yToPix (y+dely);
      g.setColor (errorBarColor);
      g.drawLine(xpix1,ypix,xpix2,ypix);
      g.drawLine(xpix,ypix1,xpix,ypix2);
      g.drawLine(xpix1,ypix-tick,xpix1,ypix+tick);
      g.drawLine(xpix2,ypix-tick,xpix2,ypix+tick);
      g.drawLine(xpix-tick,ypix1,xpix+tick,ypix1);
      g.drawLine(xpix-tick,ypix2,xpix+tick,ypix2);
   }
  }
}

