/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.display;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.table.AbstractTableModel;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLLoader;

/**
 *  Histogram maps bin number to occurrences. Histogram is Drawable and can be
 *  rendered on a DrawingPanel. Histogram also implements TableModel and can be
 *  displayed in a JTable. By default, bins consist of (notation: [ inclusive, )
 *  exclusive): ..., [-1,0), [0,1), [1,2), ...
 *
 * @author     Joshua Gould
 * @author     Wolfgang Christian
 * @created    June 26, 2002
 * @version    1.1
 */
public class Histogram extends AbstractTableModel implements Measurable, LogMeasurable, Data {
  HistogramDataset histogramDataset;

  /** draw point at top of bin */
  public final static int DRAW_POINT = 0;

  /** draw bin from y min to top of bin */
  public final static int DRAW_BIN = 1;
  
  /**
   * Should histogram be drawn on a log scale?  Default is false.
   */
  public boolean logScale = false;

  /**
   * Should the height be adjusted by bin width?  Default is false.
   */
  public boolean adjustForWidth = false;

  /**
   * The visibility of the histogram
   */
  private boolean visible=true; // Paco
  
  /**
   * Force the measured condition
   */
  private boolean measured = true; // Paco 
  
  /** color of bins */
  protected Color binFillColor = Color.red;

  /** color of bins */
  protected Color binEdgeColor = Color.red;

  /** style for drawing bins */
  protected int binStyle = DRAW_BIN;

  /** maps bin number to occurrences */
  HashMap<Integer, Double> bins;

  /** width of a bin */
  double binWidth = 1;

  /** offset of the bins */
  double binOffset = 0;

  /** false if the bins are continuous */
  boolean discrete = true;

  /** binNumber*binWidth + binOffset */
  double xmin;

  /** binNumber*binWidth + binWidth + binOffset */
  double xmax;

  /** min number of occurrences for all bins */
  final int YMIN = 0;

  /** max number of occurrences for all bins */
  double ymax;

  /** the name of the histogram */
  String name;

  /** the name of the bin */
  String binColumnName;

  /** the name of the x column */
  String xColumnName;

  /** the name of the occurrences */
  String yColumnName;

  /**
   *  bin number-occurrences pairs in histogram, used for table model
   *  implementation
   */
  Map.Entry<?, ?>[] entries = new Map.Entry<?, ?>[0];

  /** whether the data has changed since the last time the entries were retrieved */
  boolean dataChanged;

  /** total occurrences in histogram */
  double sum;

  /** whether occurrences are normalized to one */
  boolean normalizedToOne = false;

  /**
   *  amount by which this histogram is shifted to the right, so that it peeks
   *  out from behind other histograms.
   */
  double barOffset;

  /** an integer ID that identifies this object */
  protected int datasetID = hashCode();

  /** Histogram constructor. */
  public Histogram() {
    binColumnName = DisplayRes.getString("Histogram.Column.BinNumber"); //$NON-NLS-1$
    xColumnName = "x";                                                  //$NON-NLS-1$
    yColumnName = DisplayRes.getString("Histogram.Column.Occurrences"); //$NON-NLS-1$
    name = DisplayRes.getString("Histogram.Title");                     //$NON-NLS-1$
    clear();
  }

