ingo@617: package de.intevation.gnv.chart;
ingo@617: 
ingo@629: import java.text.NumberFormat;
ingo@629: import java.text.ParseException;
sascha@779: 
ingo@629: import java.util.Locale;
ingo@629: import java.util.Map;
ingo@629: 
ingo@617: import org.apache.log4j.Logger;
ingo@617: 
ingo@617: import org.jfree.chart.ChartTheme;
sascha@623: 
ingo@617: import org.jfree.chart.plot.XYPlot;
ingo@617: 
ingo@617: import org.jfree.data.statistics.HistogramDataset;
ingo@617: 
ingo@617: /**
ingo@767:  * Default implementation of {@link de.intevation.gnv.chart.AbstractHistogram}.
ingo@767:  *
ingo@767:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@617:  */
ingo@617: public class DefaultHistogram
ingo@617: extends      AbstractHistogram
ingo@617: {
ingo@767:     /**
ingo@767:      * Default bin count.
ingo@767:      * TODO find a better default value
ingo@767:      */
ingo@617:     public static final int DEFAULT_BINS = 15;
ingo@767: 
ingo@767:     /**
ingo@767:      * Constant field for limitating the number of bin in a single histogram.
ingo@767:      */
ingo@629:     public static final int MAXIMAL_BINS = 20;
ingo@767: 
ingo@767:     /**
ingo@767:      * Default key to retrieve the number of bins from {@link
ingo@767:      * #requestParameter}.
ingo@767:      */
ingo@629:     public static final String REQUEST_KEY_BIN_COUNT   = "bincount";
ingo@767: 
ingo@767:     /**
ingo@767:      * Default key to retrieve the width of a single bin from {@link
ingo@767:      * #requestParameter}.
ingo@767:      */
ingo@629:     public static final String REQUEST_KEY_BIN_WIDTH   = "binwidth";
ingo@767: 
ingo@767:     /**
ingo@767:      * Default key to retrieve the chart width from {@link #requestParameter}.
ingo@767:      */
ingo@629:     public static final String REQUEST_KEY_CHART_WIDTH = "width";
ingo@767: 
ingo@767:     /**
ingo@767:      * Default key to retrieve the <code>Locale</code> object from {@link
ingo@767:      * #requestParameter} used for i18n support.
ingo@767:      */
ingo@629:     public static final String REQUEST_KEY_LOCALE      = "locale";
ingo@767: 
ingo@767:     /**
sascha@778:      * Default key to retrieve the object from {@link #requestParameter}. It
ingo@767:      * defines which value this chart has to be used for bin calculation. You
ingo@767:      * can either adjust the number of bins or the width of a single bin.
ingo@767:      */
ingo@629:     public static final String REQUEST_KEY_BIN_CHOICE  = "bintype";
ingo@617: 
ingo@767:     /**
ingo@767:      * Logger used for logging with log4j.
ingo@767:      */
ingo@617:     private static Logger logger = Logger.getLogger(DefaultHistogram.class);
ingo@617: 
ingo@767:     /**
ingo@767:      * Object storing some further parameter used for chart settings.
ingo@767:      */
ingo@629:     protected Map requestParameter;
ingo@629: 
ingo@617: 
ingo@767:     /**
ingo@767:      * Constructor to create DefaultHistogram objects.
ingo@767:      *
ingo@767:      * @param labels Labels to decorate this chart.
ingo@767:      * @param data Raw data to be displayed in histogram.
ingo@767:      * @param theme Theme used to adjust the chart look.
ingo@767:      * @param requestParameter Object which serves some further settings.
ingo@767:      */
ingo@617:     public DefaultHistogram(
ingo@629:         ChartLabels labels, Object[] data, ChartTheme theme, Map requestParameter
ingo@617:     ) {
ingo@617:         super(labels, data, theme);
ingo@629:         this.requestParameter = requestParameter;
ingo@617:     }
ingo@617: 
ingo@617: 
ingo@767:     /**
ingo@767:      * @see de.intevation.gnv.chart.AbstractHistogram#applyDatasets()
ingo@767:      */
ingo@767:     @Override
ingo@617:     protected void applyDatasets() {
ingo@617:         XYPlot plot = (XYPlot) chart.getPlot();
ingo@617: 
ingo@617:         // prepare data and create add them to histogram dataset
ingo@617:         String   name   = (String)   data[0];
ingo@617:         double[] values = toDouble((Double[]) data[1]);
ingo@629:         int      bins   = getBinCount(values);
ingo@617: 
ingo@617:         HistogramDataset dataset = new HistogramDataset();
ingo@629:         dataset.addSeries(name, values, bins);
ingo@617: 
ingo@617:         plot.setDataset(0, dataset);
ingo@617:     }
ingo@617: 
ingo@617: 
ingo@767:     /**
ingo@767:      * Method which scans the hole bunch of values and returns an array with
ingo@767:      * contains min and max value. Min value is stored at position 0, max value
ingo@767:      * is stored at position 1 in that array.
ingo@767:      *
ingo@767:      * @param values Array which contains all values
ingo@767:      *
ingo@767:      * @return Array which contains min and max value
ingo@767:      */
ingo@629:     protected double[] getMinMax(double[] values) {
ingo@629:         double[] minmax = new double[2];
ingo@629:         minmax[0] = Double.MAX_VALUE;
ingo@629:         minmax[1] = Double.MIN_VALUE;
ingo@629: 
ingo@629:         int length = values.length;
ingo@629:         for (int i = 0; i < length; i++) {
ingo@629:             minmax[0] = values[i] < minmax[0] ? values[i] : minmax[0];
ingo@629:             minmax[1] = values[i] > minmax[1] ? values[i] : minmax[1];
ingo@629:         }
ingo@629: 
ingo@629:         return minmax;
ingo@629:     }
ingo@629: 
ingo@629: 
ingo@767:     /**
ingo@767:      * Turn a Double[] into a double[].
ingo@767:      *
ingo@767:      * @param array Doube[]
ingo@767:      *
ingo@767:      * @return double[]
ingo@767:      */
ingo@617:     protected double[] toDouble(Double[] array) {
ingo@617:         int length      = array.length;
ingo@617:         double[] values = new double[length];
ingo@617: 
ingo@617:         for(int i = 0; i < length; i++) {
ingo@617:             values[i] = array[i].doubleValue();
ingo@617:         }
ingo@617: 
ingo@617:         return values;
ingo@617:     }
ingo@629: 
ingo@629: 
ingo@767:     /**
ingo@767:      * Method to calculate the number of bins this chart should be parted into.
ingo@767:      * The real calculation takes place in {@link #getBinCountByNumber} and
ingo@767:      * {@link #getBinCountByWidth}. This method switches between these methods
ingo@767:      * depending on the object stored in {@link #requestParameter}.
ingo@767:      *
ingo@767:      * @param values All values used in this histogram
ingo@767:      *
ingo@767:      * @return Number of bins
ingo@767:      */
ingo@629:     protected int getBinCount(double[] values) {
ingo@629:         String param = (String) requestParameter.get(REQUEST_KEY_BIN_CHOICE);
ingo@629: 
ingo@629:         if (param != null && param.equalsIgnoreCase(REQUEST_KEY_BIN_WIDTH)) {
ingo@629:             return getBinCountByWidth(values);
ingo@629:         }
ingo@629:         else {
ingo@629:             return getBinCountByNumber();
ingo@629:         }
ingo@629:     }
ingo@629: 
ingo@629: 
ingo@767:     /**
ingo@767:      * Method to retrieve the number of bins. If {@link #requestParameter}
ingo@767:      * contains a valid <code>Integer</code> at
sascha@778:      * <code>REQUEST_KEY_BIN_COUNT</code> and this is smaller than or equal
ingo@767:      * {@link #MAXIMAL_BINS}, this value is used. If no valid
ingo@767:      * <code>Integer</code> is given or if the value in {@link #requestParameter}
ingo@767:      * is bigger than {@link #MAXIMAL_BINS}, {@link #DEFAULT_BINS} is used.
ingo@767:      *
ingo@767:      * @return Number of bins
ingo@767:      */
ingo@629:     protected int getBinCountByNumber() {
ingo@629:         int    bins  = -1;
ingo@629:         String param = (String) requestParameter.get(REQUEST_KEY_BIN_COUNT);
ingo@629: 
ingo@629:         try {
ingo@629:             bins = Integer.parseInt(param);
ingo@629:             bins = bins <= 0 ? DEFAULT_BINS : bins;
ingo@629:             bins = bins >  MAXIMAL_BINS ? MAXIMAL_BINS : bins;
ingo@629: 
ingo@629:             return bins;
ingo@629:         }
ingo@629:         catch (NumberFormatException nfe) {
ingo@629:             logger.warn("Invalid number of bins for histogram chart: " + param);
ingo@629:             logger.warn("Return default bins: " + DEFAULT_BINS);
ingo@629: 
ingo@629:             return DEFAULT_BINS;
ingo@629:         }
ingo@629:     }
ingo@629: 
ingo@629: 
ingo@767:     /**
ingo@767:      * Serves the number of bins depending on a given width for each bin, but
ingo@767:      * maximum bin count is limited by {@link MAXIMAL_BINS}.
ingo@767:      *
ingo@767:      * @param values All values in this histogram
ingo@767:      *
ingo@767:      * @return Number of bins
ingo@767:      */
ingo@629:     protected int getBinCountByWidth(double[] values) {
ingo@629:         int    bins   = -1;
ingo@629:         String param  = (String) requestParameter.get(REQUEST_KEY_BIN_WIDTH);
ingo@629:         Locale locale = (Locale) requestParameter.get(REQUEST_KEY_LOCALE);
ingo@629:         NumberFormat format = NumberFormat.getInstance(locale);
ingo@629: 
ingo@629:         try {
ingo@629:             double[] minmax     = getMinMax(values);
ingo@629:             double   totalWidth = minmax[1] - minmax[0];
ingo@629:             Number   number     = format.parse(param);
ingo@629:             double   binWidth   = -1d;
ingo@629: 
ingo@629:             if (number instanceof Double) {
ingo@629:                 binWidth = ((Double) number).doubleValue();
ingo@629:             }
ingo@629:             else if (number instanceof Long) {
ingo@629:                 binWidth = ((Long) number).doubleValue();
ingo@629:             }
ingo@629:             else if (number instanceof Integer) {
ingo@629:                 binWidth = ((Integer) number).doubleValue();
ingo@629:             }
ingo@629:             else {
ingo@629:                 logger.warn("Invalid bin width for histogram chart: " + param);
ingo@629:                 logger.warn("Return default bins: " + DEFAULT_BINS);
ingo@629: 
ingo@629:                 return DEFAULT_BINS;
ingo@629:             }
ingo@629: 
ingo@629:             double tmpBins = totalWidth / binWidth;
ingo@629: 
ingo@629:             bins = (int) Math.round(tmpBins);
ingo@629:             bins = bins <= 0 ? DEFAULT_BINS : bins;
ingo@629:             bins = bins >  MAXIMAL_BINS ? MAXIMAL_BINS : bins;
ingo@629: 
ingo@629:             return bins;
ingo@629:         }
ingo@629:         catch (ParseException pe) {
ingo@629:             logger.warn("Invalid bin width for histogram chart: " + param);
ingo@629:             logger.warn("Return default bins: " + DEFAULT_BINS);
ingo@629: 
ingo@629:             return DEFAULT_BINS;
ingo@629:         }
ingo@629:         catch (NumberFormatException nfe) {
ingo@629:             logger.warn("Invalid bin width for histogram chart: " + param);
ingo@629:             logger.warn("Return default bins: " + DEFAULT_BINS);
ingo@629: 
ingo@629:             return DEFAULT_BINS;
ingo@629:         }
ingo@629:     }
ingo@617: }
ingo@617: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :