package umontreal.iro.lecuyer.charts;

import java.util.*;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.statistics.HistogramBin;
import org.jfree.data.statistics.HistogramType;
import org.jfree.data.xy.AbstractIntervalXYDataset;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PublicCloneable;


/**
 * A dataset that can be used for creating histograms.
 * This class is inspired from JFreeChart HistogramDataset class,
 * and provides tools to customize histogram bins.
 *
 */
public class CustomHistogramDataset extends AbstractIntervalXYDataset
         implements IntervalXYDataset, Cloneable, PublicCloneable
{

   /** A list of maps. */
   private List list;

   /** The histogram type. */
   public HistogramType type;

   /**
    * Creates a new (empty) dataset with a default type of
    * {@link HistogramType}.FREQUENCY.
    */
   public CustomHistogramDataset()
   {
      list = new ArrayList();
      type = HistogramType.FREQUENCY;
   }

   /**
    * Returns the histogram type.
    */
   public HistogramType getType()
   {
      return type;
   }

   /**
    * Sets the histogram type and sends a {@link DatasetChangeEvent} to all
    * registered listeners.
    *
    * @param type  the type (null not permitted).
    */
   public void setType(HistogramType type)
   {
      if (type == null) {
         throw new IllegalArgumentException("Null 'type' argument");
      } else {
         this.type = type;
         notifyListeners(new DatasetChangeEvent(this, this));
         return ;
      }
   }

   /**
    * Adds a series to the dataset, using the specified number of bins.
    *
    * @param key  the series key (null not permitted).
    * @param values the values (null not permitted).
    * @param bins  the number of bins (must be at least 1).
    */
   public void addSeries(Comparable key, double values[], int bins)
   {
      double minimum = getMinimum(values);
      double maximum = getMaximum(values);
      addSeries(key, values, bins, minimum, maximum);
   }

   /**
    * Adds a series to the dataset, using the specified number of bins.
    *
    * @param key  the series key (null not permitted).
    * @param values the values (null not permitted).
    * @param numPoints  only the first numPoints values are used.
    * @param bins  the number of bins (must be at least 1).
    */
   public void addSeries(Comparable key, double values[], int numPoints, int bins)
   {
      double minimum = getMinimum(values);
      double maximum = getMaximum(values);
      addSeries(key, values, numPoints, bins, minimum, maximum);
   }

   /**
    * Adds a series to the dataset. Any data value less than minimum will be
    * assigned to the first bin, and any data value greater than maximum will
    * be assigned to the last bin.  Values falling on the boundary of
    * adjacent bins will be assigned to the higher indexed bin.
    *
    * @param key  the series key (null not permitted).
    * @param values  the raw observations.
    * @param bins  the number of bins (must be at least 1).
    * @param minimum  the lower bound of the bin range.
    * @param maximum  the upper bound of the bin range.
    */
   public void addSeries(Comparable key, double values[], int bins,
                         double minimum, double maximum)
   {
      addSeries(key, values, values.length, bins, minimum, maximum);
   }

   /**
    * Adds a series to the dataset. Any data value less than minimum will be
    * assigned to the first bin, and any data value greater than maximum will
    * be assigned to the last bin.  Values falling on the boundary of
    * adjacent bins will be assigned to the higher indexed bin.
    * Only the first numPoints values are used.
    *
    * @param key  the series key (null not permitted).
    * @param values  the raw observations.
    * @param numPoints  only the first numPoints values are used.
    * @param bins  the number of bins (must be at least 1).
    * @param minimum  the lower bound of the bin range.
    * @param maximum  the upper bound of the bin range.
    */
   public void addSeries(Comparable key, double values[], int numPoints,
                         int bins, double minimum, double maximum)
   {
      if (key == null)
         throw new IllegalArgumentException("Null 'key' argument.");
      if (values == null)
         throw new IllegalArgumentException("Null 'values' argument.");
      if (bins < 1)
         throw new IllegalArgumentException("The 'bins' value must be at least 1.");
      double binWidth = (maximum - minimum) / (double)bins;
      double lower = minimum;
      final double EPS = 1.0e-15;
      List binList = new ArrayList(bins);
      for (int i = 0; i < bins; i++) {
         HistogramBin bin;
         if (i == bins - 1) {
            bin = new HistogramBin(lower, maximum*(1.0 + EPS));
         } else {
            double upper = minimum + (double)(i + 1) * binWidth;
            bin = new HistogramBin(lower, upper);
            lower = upper;
         }
         binList.add(bin);
      }

      ArrayList valuesList = new ArrayList(numPoints);
      for (int i = 0; i < numPoints; i++)
         valuesList.add(new Double(values[i]));

      synchronizeValuesAndBins(binList, valuesList);
      Map map = new HashMap();
      map.put("key", key);
      map.put("values", valuesList);
      map.put("bins", binList);
      map.put("numPoints", new Integer(numPoints));
      map.put("bin width", new Double(binWidth));
      list.add(map);
   }

   /**
    * Adds a series to the dataset. Values falling on the boundary of
    * adjacent bins will be assigned to the higher indexed bin.
    *
    * @param key  the series key (null not permitted).
    * @param values  the raw observations.
    * @param bins  new bins (size must be at least 1).
    */
   public void addSeries(Comparable key, double values[], HistogramBin bins[])
   {
      addSeries(key, values, values.length, bins);
   }

   /**
    * Adds a series to the dataset. Values falling on the boundary of
    * adjacent bins will be assigned to the higher indexed bin.
    *
    * @param key  the series key (null not permitted).
    * @param values  the raw observations.
    * @param numPoints  only the first numPoints values are used.
    * @param bins  new bins (size must be at least 1).
    */
   public void addSeries(Comparable key, double values[], int numPoints,
                         HistogramBin bins[])
   {
      if (key == null)
         throw new IllegalArgumentException("Null 'key' argument.");
      if (values == null)
         throw new IllegalArgumentException("Null 'values' argument.");
      if (bins == null || bins.length < 2)
         throw new IllegalArgumentException
             ("The 'bins' table must contain at least 1 org.jfree.data.statistics.HistogramBin.");
      List binList = new ArrayList(bins.length);
      for (int i = 0; i < bins.length; i++)
         binList.add(bins[i]);

      ArrayList valuesList = new ArrayList(numPoints);
      for (int i = 0; i < numPoints; i++)
         valuesList.add(new Double(values[i]));

      synchronizeValuesAndBins(binList, valuesList);
      Map map = new HashMap();
      map.put("key", key);
      map.put("values", valuesList);
      map.put("bins", binList);
      map.put("numPoints", new Integer(numPoints));
      map.put("bin width", new Double( -1D));
      list.add(map);
   }

   /**
    * Returns the minimum value in an array of values.
    *
    * @param values  the values (null not permitted and
    *                zero-length array not permitted).
    *
    * @return The minimum value.
    */
   private double getMinimum(double values[])
   {
      if (values == null || values.length < 1)
         throw new IllegalArgumentException
            ("Null or zero length 'values' argument.");
      double min = 1.7E+308D;
      for (int i = 0; i < values.length; i++)
         if (values[i] < min)
            min = values[i];

      return min;
   }

   /**
    * Returns the maximum value in an array of values.
    *
    * @param values  the values (null not permitted and
    *                zero-length array not permitted).
    *
    * @return The maximum value.
    */
   private double getMaximum(double values[])
   {
      if (values == null || values.length < 1)
         throw new IllegalArgumentException
            ("Null or zero length 'values' argument.");
      double max = -1.7E+308D;
      for (int i = 0; i < values.length; i++)
         if (values[i] > max)
            max = values[i];

      return max;
   }

   /**
    * Returns the bins for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    *
    * @return A list of bins.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public List getBins(int series)
   {
      Map map = (Map)list.get(series);
      return (List)map.get("bins");
   }

   /**
    * Sets the bins for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param bins  the number of bins (must be at least 1).
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public void setBins(int series, int bins)
   {
      double minimum = getMinimum(getValues(series));
      double maximum = getMaximum(getValues(series));
      setBins(series, bins, minimum, maximum);
   }

   /**
    * Sets the bins for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param bins  the number of bins (must be at least 1).
    * @param minimum  the lower bound of the bin range.
    * @param maximum  the upper bound of the bin range.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public void setBins(int series, int bins, double minimum, double maximum)
   {
      Map map = (Map)list.get(series);
      List currentValues = (List)map.get("values");
      double binWidth = (maximum - minimum) / (double)bins;
      double lower = minimum;
      List binList = new ArrayList(bins);
      double EPS = 1.0e-15;
      for (int i = 0; i < bins; i++) {
         HistogramBin bin;
         if (i == bins - 1) {
            bin = new HistogramBin(lower, maximum*(1.0 + EPS));
         } else {
            double upper = minimum + (double)(i + 1) * binWidth;
            bin = new HistogramBin(lower, upper);
            lower = upper;
         }
         binList.add(bin);
      }

      synchronizeValuesAndBins(binList, currentValues);
      map.put("values", currentValues);
      map.put("bins", binList);
   }

   /**
    * Sets the bins for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param bins  the number of bins (must be at least 1).
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public void setBins(int series, HistogramBin bins[])
   {
      Map map = (Map)list.get(series);
      List currentValues = (List)map.get("values");
      ArrayList binList = new ArrayList(bins.length);
      for (int i = 0; i < bins.length; i++)
         binList.add(bins[i]);

      synchronizeValuesAndBins(binList, currentValues);
      map.put("values", currentValues);
      map.put("bins", binList);
   }

   /**
    * Returns the values for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    *
    * @return A list of values.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public List getValuesList(int series)
   {
      Map map = (Map)list.get(series);
      return (List)map.get("values");
   }

   /**
    * Returns the values for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    *
    * @return A table of values.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public double[] getValues(int series)
   {
      List valuesList = (List)((Map)list.get(series)).get("values");
      ListIterator iter = valuesList.listIterator();
      double retour[] = new double[valuesList.size()];
      for (int i = 0; iter.hasNext(); i++)
         retour[i] = ((Double)iter.next()).doubleValue();

      return retour;
   }

   /**
    * Sets the values for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param valuesList  List of new values.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public void setValues(int series, List valuesList)
   {
      Map map = (Map)list.get(series);
      List currentBins = (List)map.get("bins");
      synchronizeValuesAndBins(currentBins, valuesList);
      map.put("values", valuesList);
      map.put("bins", currentBins);
   }

   /**
    * Sets the values for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param values  Table of new values.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public void setValues(int series, double values[])
   {
      ArrayList valuesList = new ArrayList(values.length);
      for (int i = 0; i < values.length; i++)
         valuesList.add(new Double(values[i]));

      setValues(series, ((List) (valuesList)));
   }

   /**
    * Synchronize values to bins. Compute bins values.
    *
    * @param bins   List of bins.
    * @param values List of values.
    */
   private void synchronizeValuesAndBins(List bins, List values)
   {
      ListIterator iterBins = bins.listIterator(0);
      ListIterator iterValues = values.listIterator();
      HistogramBin bin;
      for (; iterBins.hasNext(); iterBins.set(
            new HistogramBin(bin.getStartBoundary(), bin.getEndBoundary())))
         bin = (HistogramBin)iterBins.next();

      iterBins = bins.listIterator(0);
      while (iterValues.hasNext()) {
         double currentValue = ((Double)iterValues.next()).doubleValue();
         boolean continu = true;
         iterBins = bins.listIterator(0);
         while (continu && iterBins.hasNext()) {
            HistogramBin tempBin = (HistogramBin)iterBins.next();
            if (currentValue >= tempBin.getStartBoundary() &&
                currentValue <  tempBin.getEndBoundary()) {
               tempBin.incrementCount();
               continu = false;
            }
         }
      }
   }

   /**
    * Returns the total number of observations for a series.
    *
    * @param series  the series index.
    *
    * @return The total.
    */
   public int getTotal(int series)
   {
      Map map = (Map)list.get(series);
      return ((Integer)map.get("numPoints")).intValue();
   }

   /**
    * Returns the bin width for a series.
    *
    * @param series  the series index (zero based).
    *
    * @return The bin width.
    */
   public double getBinWidth(int series)
   {
      Map map = (Map)list.get(series);
      return ((Double)map.get("bin width")).doubleValue();
   }

   /**
    * Returns the number of series in the dataset.
    *
    * @return The series count.
    */
   public int getSeriesCount()
   {
      return list.size();
   }

   /**
    * Returns the key for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    *
    * @return The series key.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Comparable getSeriesKey(int series)
   {
      Map map = (Map)list.get(series);
      return (Comparable)map.get("key");
   }

   /**
    * Returns the number of data items for a series.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    *
    * @return The item count.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public int getItemCount(int series)
   {
      return getBins(series).size();
   }

   /**
    * Returns the X value for a bin.  This value won't be used for plotting
    * histograms, since the renderer will ignore it.  But other renderers can
    * use it (for example, you could use the dataset to create a line
    * chart).
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param item  the item index (zero based).
    *
    * @return The start value.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Number getX(int series, int item)
   {
      List bins = getBins(series);
      HistogramBin bin = (HistogramBin)bins.get(item);
      double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2D;
      return new Double(x);
   }

   /**
    * Returns the y-value for a bin (calculated to take into account the
    * histogram type).
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param item  the item index (zero based).
    *
    * @return The y-value.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Number getY(int series, int item)
   {
      List bins = getBins(series);
      HistogramBin bin = (HistogramBin)bins.get(item);
      double total = getTotal(series);
      double binWidth = getBinWidth(series);
      if (type == HistogramType.FREQUENCY)
         return new Double(bin.getCount());
      if (type == HistogramType.RELATIVE_FREQUENCY)
         return new Double((double)bin.getCount() / total);
      if (type == HistogramType.SCALE_AREA_TO_1)
         return new Double((double)bin.getCount() / (binWidth * total));
      else
         throw new IllegalStateException();
   }

   /**
    * Returns the start value for a bin.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param item  the item index (zero based).
    *
    * @return The start value.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Number getStartX(int series, int item)
   {
      List bins = getBins(series);
      HistogramBin bin = (HistogramBin)bins.get(item);
      return new Double(bin.getStartBoundary());
   }

   /**
    * Returns the end value for a bin.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param item  the item index (zero based).
    *
    * @return The end value.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Number getEndX(int series, int item)
   {
      List bins = getBins(series);
      HistogramBin bin = (HistogramBin)bins.get(item);
      return new Double(bin.getEndBoundary());
   }

   /**
    * Returns the start y-value for a bin (which is the same as the y-value).
    * This method exists only to support the general form of the
    * {IntervalXYDataset} interface.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param item  the item index (zero based).
    *
    * @return The y-value.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Number getStartY(int series, int item)
   {
      return getY(series, item);
   }

   /**
    * Returns the end y-value for a bin (which is the same as the y-value).
    * This method exists only to support the general form of the
    * IntervalXYDataset} interface.
    *
    * @param series  the series index (in the range 0 to
    *     getSeriesCount() - 1).
    * @param item  the item index (zero based).
    *
    * @return The Y value.
    *
    * @throws IndexOutOfBoundsException if series is outside the
    *     specified range.
    */
   public Number getEndY(int series, int item)
   {
      return getY(series, item);
   }

   /**
    * Tests this dataset for equality with an arbitrary object.
    *
    * @param obj  the object to test against (null permitted).
    *
    * @return A boolean.
    */
   public boolean equals(Object obj)
   {
      if (obj == this)
         return true;
      if (!(obj instanceof CustomHistogramDataset))
         return false;
      CustomHistogramDataset that = (CustomHistogramDataset)obj;
      if (!ObjectUtilities.equal(type, that.type))
         return false;
      return ObjectUtilities.equal(list, that.list);
   }

   /**
    * Returns a clone of the dataset.
    *
    * @return A clone of the dataset.
    *
    * @throws CloneNotSupportedException if the object cannot be cloned.
    */
   public Object clone()
   throws CloneNotSupportedException
   {
      return super.clone();
   }
}

 

Ads help maintain this website.