  /**
   *  Reads a file and appends the data contained in the file to this Histogram.
   *  The format of the file is bins \t occurrences. Lines beginning with # and
   *  empty lines are ignored.
   *
   * @param  inputPathName            A pathname string.
   * @exception  java.io.IOException  Description of the Exception
   */
  public void read(String inputPathName) throws java.io.IOException {
    java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(inputPathName));
    String s = null;
    while((s = reader.readLine())!=null) {
      s = s.trim();
      if(s.equals("")||(s.length()==0)||(s.charAt(0)=='#')) { // ignore empty lines and lines beginning with # //$NON-NLS-1$
        continue;
      }
      try {
        java.util.StringTokenizer st = new java.util.StringTokenizer(s, "\t"); //$NON-NLS-1$
        int binNumber = Integer.parseInt(st.nextToken());
        double numberOfoccurrences = Double.parseDouble(st.nextToken());
        Double prioroccurrences = bins.get(new Integer(binNumber));
        if(prioroccurrences==null) {                                           // first occurence for this bin
          bins.put(new Integer(binNumber), new Double(numberOfoccurrences));
        } else {
          numberOfoccurrences += prioroccurrences.doubleValue();               // increase occurrences for bin by prioroccurrences
          bins.put(new Integer(binNumber), new Double(numberOfoccurrences));
        }
        ymax = Math.max(numberOfoccurrences, ymax);
        xmin = Math.min(binNumber*binWidth+binOffset, xmin);
        xmax = Math.max(binNumber*binWidth+binWidth+binOffset, xmax);
      } catch(java.util.NoSuchElementException nsee) {
        nsee.printStackTrace();
      } catch(NumberFormatException nfe) {
        nfe.printStackTrace();
      }
    }
    dataChanged = true;
  }

  /**
   *  Creates a string representation of this Histogram. The bins are displayed
   *  in ascending order. The format of this string is bin number \t occurrences.
   *  Each bin starts on a new line.
   *
   * @return    A String with the number of occurrences for each bin.
   * @see       #toString
   */
  public String toSortedString() {
    Set<Integer> keySet = bins.keySet();
    Object[] keys = keySet.toArray();
    Arrays.sort(keys);
    String s = "x\tx"; //$NON-NLS-1$
    StringBuffer buf = new StringBuffer(s.length()*keys.length);
    for(int i = 0; i<keys.length; i++) {
      Object key = keys[i];
      buf.append(key);
      buf.append("\t"); //$NON-NLS-1$
      buf.append(bins.get(keys[i]));
      buf.append("\n"); //$NON-NLS-1$
    }
    return buf.toString();
  }

  /**
   *  Creates a string representation of this Histogram. The format is bin
   *  number\t occurrences. Each new bin starts on a new line.
   *
   * @return    A String with the number of occurrences for each bin.
   */
  public String toString() {
    Set<Integer> set = bins.keySet();
    Iterator<Integer> keys = set.iterator();
    String s = "x\tx"; //$NON-NLS-1$
    StringBuffer buf = new StringBuffer(s.length()*set.size());
    while(keys.hasNext()) {
      Integer binNumber = keys.next();
      Double occurrences = bins.get(binNumber);
      buf.append(binNumber);
      buf.append("\t"); //$NON-NLS-1$
      buf.append(occurrences);
      buf.append("\n"); //$NON-NLS-1$
    }
    return buf.toString();
  }

  /**
   *  Computes the hash code (bin number) for the specified value
   *
   * @param  value
   * @return        the hash code
   */
  public int hashCode(double value) {
    return(int) (Math.floor((value-binOffset)/binWidth));
  }

  /**
   *  Append a value with number of occurrences to the Histogram.
   *
   * @param  value
   * @param  numberOfoccurrences
   */
  public synchronized void append(double value, double numberOfoccurrences) {
    sum += numberOfoccurrences;
    int binNumber = hashCode(value);
    // Determine if there have previously been any occurrences for this bin
    Double occurrences = bins.get(new Integer(binNumber));
    if(occurrences==null) {                             // first occurence for this bin
      bins.put(new Integer(binNumber), new Double(numberOfoccurrences));
    } else {
      // need to put Objects in HashMap, but can only add doubles
      numberOfoccurrences += occurrences.doubleValue(); // increase occurrences for bin by numberOfoccurrences
      bins.put(new Integer(binNumber), new Double(numberOfoccurrences));
    }
    ymax = Math.max(numberOfoccurrences, ymax);
    xmin = Math.min(binNumber*binWidth+binOffset, xmin);
    xmax = Math.max(binNumber*binWidth+binWidth+binOffset, xmax);
    dataChanged = true;
  }

  /**
   *  Appends a value with 1 occurence.
   *
   * @param  value
   */
  public void append(double value) {
    append(value, 1);
  }

  /**
   *  Appends values from an input file. Each value is separated by a \n
   *
   * @param  inputPathName    A pathname string.
   * @exception  IOException  Description of the Exception
   */
  public void append(String inputPathName) throws IOException {
    BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(inputPathName));
    String s = null;
    while((s = br.readLine())!=null) {
      s = s.trim();
      if(s.equals("")||(s.length()==0)||(s.charAt(0)=='#')) { //$NON-NLS-1$
        continue;
      }
      try {
        double d = Double.parseDouble(s);
        append(d, 1);
      } catch(NumberFormatException nfe) {
        nfe.printStackTrace();
      }
    }
  }

  /**
   *  Appends an array of values with 1 occurence.
   *
   * @param  values
   */
  public void append(double[] values) {
    for(int i = 0; i<values.length; i++) {
      append(values[i], 1);
    }
  }

  /**
   * Sets the visibility of the histogram
   * @param visibility
   */
  public void setVisible(boolean visibility) {
    this.visible = visibility;
  }
  
  /**
   *  Draws this histogram in the drawing panel.
   *
   * @param  drawingPanel
   * @param  g
   */
  public synchronized void draw(DrawingPanel drawingPanel, Graphics g) {
    if(bins.size()==0 || !visible) {
      return;
    }
    Shape oldClip = g.getClip();
    g.setColor(binFillColor);
    g.clipRect(0, 0, drawingPanel.getWidth(), drawingPanel.getHeight());
    for(Iterator<Integer> keys = bins.keySet().iterator(); keys.hasNext(); ) {
      Integer binNumber = keys.next();
      Double d = (bins.get(binNumber));
      if(d==null) {
        return;
      }
      double occurrences = d.doubleValue();
      if(normalizedToOne) {
        occurrences /= sum;
      }
      if(binStyle==DRAW_BIN) {
        drawBin(drawingPanel, g, binNumber.intValue(), occurrences);
      } else {
        drawPoint(drawingPanel, g, binNumber.intValue(), occurrences);
      }
    }
    g.setClip(oldClip);
  }

  /** Clears all data from this histogram and resets min and max values. */
  public synchronized void clear() {
    bins = new HashMap<Integer, Double>();
    xmin = Integer.MAX_VALUE;
    xmax = Integer.MIN_VALUE;
    ymax = Integer.MIN_VALUE;
    sum = 0;
    dataChanged = true;
  }

  /**
   *  Gets an array of bin number-occurrences pairs
   *
   * @return    The entries.
   */
  public Map.Entry<?, ?>[] entries() {
    updateEntries();
    return entries;
  }

  /**
   *  Sets the style for drawing this histogram. Options are DRAW_POINT, which
   *  draws a point at the top of the bin, and DRAW_BIN which draws the entire
   *  bin down to the x axis. Default is DRAW_BIN.
   *
   * @param  style
   */
  public void setBinStyle(int style) {
    binStyle = style;
  }

  /**
   *  Sets the discrete flag.
   *
   * @param  _discrete  <code>true<\code> if bins are discrete, <code>false<\code> if bins are continuous.
   */
  public void setDiscrete(boolean _discrete) {
    discrete = _discrete;
  }

  /**
   *  Sets the offset of the bins. Default is 0.
   *
   * @param  _binOffset
   */
  public void setBinOffset(double _binOffset) {
    binOffset = _binOffset;
  }

  /**
   *  Set the offset of the bars as a fraction of a bin width. The offset is the
   *  amount by which this histogram is shifted to the right, so that it peeks
   *  out from behind later histograms when displayed in a DrawingPanel.
   *
   * @param  _barOffset  The new barOffset value
   */
  public void setBarOffset(double _barOffset) {
    barOffset = _barOffset;
  }

  /**
   * Line color to use for this data
   * @return
   */
  public java.awt.Color getLineColor() {
    return binEdgeColor;
  }

  /**
   * Line colors for Data interface.
   * @return
   */
  public java.awt.Color[] getLineColors() {
    return new Color[] {binEdgeColor, binEdgeColor};
  }

  /**
   * Fill color to use for this data
   * @return
   */
  public java.awt.Color getFillColor() {
    return binFillColor;
  }

  /**
   * Fill colors for Data interface.
   * @return
   */
  public java.awt.Color[] getFillColors() {
    return new Color[] {binFillColor, binFillColor};
  }

  /**
   *  Sets the bin color.
   *
   * @param  binColor
   */
  public void setBinColor(Color binColor) {
    binFillColor = binColor;
    binEdgeColor = binColor;
  }

  /**
   * Sets the bin's fill and edge colors.  If the fill color is null the bin is not filled.
   *
   * @param fillColor
   * @param edgeColor
   */
  public void setBinColor(Color fillColor, Color edgeColor) {
    binFillColor = fillColor;
    binEdgeColor = edgeColor;
  }

  /**
   *  Sets the width of a bin.
   *
   * @param  _binWidth
   */
  public void setBinWidth(double _binWidth) {
    binWidth = _binWidth;
  }

  /**
   * Sets a name that can be used to identify the dataset.
   *
   * @param name String
   */
  public void setName(String name) {
    this.name = TeXParser.parseTeX(name);
  }

  /**
   * Gets the dataset name.
   *
   * @return String
   */
  public String getName() {
    return name;
  }

  /**
   * The column names to be used in the data display tool
   * @return
   */
  public String[] getColumnNames() {
    return new String[] {xColumnName, yColumnName};
  }

  /**
   * Some elements (a Group, for instance) do not contain data, but a list of subelements which do.
   * This method is used by Data displaying tools to create as many pages as needed.
   * @return A list of DataInformation elements, null if the element itself is a DataInformation
   */
  public java.util.List<Data> getDataList() {
    return null;
  }

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

  /**
   *  Sets the column names when rendering this histogram in a JTable.
   *
   * @param  _binColumnName
   * @param  _yColumnName
   * @param _name String  the name of the histogram
   */
  public void setXYColumnNames(String _xColumnName, String _yColumnName, String _name) {
    xColumnName = TeXParser.parseTeX(_xColumnName);
    yColumnName = TeXParser.parseTeX(_yColumnName);
    name = TeXParser.parseTeX(_name);
  }

  /**
   *  Normalizes the occurrences in this histogram to one.
   *
   * @param  b
   */
  public void setNormalizedToOne(boolean b) {
    normalizedToOne = b;
  }

  /**
   *  Gets the width of a bin.
   *
   * @return    The bin width.
   */
  public double getBinWidth() {
    return binWidth;
  }

  /**
   *  Gets the offset of the bins.
   *
   * @return    The bin offset.
   */
  public double getBinOffset() {
    return binOffset;
  }

  /**
   *  Gets the x world coordinate for the left hand side of this histogram.
   *
   * @return    xmin
   */
  public double getXMin() {
    return(discrete&&(bins.size()>1)) ? xmin-binWidth : xmin;
  }

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

  /**
   *  Gets the y world coordinate for the bottom of this histogram.
   *
   * @return    minimum y value
   */
  public double getYMin() {
    return YMIN;
  }

  /**
   *  Gets the y world coordinate for the top of this histogram.
   *
   * @return    xmax
   */
  public double getYMax() {
    double max = (normalizedToOne ? ymax/sum : ymax);
    if(adjustForWidth) {
      max = max/getBinWidth();
    }
    if(logScale) {
      max = Math.log(max);
    }
    return max;
  }

  /**
   * Gets the minimum x needed to draw this object on a log scale.
   * @return minimum
   */
  public double getXMinLogscale() {
    double xmin = getXMin();
    if(xmin>0) {
      return xmin;
    }
    return binOffset;
  }

  /**
   * Gets the maximum x needed to draw this object on a log scale.
   * @return maximum
   */
  public double getXMaxLogscale() {
    double xmax = getXMax();
    if(xmax>0) {
      return xmax;
    }
    return binOffset+10;
  }

  /**
   * Gets the minimum y needed to draw this object on a log scale.
   * @return minimum
   */
  public double getYMinLogscale() {
    return 1;
  }

  /**
   * Gets the maximum y needed to draw this object on a log scale on a log scale.
   * @return maximum
   */
  public double getYMaxLogscale() {
    return Math.max(10, getYMax()); // make sure we have at least one decade on log scale
  }

  /**
   *  Gets the valid measure flag. The measure is valid if this histogram is not
   *  empty.
   *
   * @return    <code>true<\code> if measure is valid.
   */
  public boolean isMeasured() {
    return bins.size()>0 && measured; // Paco
  }

  /**
   * Forces the measured condition of the histogram
   * @param visibility
   */
  public void setMeasured(boolean measure) { // Paco
    this.measured = measure;
  }
  
  /**
   *  Gets the name of the column for rendering in a JTable
   *
   * @param  column  the column whose value is to be queried
   * @return         the name
   */
  public String getColumnName(int column) {
    if(column==0) {
      return binColumnName;
    } else if(column==1) {
      return xColumnName;
    } else {
      return yColumnName;
    }
  }

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

  /**
   *  Gets the name of the colummn for rendering in a JTable
   *
   * @return    the name
   */
  public int getColumnCount() {
    return 3;
  }

  /**
   *  Gets a bin number or occurrences for bin number for rendering in a JTable.
   *
   * @param  row     the row whose value is to be queried
   * @param  column  the column whose value is to be queried
   * @return         the datum
   */
  public Object getValueAt(int row, int column) {
    updateEntries();
    Map.Entry<?, ?> entry = entries[row];
    if(column==0) {
      return entry.getKey();
    }
    if(column==1) {
      return new Double(((Integer) entry.getKey()).doubleValue()*binWidth+binWidth/2.0+binOffset);
    }
    if(normalizedToOne) {
      Double d = (Double) entry.getValue();
      return new Double(d.doubleValue()/sum);
    }
    return entry.getValue();
  }

  /**
   *  Gets the type of object for JTable entry.
   *
   * @param  columnIndex  the column whose value is to be queried
   * @return              the class
   */
  public Class<?> getColumnClass(int columnIndex) {
    return((columnIndex==0) ? Integer.class : Double.class);
  }

  /**
   * Sets the ID number of this Data.
   *
   * @param id the ID number
   */
  public void setID(int id) {
    datasetID = id;
  }

  /**
   * Returns a unique identifier for this Data.
   *
   * @return the ID number
   */
  public int getID() {
    return datasetID;
  }

  /**
   *  Draws a point at the top of a bin.
   *
   * @param  drawingPanel
   * @param  g
   * @param  binNumber
   * @param  occurrences
   */
  protected void drawPoint(DrawingPanel drawingPanel, Graphics g, int binNumber, double occurrences) {
    int px = drawingPanel.xToPix(getLeftMostBinPosition(binNumber)); // leftmost position of bin
    int py = drawingPanel.yToPix(occurrences);
    int pointRadius = 2;
    if(discrete) {
      g.fillRect(px-pointRadius, py-pointRadius, pointRadius*2, pointRadius*2);
    } else { // continous, draw entire bin
      int px2 = drawingPanel.xToPix(getRightMostBinPosition(binNumber));
      int pWidth = px2-px;
      g.fillRect(px, py, pWidth, pointRadius*2);
    }
  }

  /**
   *  Draws a filled bin.
   *
   * @param  drawingPanel
   * @param  g
   * @param  binNumber
   * @param  occurrences
   */
  protected void drawBin(DrawingPanel drawingPanel, Graphics g, int binNumber, double occurrences) {
    if(adjustForWidth) {
      occurrences = occurrences/getBinWidth();
    }
    if(logScale) {
      occurrences = Math.max(0, Math.log(occurrences));
    }
    int binlx = drawingPanel.xToPix(getLeftMostBinPosition(binNumber));
    if(discrete) {
      if(binEdgeColor!=null) {
        g.setColor(binEdgeColor);
        g.drawLine(binlx, drawingPanel.yToPix(YMIN), binlx, drawingPanel.yToPix(occurrences));
      }
    } else { // continous, draw entire bin
      int binrx = drawingPanel.xToPix(getRightMostBinPosition(binNumber));
      int pWidth = binrx-binlx;
      double pHeight = drawingPanel.getYPixPerUnit()*occurrences;
      java.awt.geom.Rectangle2D.Double rect = new java.awt.geom.Rectangle2D.Double(binlx, drawingPanel.yToPix(occurrences), pWidth, pHeight);
      Graphics2D g2 = (Graphics2D) g;
      if(binFillColor!=null) {
        g.setColor(binFillColor);
        g2.fill(rect);
      }
      if(binEdgeColor!=null) {
        g.setColor(binEdgeColor);
        g2.draw(rect);
      }
    }
  }

  /**
   *  Gets an array containing the bin centers.
   *
   * @return   the bins
   */
  public double[] getXPoints() {
    int nbins = 1+(int) ((xmax-xmin)/binWidth);
    if(nbins<1) {
      return new double[0];
    }
    double[] xdata = new double[nbins];
    for(int i = 0; i<nbins; i++) {
      xdata[i] = xmin+i*binWidth+binOffset+binWidth/2;
      //System.out.println("number="+i+"  x="+xdata[i]);
    }
    return xdata;
  }

  /**
   * Gets an array containing the values within the bins.
   *
   * @return    the values of the bins
   */
  public double[] getYPoints() {
    int nbins = 1+(int) ((xmax-xmin)/binWidth);
    if(nbins<1) {
      return new double[0];
    }
    double[] ydata = new double[nbins];
    for(int i = 0; i<nbins; i++) {
      Integer binNumber = new Integer(i);
      Double bin = bins.get(binNumber);
      ydata[i] = (bin==null) ? 0 : bin.doubleValue();
      //System.out.println("number"+binNumber.intValue()+"  x="+data[0][i]+ "  occurrences="+data[1][i]);
    }
    return ydata;
  }

  /**
   * Gets a data array containing both the bin centers and the values within the bins.
   *
   * @return a double[index][2] array of data
   */
  public double[][] getPoints() {
    int nbins = 1+(int) ((xmax-xmin)/binWidth);
    if(nbins<1) {
      return new double[2][0];
    }
    double[][] data = new double[2][nbins];
    int iStart = (int) (xmin/binWidth);
    for(int i = 0; i<nbins; i++) {
      Integer binNumber = new Integer(i+iStart);
      Double bin = bins.get(binNumber);
      data[0][i] = xmin+i*binWidth+binOffset+binWidth/2;
      data[1][i] = (bin==null) ? 0 : bin.doubleValue();
      // System.out.println("number"+binNumber.intValue()+"  x="+data[0][i]+ "  occurances="+data[1][i]);
    }
    return data;
  }

  /**
   * Gets a data array containing both the bin centers and the values within the bins.
   *
   * @return a double[index][2] array of data
   */
  public double[][] getLogPoints() {
    int nbins = (int) Math.round((xmax-xmin)/binWidth);
    if(nbins<1) {
      return new double[2][0];
    }
    double[][] data = new double[2][nbins];
    int iStart = (int) (xmin/binWidth);
    for(int i = 0; i<nbins; i++) {
      Integer binNumber = new Integer(i+iStart);
      Double bin = bins.get(binNumber);
      data[0][i] = xmin+i*binWidth+binOffset+binWidth/2;
      data[1][i] = (bin==null) ? 0 : bin.doubleValue();
      data[1][i] = (data[1][i]>0) ? Math.log(data[1][i]) : 0;
    }
    return data;
  }

  /**
   * Method getLeftMostBinPosition
   *
   * @param binNumber
   *
   * @return position
   */
  public double getLeftMostBinPosition(int binNumber) {
    return binNumber*binWidth+binOffset+binWidth*barOffset;
  }

  /**
   * Method getRightMostBinPosition
   *
   * @param binNumber
   *
   * @return position
   */
  public double getRightMostBinPosition(int binNumber) {
    return binNumber*binWidth+binWidth+binOffset+binWidth*barOffset;
  }

  /**
   *  Updates the bin number-occurrences array if data has changed since the last
   *  update
   */
  private synchronized void updateEntries() {
    if(dataChanged) {
      entries = bins.entrySet().toArray(entries);
      dataChanged = false;
    }
  }

  public double[][] getData2D() {
    double[][] data = (logScale) ? getLogPoints() : getPoints();
    return data;
  }

  public double[][][] getData3D() {
    return null;
  }

  public ArrayList<Dataset> getDatasets() {
    double[][] data = (logScale) ? getLogPoints() : getPoints();
    double[] bins = data[0];
    double[] vals = data[1];
    //double start=bins[0]-getBinWidth()/2;
    //double stop=bins[bins.length-1]+getBinWidth()/2;
    double start = 0;
    double stop = getBinWidth();
    if((bins!=null)&&(bins.length>0)) {
      start = bins[0]-getBinWidth()/2;
      stop = bins[bins.length-1]+getBinWidth()/2;
    }
    if(histogramDataset==null) {
      histogramDataset = new HistogramDataset(start, stop, getBinWidth());
    } else {
      histogramDataset.clear();
      histogramDataset.setBinWidth(start, stop, getBinWidth());
    }
    histogramDataset.setXYColumnNames(xColumnName, yColumnName, name);
    histogramDataset.append(bins, vals);
    histogramDataset.setMarkerColor(binFillColor, binEdgeColor);
    ArrayList<Dataset> list = new ArrayList<Dataset>();
    list.add(histogramDataset);
    return list;
  }

  /**
* Returns the XML.ObjectLoader for this class.
*
* @return the object loader
*/
  public static XML.ObjectLoader getLoader() {
    return new HistogramLoader();
  }

  /**
   * A class to save and load Dataset data in an XMLControl.
   */
  protected static class HistogramLoader extends XMLLoader {
    public void saveObject(XMLControl control, Object obj) {
      Histogram his = (Histogram) obj;
      double[][] points = (his.logScale) ? his.getLogPoints() : his.getPoints();
      double[] bins = points[0];
      double[] vals = points[1];
      control.setValue("log_scale", his.logScale);              //$NON-NLS-1$
      control.setValue("discrete", his.discrete);               //$NON-NLS-1$
      control.setValue("adjust_for_width", his.adjustForWidth); //$NON-NLS-1$
      control.setValue("bin_fill_color", his.binFillColor);     //$NON-NLS-1$
      control.setValue("bin_edge_color", his.binEdgeColor);     //$NON-NLS-1$
      control.setValue("bin_style", his.binStyle);              //$NON-NLS-1$
      control.setValue("bin_width", his.binWidth);              //$NON-NLS-1$
      control.setValue("bin_offset", his.binOffset);            //$NON-NLS-1$
      control.setValue("name", his.name);                       //$NON-NLS-1$
      control.setValue("x_column_name", his.xColumnName);       //$NON-NLS-1$
      control.setValue("y_column_name", his.yColumnName);       //$NON-NLS-1$
      control.setValue("bin_column_name", his.binColumnName);   //$NON-NLS-1$
      control.setValue("bins", bins);                           //$NON-NLS-1$
      control.setValue("vals", vals);                           //$NON-NLS-1$
    }

    public Object createObject(XMLControl control) {
      return new Histogram();
    }

    public Object loadObject(XMLControl control, Object obj) {
      Histogram his = (Histogram) obj;
      double[] bins = (double[]) control.getObject("bins"); //$NON-NLS-1$
      double[] vals = (double[]) control.getObject("vals"); //$NON-NLS-1$
      his.name = control.getString("name");                           //$NON-NLS-1$
      his.xColumnName = control.getString("x_column_name");           //$NON-NLS-1$
      his.yColumnName = control.getString("y_column_name");           //$NON-NLS-1$
      his.binColumnName = control.getString("bin_column_name");       //$NON-NLS-1$
      his.logScale = control.getBoolean("log_scale");                 //$NON-NLS-1$
      his.discrete = control.getBoolean("discrete");                  //$NON-NLS-1$
      his.adjustForWidth = control.getBoolean("adjust_for_width");    //$NON-NLS-1$
      his.binFillColor = (Color) control.getObject("bin_fill_color"); //$NON-NLS-1$
      his.binEdgeColor = (Color) control.getObject("bin_edge_color"); //$NON-NLS-1$
      his.binStyle = control.getInt("bin_style");                     //$NON-NLS-1$
      his.binWidth = control.getDouble("bin_width");                  //$NON-NLS-1$
      his.binOffset = control.getDouble("bin_offset");                //$NON-NLS-1$
      his.adjustForWidth = control.getBoolean("adjust_for_width");    //$NON-NLS-1$
      if((bins!=null)&&(vals!=null)) {
        for(int i = 0, n = bins.length; i<n; i++) {
          his.append(bins[i], vals[i]);
        }
      }
      return obj;
    }

  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
