ingo@617: package de.intevation.gnv.chart; ingo@617: ingo@629: import java.text.NumberFormat; ingo@629: import java.text.ParseException; 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 Ingo Weinzierl 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 Locale 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: /** ingo@767: * 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 Integer at ingo@767: * REQUEST_KEY_BIN_COUNT and this is smaller than or equal ingo@767: * {@link #MAXIMAL_BINS}, this value is used. If no valid ingo@767: * Integer 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 :