org.jfree.data.DatasetUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.jfree.data.DatasetUtilities.java

Source

/*
 * ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.
 * Project Info: http://www.jfree.org/jfreechart/index.html
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 * This library 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 Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 * ---------------------
 * DatasetUtilities.java
 * ---------------------
 * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.
 * Original Author: David Gilbert (for Object Refinery Limited);
 * Contributor(s): Andrzej Porebski (bug fix);
 * Jonathan Nash (bug fix);
 * Richard Atkinson;
 * Andreas Schroeder (beatification)
 * $Id: DatasetUtilities.java,v 1.1 2011-01-31 09:02:14 klukas Exp $
 * Changes (from 18-Sep-2001)
 * --------------------------
 * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
 * 15-Nov-2001 : Moved to package com.jrefinery.data.* in the JCommon class library (DG);
 * Changed to handle null values from datasets (DG);
 * Bug fix (thanks to Andrzej Porebski) - initial value now set to positive or
 * negative infinity when iterating (DG);
 * 22-Nov-2001 : Datasets with containing no data now return null for min and max calculations (DG);
 * 13-Dec-2001 : Extended to handle HighLowDataset and IntervalXYDataset (DG);
 * 15-Feb-2002 : Added getMinimumStackedRangeValue() and getMaximumStackedRangeValue() (DG);
 * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
 * 18-Mar-2002 : Fixed bug in min/max domain calculation for datasets that implement the
 * CategoryDataset interface AND the XYDataset interface at the same time. Thanks
 * to Jonathan Nash for the fix (DG);
 * 23-Apr-2002 : Added getDomainExtent() and getRangeExtent() methods (DG);
 * 13-Jun-2002 : Modified range measurements to handle IntervalCategoryDataset (DG);
 * 12-Jul-2002 : Method name change in DomainInfo interface (DG);
 * 30-Jul-2002 : Added pie dataset summation method (DG);
 * 01-Oct-2002 : Added a method for constructing an XYDataset from a Function2D instance (DG);
 * 24-Oct-2002 : Amendments required following changes to the CategoryDataset interface (DG);
 * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
 * 04-Mar-2003 : Added isEmpty(XYDataset) method (DG);
 * 05-Mar-2003 : Added a method for creating a CategoryDataset from a KeyedValues instance (DG);
 * 15-May-2003 : Renamed isEmpty --> isEmptyOrNull (DG);
 * 25-Jun-2003 : Added limitPieDataset methods (RA);
 * 26-Jun-2003 : Modified getDomainExtent(...) method to accept null datasets (DG);
 * 27-Jul-2003 : Added getStackedRangeExtent(TableXYDataset data) (RA);
 * 18-Aug-2003 : getStackedRangeExtent(TableXYDataset data) now handles null values (RA);
 * 02-Sep-2003 : Added method to check for null or empty PieDataset (DG);
 * 18-Sep-2003 : Fix for bug 803660 (getMaximumRangeValue for CategoryDataset) (DG);
 * 20-Oct-2003 : Added getCumulativeRangeExtent(...) method (DG);
 * 09-Jan-2003 : Added argument checking code to the createCategoryDataset(...) method (DG);
 * 23-Mar-2004 : Fixed bug in getMaximumStackedRangeValue() method (DG);
 * 31-Mar-2004 : Exposed the extent iteration algorithms to use one of them and applied
 * noninstantiation pattern (AS);
 * 11-May-2004 : Renamed getPieDatasetTotal --> calculatePieDatasetTotal (DG);
 */

package org.jfree.data;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * A collection of useful static methods relating to datasets.
 */
public final class DatasetUtilities {

    /**
     * Private constructor for non-instanceability.
     */
    private DatasetUtilities() {
        // now try to instanciate this ;-)
    }

    /**
     * Constructs an array of <code>Number</code> objects from an array of <code>double</code> primitives.
     * 
     * @param data
     *           the data.
     * @return an array of <code>Double</code>.
     */
    public static Number[] createNumberArray(final double[] data) {

        final Number[] result = new Number[data.length];

        for (int i = 0; i < data.length; i++) {
            result[i] = new Double(data[i]);
        }

        return result;

    }

    /**
     * Constructs an array of arrays of <code>Number</code> objects from a corresponding
     * structure containing <code>double</code> primitives.
     * 
     * @param data
     *           the data.
     * @return an array of <code>Double</code>.
     */
    public static Number[][] createNumberArray2D(final double[][] data) {

        final int l1 = data.length;
        final int l2 = data[0].length;

        final Number[][] result = new Number[l1][l2];

        for (int i = 0; i < l1; i++) {
            result[i] = createNumberArray(data[i]);
        }

        return result;

    }

    /**
     * Returns the range of values in the domain for the dataset.
     * <P>
     * If the supplied dataset is <code>null</code>, the range returned is <code>null</code>.
     * 
     * @param dataset
     *           the dataset (<code>null</code> permitted).
     * @return The range of values (possibly <code>null</code>).
     */
    public static Range getDomainExtent(final Dataset dataset) {

        // check parameters...
        if (dataset == null) {
            return null;
        }

        if ((dataset instanceof CategoryDataset) && !(dataset instanceof XYDataset)) {
            throw new IllegalArgumentException("The dataset does not have a numerical domain.");
        }

        // work out the minimum value...
        if (dataset instanceof DomainInfo) {
            final DomainInfo info = (DomainInfo) dataset;
            return info.getDomainRange();
        }

        // hasn't implemented DomainInfo, so iterate...
        else if (dataset instanceof XYDataset) {
            return iterateDomainExtent((XYDataset) dataset);
        } else {
            return null; // unrecognised dataset...how should this be handled?
        }
    }

    /**
     * Iterates over the data item of the xy dataset to find
     * the domain extent.
     * 
     * @param data
     *           the xy dataset to iterate over.
     * @return the domain extent of the data within the dataset.
     */
    public static Range iterateDomainExtent(final XYDataset data) {
        double minimum = Double.POSITIVE_INFINITY;
        double maximum = Double.NEGATIVE_INFINITY;
        final int seriesCount = data.getSeriesCount();
        for (int series = 0; series < seriesCount; series++) {
            final int itemCount = data.getItemCount(series);
            for (int item = 0; item < itemCount; item++) {

                final Number lvalue;
                final Number uvalue;
                if (data instanceof IntervalXYDataset) {
                    final IntervalXYDataset intervalXYData = (IntervalXYDataset) data;
                    lvalue = intervalXYData.getStartXValue(series, item);
                    uvalue = intervalXYData.getEndXValue(series, item);
                } else {
                    lvalue = data.getXValue(series, item);
                    uvalue = lvalue;
                }
                if (lvalue != null) {
                    minimum = Math.min(minimum, lvalue.doubleValue());
                }
                if (uvalue != null) {
                    maximum = Math.max(maximum, uvalue.doubleValue());
                }

            }
        }
        if (minimum == Double.POSITIVE_INFINITY) {
            return null;
        } else {
            return new Range(minimum, maximum);
        }
    }

    /**
     * Returns the range of values in the range for the dataset. This method
     * is the partner for the getDomainExtent method.
     * 
     * @param data
     *           the dataset.
     * @return the range of values in the range for the dataset.
     */
    public static Range getRangeExtent(final Dataset data) {

        // check parameters...
        if (data == null) {
            return null;
        }

        // work out the minimum value...
        if (data instanceof RangeInfo) {
            final RangeInfo info = (RangeInfo) data;
            return info.getValueRange();
        }

        // hasn't implemented RangeInfo, so we'll have to iterate...
        else if (data instanceof CategoryDataset) {
            return iterateCategoryRangeExtent((CategoryDataset) data);
        }
        // hasn't implemented RangeInfo, so we'll have to iterate...
        else if (data instanceof XYDataset) {
            return iterateXYRangeExtent((XYDataset) data);
        } else {
            return null;
        }
    }

    /**
     * Iterates over the data item of the category dataset to find
     * the range extent.
     * 
     * @param data
     *           the category dataset to iterate over.
     * @return the range extent of the data within the dataset.
     */
    public static Range iterateCategoryRangeExtent(final CategoryDataset data) {
        double minimum = Double.POSITIVE_INFINITY;
        double maximum = Double.NEGATIVE_INFINITY;
        final int rowCount = data.getRowCount();
        final int columnCount = data.getColumnCount();
        for (int row = 0; row < rowCount; row++) {
            for (int column = 0; column < columnCount; column++) {
                final Number lvalue;
                final Number uvalue;
                if (data instanceof IntervalCategoryDataset) {
                    final IntervalCategoryDataset icd = (IntervalCategoryDataset) data;
                    lvalue = icd.getStartValue(row, column);
                    uvalue = icd.getEndValue(row, column);
                } else {
                    lvalue = data.getValue(row, column);
                    uvalue = lvalue;
                }
                if (lvalue != null) {
                    minimum = Math.min(minimum, lvalue.doubleValue());
                }
                if (uvalue != null) {
                    maximum = Math.max(maximum, uvalue.doubleValue());
                }
            }
        }
        if (minimum == Double.POSITIVE_INFINITY) {
            return null;
        } else {
            return new Range(minimum, maximum);
        }
    }

    /**
     * Iterates over the data item of the xy dataset to find
     * the range extent.
     * 
     * @param data
     *           the xy dataset to iterate over.
     * @return the range extent of the data within the dataset.
     */
    public static Range iterateXYRangeExtent(final XYDataset data) {
        double minimum = Double.POSITIVE_INFINITY;
        double maximum = Double.NEGATIVE_INFINITY;
        final int seriesCount = data.getSeriesCount();
        for (int series = 0; series < seriesCount; series++) {
            final int itemCount = data.getItemCount(series);
            for (int item = 0; item < itemCount; item++) {

                final Number lvalue;
                final Number uvalue;
                if (data instanceof IntervalXYDataset) {
                    final IntervalXYDataset intervalXYData = (IntervalXYDataset) data;
                    lvalue = intervalXYData.getStartYValue(series, item);
                    uvalue = intervalXYData.getEndYValue(series, item);
                } else if (data instanceof HighLowDataset) {
                    final HighLowDataset highLowData = (HighLowDataset) data;
                    lvalue = highLowData.getLowValue(series, item);
                    uvalue = highLowData.getHighValue(series, item);
                } else {
                    lvalue = data.getYValue(series, item);
                    uvalue = lvalue;
                }
                if (lvalue != null) {
                    minimum = Math.min(minimum, lvalue.doubleValue());
                }
                if (uvalue != null) {
                    maximum = Math.max(maximum, uvalue.doubleValue());
                }

            }
        }
        if (minimum == Double.POSITIVE_INFINITY) {
            return null;
        } else {
            return new Range(minimum, maximum);
        }
    }

    /**
     * Returns the minimum domain value for the specified dataset.
     * <P>
     * This is easy if the dataset implements the DomainInfo interface (a good idea if there is an efficient way to determine the minimum value). Otherwise, it
     * involves iterating over the entire data-set.
     * <p>
     * Returns null if all the data values in the dataset are null.
     * 
     * @param data
     *           the dataset.
     * @return the minimum domain value in the dataset (or null).
     */
    public static Number getMinimumDomainValue(final Dataset data) {

        // check parameters...
        if (data == null) {
            throw new IllegalArgumentException("DatasetUtilities.getMinimumDomainValue: null dataset not allowed.");
        }

        if ((data instanceof CategoryDataset) && !(data instanceof XYDataset)) {
            throw new IllegalArgumentException("DatasetUtilities.getMinimumDomainValue(...): "
                    + "TableDataset does not have numerical domain.");
        }

        // work out the minimum value...
        if (data instanceof DomainInfo) {
            final DomainInfo info = (DomainInfo) data;
            return info.getMinimumDomainValue();
        }

        // hasn't implemented DomainInfo, so iterate...
        else if (data instanceof XYDataset) {
            double minimum = Double.POSITIVE_INFINITY;
            final XYDataset xyData = (XYDataset) data;
            final int seriesCount = xyData.getSeriesCount();
            for (int series = 0; series < seriesCount; series++) {
                final int itemCount = xyData.getItemCount(series);
                for (int item = 0; item < itemCount; item++) {

                    final Number value;
                    if (data instanceof IntervalXYDataset) {
                        final IntervalXYDataset intervalXYData = (IntervalXYDataset) data;
                        value = intervalXYData.getStartXValue(series, item);
                    } else {
                        value = xyData.getXValue(series, item);
                    }
                    if (value != null) {
                        minimum = Math.min(minimum, value.doubleValue());
                    }

                }
            }
            if (minimum == Double.POSITIVE_INFINITY) {
                return null;
            } else {
                return new Double(minimum);
            }
        }

        else {
            return null; // unrecognised dataset...how should this be handled?
        }

    }

    /**
     * Returns the maximum domain value for the specified dataset.
     * <P>
     * This is easy if the dataset implements the DomainInfo interface (a good idea if there is an efficient way to determine the maximum value). Otherwise, it
     * involves iterating over the entire data-set.
     * <p>
     * Returns null if all the data values in the dataset are null.
     * 
     * @param data
     *           the dataset.
     * @return the maximum domain value in the dataset (or null).
     */
    public static Number getMaximumDomainValue(final Dataset data) {

        // check parameters...
        if (data == null) {
            throw new IllegalArgumentException("Datasets.getMaximumDomainValue: null dataset not allowed.");
        }

        if ((data instanceof CategoryDataset) && !(data instanceof XYDataset)) {
            throw new IllegalArgumentException(
                    "Datasets.getMaximumDomainValue(...): " + "CategoryDataset does not have numerical domain.");
        }

        // work out the maximum value...
        if (data instanceof DomainInfo) {
            final DomainInfo info = (DomainInfo) data;
            return info.getMaximumDomainValue();
        }

        // hasn't implemented DomainInfo, so iterate...
        else if (data instanceof XYDataset) {
            final XYDataset xyData = (XYDataset) data;
            double maximum = Double.NEGATIVE_INFINITY;
            final int seriesCount = xyData.getSeriesCount();
            for (int series = 0; series < seriesCount; series++) {
                final int itemCount = xyData.getItemCount(series);
                for (int item = 0; item < itemCount; item++) {

                    final Number value;
                    if (data instanceof IntervalXYDataset) {
                        final IntervalXYDataset intervalXYData = (IntervalXYDataset) data;
                        value = intervalXYData.getEndXValue(series, item);
                    } else {
                        value = xyData.getXValue(series, item);
                    }
                    if (value != null) {
                        maximum = Math.max(maximum, value.doubleValue());
                    }
                }
            }
            if (maximum == Double.NEGATIVE_INFINITY) {
                return null;
            } else {
                return new Double(maximum);
            }

        } else {
            return null; // unrecognised dataset...how should this be handled?
        }

    }

    /**
     * Returns the minimum range value for the specified dataset.
     * <P>
     * This is easy if the dataset implements the RangeInfo interface (a good idea if there is an efficient way to determine the minimum value). Otherwise, it
     * involves iterating over the entire data-set.
     * <p>
     * Returns null if all the data values in the dataset are null.
     * 
     * @param data
     *           the dataset.
     * @return the minimum range value in the dataset (or null).
     */
    public static Number getMinimumRangeValue(final Dataset data) {

        // check parameters...
        if (data == null) {
            throw new IllegalArgumentException("Datasets.getMinimumRangeValue: null dataset not allowed.");
        }

        // work out the minimum value...
        if (data instanceof RangeInfo) {
            final RangeInfo info = (RangeInfo) data;
            return info.getMinimumRangeValue();
        }

        // hasn't implemented RangeInfo, so we'll have to iterate...
        else if (data instanceof CategoryDataset) {

            final CategoryDataset categoryData = (CategoryDataset) data;
            double minimum = Double.POSITIVE_INFINITY;
            final int seriesCount = categoryData.getRowCount();
            final int itemCount = categoryData.getColumnCount();
            for (int series = 0; series < seriesCount; series++) {
                for (int item = 0; item < itemCount; item++) {
                    final Number value;
                    if (data instanceof IntervalCategoryDataset) {
                        final IntervalCategoryDataset icd = (IntervalCategoryDataset) data;
                        value = icd.getStartValue(series, item);
                    } else {
                        value = categoryData.getValue(series, item);
                    }
                    if (value != null) {
                        minimum = Math.min(minimum, value.doubleValue());
                    }
                }
            }
            if (minimum == Double.POSITIVE_INFINITY) {
                return null;
            } else {
                return new Double(minimum);
            }

        } else if (data instanceof XYDataset) {

            // hasn't implemented RangeInfo, so we'll have to iterate...
            final XYDataset xyData = (XYDataset) data;
            double minimum = Double.POSITIVE_INFINITY;
            final int seriesCount = xyData.getSeriesCount();
            for (int series = 0; series < seriesCount; series++) {
                final int itemCount = xyData.getItemCount(series);
                for (int item = 0; item < itemCount; item++) {

                    final Number value;
                    if (data instanceof IntervalXYDataset) {
                        final IntervalXYDataset intervalXYData = (IntervalXYDataset) data;
                        value = intervalXYData.getStartYValue(series, item);
                    } else if (data instanceof HighLowDataset) {
                        final HighLowDataset highLowData = (HighLowDataset) data;
                        value = highLowData.getLowValue(series, item);
                    } else {
                        value = xyData.getYValue(series, item);
                    }
                    if (value != null) {
                        minimum = Math.min(minimum, value.doubleValue());
                    }

                }
            }
            if (minimum == Double.POSITIVE_INFINITY) {
                return null;
            } else {
                return new Double(minimum);
            }

        } else {
            return null;
        }

    }

    /**
     * Returns the maximum range value for the specified dataset.
     * <P>
     * This is easy if the dataset implements the RangeInfo interface (a good idea if there is an efficient way to determine the maximum value). Otherwise, it
     * involves iterating over the entire data-set.
     * <p>
     * Returns null if all the data values are null.
     * 
     * @param data
     *           the dataset.
     * @return the maximum range value in the dataset (or null).
     */
    public static Number getMaximumRangeValue(final Dataset data) {

        // check parameters...
        if (data == null) {
            throw new IllegalArgumentException("Datasets.getMinimumRangeValue: null dataset not allowed.");
        }

        // work out the minimum value...
        if (data instanceof RangeInfo) {
            final RangeInfo info = (RangeInfo) data;
            return info.getMaximumRangeValue();
        }

        // hasn't implemented RangeInfo, so we'll have to iterate...
        else if (data instanceof CategoryDataset) {

            final CategoryDataset categoryData = (CategoryDataset) data;
            double maximum = Double.NEGATIVE_INFINITY;
            final int seriesCount = categoryData.getRowCount();
            final int itemCount = categoryData.getColumnCount();
            for (int series = 0; series < seriesCount; series++) {
                for (int item = 0; item < itemCount; item++) {
                    final Number value;
                    if (data instanceof IntervalCategoryDataset) {
                        final IntervalCategoryDataset icd = (IntervalCategoryDataset) data;
                        value = icd.getEndValue(series, item);
                    } else {
                        value = categoryData.getValue(series, item);
                    }
                    if (value != null) {
                        maximum = Math.max(maximum, value.doubleValue());
                    }
                }
            }
            if (maximum == Double.NEGATIVE_INFINITY) {
                return null;
            } else {
                return new Double(maximum);
            }

        } else if (data instanceof XYDataset) {

            final XYDataset xyData = (XYDataset) data;
            double maximum = Double.NEGATIVE_INFINITY;
            final int seriesCount = xyData.getSeriesCount();
            for (int series = 0; series < seriesCount; series++) {
                final int itemCount = xyData.getItemCount(series);
                for (int item = 0; item < itemCount; item++) {
                    final Number value;
                    if (data instanceof IntervalXYDataset) {
                        final IntervalXYDataset intervalXYData = (IntervalXYDataset) data;
                        value = intervalXYData.getEndYValue(series, item);
                    } else if (data instanceof HighLowDataset) {
                        final HighLowDataset highLowData = (HighLowDataset) data;
                        value = highLowData.getHighValue(series, item);
                    } else {
                        value = xyData.getYValue(series, item);
                    }
                    if (value != null) {
                        maximum = Math.max(maximum, value.doubleValue());
                    }
                }
            }
            if (maximum == Double.NEGATIVE_INFINITY) {
                return null;
            } else {
                return new Double(maximum);
            }

        } else {
            return null;
        }

    }

    /**
     * Creates a pie dataset from a table dataset by taking all the values
     * for a single row.
     * 
     * @param data
     *           the data.
     * @param rowKey
     *           the row key.
     * @return a pie dataset.
     */
    public static PieDataset createPieDatasetForRow(final CategoryDataset data, final Comparable rowKey) {

        final int row = data.getRowIndex(rowKey);
        return createPieDatasetForRow(data, row);

    }

    /**
     * Creates a pie dataset from a table dataset by taking all the values
     * for a single row.
     * 
     * @param data
     *           the data.
     * @param row
     *           the row (zero-based index).
     * @return a pie dataset.
     */
    public static PieDataset createPieDatasetForRow(final CategoryDataset data, final int row) {

        final DefaultPieDataset result = new DefaultPieDataset();
        final int columnCount = data.getColumnCount();
        for (int current = 0; current < columnCount; current++) {
            final Comparable columnKey = data.getColumnKey(current);
            result.setValue(columnKey, data.getValue(row, current));
        }
        return result;

    }

    /**
     * Creates a pie dataset from a table dataset by taking all the values
     * for a single column.
     * 
     * @param data
     *           the data.
     * @param columnKey
     *           the column key.
     * @return a pie dataset.
     */
    public static PieDataset createPieDatasetForColumn(final CategoryDataset data, final Comparable columnKey) {

        final int column = data.getColumnIndex(columnKey);
        return createPieDatasetForColumn(data, column);

    }

    /**
     * Creates a pie dataset from a table dataset by taking all the values
     * for a single column.
     * 
     * @param data
     *           the data.
     * @param column
     *           the column (zero-based index).
     * @return a pie dataset.
     */
    public static PieDataset createPieDatasetForColumn(final CategoryDataset data, final int column) {

        final DefaultPieDataset result = new DefaultPieDataset();
        final int rowCount = data.getRowCount();
        for (int i = 0; i < rowCount; i++) {
            final Comparable rowKey = data.getRowKey(i);
            result.setValue(rowKey, data.getValue(i, column));
        }
        return result;

    }

    /**
     * Calculates the total of all the values in a {@link PieDataset}. If the dataset contains
     * negative or <code>null</code> values, they are ignored.
     * 
     * @param dataset
     *           the dataset (<code>null</code> not permitted).
     * @return The total.
     */
    public static double calculatePieDatasetTotal(final PieDataset dataset) {
        if (dataset == null) {
            throw new IllegalArgumentException("Null 'dataset' argument.");
        }
        final List keys = dataset.getKeys();
        double totalValue = 0;
        final Iterator iterator = keys.iterator();
        while (iterator.hasNext()) {
            final Comparable current = (Comparable) iterator.next();
            if (current != null) {
                final Number value = dataset.getValue(current);
                double v = 0.0;
                if (value != null) {
                    v = value.doubleValue();
                }
                if (v > 0) {
                    totalValue = totalValue + v;
                }
            }
        }
        return totalValue;
    }

    /**
     * Returns the minimum and maximum values for the dataset's range (as in domain/range),
     * assuming that the series in one category are stacked.
     * 
     * @param data
     *           the dataset.
     * @return the value range.
     */
    public static Range getStackedRangeExtent(final CategoryDataset data) {

        Range result = null;
        if (data != null) {
            double minimum = 0.0;
            double maximum = 0.0;
            final int categoryCount = data.getColumnCount();
            for (int item = 0; item < categoryCount; item++) {
                double positive = 0.0;
                double negative = 0.0;
                final int seriesCount = data.getRowCount();
                for (int series = 0; series < seriesCount; series++) {
                    final Number number = data.getValue(series, item);
                    if (number != null) {
                        final double value = number.doubleValue();
                        if (value > 0.0) {
                            positive = positive + value;
                        }
                        if (value < 0.0) {
                            negative = negative + value; // '+', remember value is negative
                        }
                    }
                }
                minimum = Math.min(minimum, negative);
                maximum = Math.max(maximum, positive);
            }
            result = new Range(minimum, maximum);
        }
        return result;

    }

    /**
     * Returns the minimum and maximum values for the dataset's range (as in domain/range),
     * assuming that the series in one category are stacked.
     * 
     * @param dataset
     *           the dataset.
     * @param map
     *           a structure that maps series to groups.
     * @return the value range.
     */
    public static Range getStackedRangeExtent(final CategoryDataset dataset, final KeyToGroupMap map) {

        Range result = null;
        if (dataset != null) {

            // create an array holding the group indices...
            int[] groupIndex = new int[dataset.getRowCount()];
            for (int i = 0; i < dataset.getRowCount(); i++) {
                groupIndex[i] = map.getGroupIndex(map.getGroup(dataset.getRowKey(i)));
            }

            // minimum and maximum for each group...
            int groupCount = map.getGroupCount();
            double[] minimum = new double[groupCount];
            double[] maximum = new double[groupCount];

            final int categoryCount = dataset.getColumnCount();
            for (int item = 0; item < categoryCount; item++) {
                double[] positive = new double[groupCount];
                double[] negative = new double[groupCount];
                final int seriesCount = dataset.getRowCount();
                for (int series = 0; series < seriesCount; series++) {
                    final Number number = dataset.getValue(series, item);
                    if (number != null) {
                        final double value = number.doubleValue();
                        if (value > 0.0) {
                            positive[groupIndex[series]] = positive[groupIndex[series]] + value;
                        }
                        if (value < 0.0) {
                            negative[groupIndex[series]] = negative[groupIndex[series]] + value;
                            // '+', remember value is negative
                        }
                    }
                }
                for (int g = 0; g < groupCount; g++) {
                    minimum[g] = Math.min(minimum[g], negative[g]);
                    maximum[g] = Math.max(maximum[g], positive[g]);
                }
            }
            for (int j = 0; j < groupCount; j++) {
                result = Range.combine(result, new Range(minimum[j], maximum[j]));
            }
        }
        return result;

    }

    /**
     * Returns the minimum value in the dataset range, assuming that values in
     * each category are "stacked".
     * 
     * @param data
     *           the dataset.
     * @return the minimum value.
     */
    public static Number getMinimumStackedRangeValue(final CategoryDataset data) {

        Number result = null;

        if (data != null) {

            double minimum = 0.0;

            final int categoryCount = data.getRowCount();
            for (int item = 0; item < categoryCount; item++) {
                double total = 0.0;

                final int seriesCount = data.getColumnCount();
                for (int series = 0; series < seriesCount; series++) {
                    final Number number = data.getValue(series, item);
                    if (number != null) {
                        final double value = number.doubleValue();
                        if (value < 0.0) {
                            total = total + value; // '+', remember value is negative
                        }
                    }
                }
                minimum = Math.min(minimum, total);

            }

            result = new Double(minimum);

        }

        return result;

    }

    /**
     * Returns the maximum value in the dataset range, assuming that values in
     * each category are "stacked".
     * 
     * @param data
     *           the dataset (<code>null</code> permitted).
     * @return The maximum value (possibly <code>null</code>).
     */
    public static Number getMaximumStackedRangeValue(final CategoryDataset data) {

        Number result = null;

        if (data != null) {
            double maximum = 0.0;
            final int categoryCount = data.getColumnCount();
            for (int item = 0; item < categoryCount; item++) {
                double total = 0.0;
                final int seriesCount = data.getRowCount();
                for (int series = 0; series < seriesCount; series++) {
                    final Number number = data.getValue(series, item);
                    if (number != null) {
                        final double value = number.doubleValue();
                        if (value > 0.0) {
                            total = total + value;
                        }
                    }
                }
                maximum = Math.max(maximum, total);
            }
            result = new Double(maximum);
        }

        return result;

    }

    /**
     * Creates an {@link XYDataset} by sampling the specified function over a fixed range.
     * 
     * @param f
     *           the function (<code>null</code> not permitted).
     * @param start
     *           the start value for the range.
     * @param end
     *           the end value for the range.
     * @param samples
     *           the number of sample points (must be > 1).
     * @param seriesName
     *           the name to give the resulting series (<code>null</code> not permitted).
     * @return A dataset.
     */
    public static XYDataset sampleFunction2D(final Function2D f, final double start, final double end,
            final int samples, final String seriesName) {

        if (f == null) {
            throw new IllegalArgumentException("Null 'f' argument.");
        }
        if (seriesName == null) {
            throw new IllegalArgumentException("Null 'seriesName' argument.");
        }
        if (start >= end) {
            throw new IllegalArgumentException("Requires 'start' < 'end'.");
        }
        if (samples < 2) {
            throw new IllegalArgumentException("Requires 'samples' > 1");
        }

        final XYSeries series = new XYSeries(seriesName);
        final double step = (end - start) / samples;
        for (int i = 0; i <= samples; i++) {
            final double x = start + (step * i);
            series.add(x, f.getValue(x));
        }
        final XYSeriesCollection collection = new XYSeriesCollection(series);
        return collection;

    }

    /**
     * Creates a {@link CategoryDataset} that contains a copy of the data in an array
     * (instances of <code>Double</code> are created to represent the data items).
     * <p>
     * Row and column keys are created by appending 0, 1, 2, ... to the supplied prefixes.
     * 
     * @param rowKeyPrefix
     *           the row key prefix.
     * @param columnKeyPrefix
     *           the column key prefix.
     * @param data
     *           the data.
     * @return the dataset.
     */
    public static CategoryDataset createCategoryDataset(final String rowKeyPrefix, final String columnKeyPrefix,
            final double[][] data) {

        final DefaultCategoryDataset result = new DefaultCategoryDataset();
        for (int r = 0; r < data.length; r++) {
            final String rowKey = rowKeyPrefix + (r + 1);
            for (int c = 0; c < data[r].length; c++) {
                final String columnKey = columnKeyPrefix + (c + 1);
                result.addValue(new Double(data[r][c]), rowKey, columnKey);
            }
        }
        return result;

    }

    /**
     * Creates a {@link CategoryDataset} that contains a copy of the data in an array.
     * <p>
     * Row and column keys are created by appending 0, 1, 2, ... to the supplied prefixes.
     * 
     * @param rowKeyPrefix
     *           the row key prefix.
     * @param columnKeyPrefix
     *           the column key prefix.
     * @param data
     *           the data.
     * @return the dataset.
     */
    public static CategoryDataset createCategoryDataset(final String rowKeyPrefix, final String columnKeyPrefix,
            final Number[][] data) {

        final DefaultCategoryDataset result = new DefaultCategoryDataset();
        for (int r = 0; r < data.length; r++) {
            final String rowKey = rowKeyPrefix + (r + 1);
            for (int c = 0; c < data[r].length; c++) {
                final String columnKey = columnKeyPrefix + (c + 1);
                result.addValue(data[r][c], rowKey, columnKey);
            }
        }
        return result;

    }

    /**
     * Creates a {@link CategoryDataset} that contains a copy of the data in an array
     * (instances of <code>Double</code> are created to represent the data items).
     * <p>
     * Row and column keys are taken from the supplied arrays.
     * 
     * @param rowKeys
     *           the row keys.
     * @param columnKeys
     *           the column keys.
     * @param data
     *           the data.
     * @return The dataset.
     */
    public static CategoryDataset createCategoryDataset(final String[] rowKeys, final String[] columnKeys,
            final double[][] data) {

        // check arguments...
        if (rowKeys == null) {
            throw new IllegalArgumentException("Argument 'rowKeys' cannot be null.");
        }
        if (columnKeys == null) {
            throw new IllegalArgumentException("Argument 'columnKeys' cannot be null.");
        }
        if (rowKeys.length != data.length) {
            throw new IllegalArgumentException(
                    "The number of row keys does not match the number of rows in the data array.");
        }
        int columnCount = 0;
        for (int r = 0; r < data.length; r++) {
            columnCount = Math.max(columnCount, data[r].length);
        }
        if (columnKeys.length != columnCount) {
            throw new IllegalArgumentException(
                    "The number of column keys does not match the number of columns in the data array.");
        }

        // now do the work...
        final DefaultCategoryDataset result = new DefaultCategoryDataset();
        for (int r = 0; r < data.length; r++) {
            final String rowKey = rowKeys[r];
            for (int c = 0; c < data[r].length; c++) {
                final String columnKey = columnKeys[c];
                result.addValue(new Double(data[r][c]), rowKey, columnKey);
            }
        }
        return result;

    }

    /**
     * Creates a {@link CategoryDataset} by copying the data from the supplied {@link KeyedValues} instance.
     * 
     * @param rowKey
     *           the row key.
     * @param rowData
     *           the row data.
     * @return A dataset.
     */
    public static CategoryDataset createCategoryDataset(final String rowKey, final KeyedValues rowData) {

        final DefaultCategoryDataset result = new DefaultCategoryDataset();
        for (int i = 0; i < rowData.getItemCount(); i++) {
            result.addValue(rowData.getValue(i), rowKey, rowData.getKey(i));
        }
        return result;

    }

    /**
     * Returns <code>true</code> if the dataset is empty (or <code>null</code>), and <code>false</code> otherwise.
     * 
     * @param data
     *           the dataset (<code>null</code> permitted).
     * @return A boolean.
     */
    public static boolean isEmptyOrNull(final XYDataset data) {

        boolean result = true;

        if (data != null) {
            for (int s = 0; s < data.getSeriesCount(); s++) {
                if (data.getItemCount(s) > 0) {
                    result = false;
                    continue;
                }
            }
        }

        return result;

    }

    /**
     * Returns <code>true</code> if the dataset is empty (or <code>null</code>), and <code>false</code> otherwise.
     * 
     * @param dataset
     *           the dataset (<code>null</code> permitted).
     * @return a boolean.
     */
    public static boolean isEmptyOrNull(final PieDataset dataset) {

        if (dataset == null) {
            return true;
        }

        final int itemCount = dataset.getItemCount();
        if (itemCount == 0) {
            return true;
        }

        for (int item = 0; item < itemCount; item++) {
            final Number y = dataset.getValue(item);
            if (y != null) {
                final double yy = y.doubleValue();
                if (yy > 0.0) {
                    return false;
                }
            }
        }

        return true;

    }

    /**
     * Returns <code>true</code> if the dataset is empty (or <code>null</code>), and <code>false</code> otherwise.
     * 
     * @param data
     *           the dataset (<code>null</code> permitted).
     * @return A boolean.
     */
    public static boolean isEmptyOrNull(final CategoryDataset data) {

        if (data == null) {
            return true;
        }

        final int rowCount = data.getRowCount();
        final int columnCount = data.getColumnCount();
        if (rowCount == 0 || columnCount == 0) {
            return true;
        }

        for (int r = 0; r < rowCount; r++) {
            for (int c = 0; c < columnCount; c++) {
                if (data.getValue(r, c) != null) {
                    return false;
                }

            }
        }

        return true;

    }

    /**
     * Creates an "Other" slice for percentages below the percent threshold.
     * 
     * @param dataset
     *           the PieDataset.
     * @param percentThreshold
     *           the percent threshold.
     * @return A PieDataset.
     */
    public static PieDataset limitPieDataset(final PieDataset dataset, final double percentThreshold) {
        return DatasetUtilities.limitPieDataset(dataset, percentThreshold, 2, "Other");
    }

    /**
     * Create an "Other" slice for percentages below the percent threshold providing there
     * are more slices below the percent threshold than specified in the slice threshold.
     * 
     * @param dataset
     *           the source dataset.
     * @param percentThreshold
     *           the percent threshold (ten percent is 0.10).
     * @param minItems
     *           only aggregate low values if there are at least this many.
     * @return A PieDataset.
     */
    public static PieDataset limitPieDataset(final PieDataset dataset, final double percentThreshold,
            final int minItems) {
        return DatasetUtilities.limitPieDataset(dataset, percentThreshold, minItems, "Other");
    }

    /**
     * Creates a new pie dataset based on the supplied dataset, but modified by aggregating all
     * the low value items (those whose value is lower than the percentThreshold) into a single
     * item. The aggregated items are assigned the specified key. Aggregation only occurs if
     * there are at least minItems items to aggregate.
     * 
     * @param dataset
     *           the source dataset.
     * @param percentThreshold
     *           the percent threshold (ten percent is 0.10).
     * @param minItems
     *           only aggregate low values if there are at least this many.
     * @param key
     *           the key to represent the aggregated items.
     * @return The pie dataset with (possibly) aggregated items.
     */
    public static PieDataset limitPieDataset(final PieDataset dataset, final double percentThreshold,
            final int minItems, final Comparable key) {

        final DefaultPieDataset result = new DefaultPieDataset();
        final double total = DatasetUtilities.calculatePieDatasetTotal(dataset);

        // Iterate and find all keys below threshold percentThreshold
        final List keys = dataset.getKeys();
        final ArrayList otherKeys = new ArrayList();
        Iterator iterator = keys.iterator();
        while (iterator.hasNext()) {
            final Comparable currentKey = (Comparable) iterator.next();
            final Number dataValue = dataset.getValue(currentKey);
            if (dataValue != null) {
                final double value = dataValue.doubleValue();
                if (value / total < percentThreshold) {
                    otherKeys.add(currentKey);
                }
            }
        }

        // Create new dataset with keys above threshold percentThreshold
        iterator = keys.iterator();
        double otherValue = 0;
        while (iterator.hasNext()) {
            final Comparable currentKey = (Comparable) iterator.next();
            final Number dataValue = dataset.getValue(currentKey);
            if (dataValue != null) {
                if (otherKeys.contains(currentKey) && otherKeys.size() >= minItems) {
                    // Do not add key to dataset
                    otherValue += dataValue.doubleValue();
                } else {
                    // Add key to dataset
                    result.setValue(currentKey, dataValue);
                }
            }
        }
        // Add other category if applicable
        if (otherKeys.size() >= minItems) {
            result.setValue(key, otherValue);
        }
        return result;
    }

    /**
     * Returns the minimum and maximum values for the dataset's range,
     * assuming that the series are stacked.
     * 
     * @param data
     *           the dataset.
     * @return the value range.
     */
    public static Range getStackedRangeExtent(final TableXYDataset data) {
        // check parameters...
        if (data == null) {
            return null;
        }
        double minimum = Double.POSITIVE_INFINITY;
        double maximum = Double.NEGATIVE_INFINITY;
        for (int itemNo = 0; itemNo < data.getItemCount(); itemNo++) {
            double value = 0;
            for (int seriesNo = 0; seriesNo < data.getSeriesCount(); seriesNo++) {
                if (data.getYValue(seriesNo, itemNo) != null) {
                    value += (data.getYValue(seriesNo, itemNo).doubleValue());
                }
            }
            if (value > maximum) {
                maximum = value;
            }
            if (value < minimum) {
                minimum = value;
            }
        }
        if (minimum == Double.POSITIVE_INFINITY) {
            return null;
        } else {
            return new Range(minimum, maximum);
        }
    }

    /**
     * Calculates the range of values for a dataset where each item is the running total of
     * the items for the current series.
     * 
     * @param dataset
     *           the dataset.
     * @return The range.
     */
    public static Range getCumulativeRangeExtent(final CategoryDataset dataset) {

        if (dataset == null) {
            return null;
        }

        boolean allItemsNull = true; // we'll set this to false if there is at least one
        // non-null data item...
        double minimum = 0.0;
        double maximum = 0.0;
        for (int row = 0; row < dataset.getRowCount(); row++) {
            double runningTotal = 0.0;
            for (int column = 0; column < dataset.getColumnCount() - 1; column++) {
                final Number n = dataset.getValue(row, column);
                if (n != null) {
                    allItemsNull = false;
                    final double value = n.doubleValue();
                    runningTotal = runningTotal + value;
                    minimum = Math.min(minimum, runningTotal);
                    maximum = Math.max(maximum, runningTotal);
                }
            }
        }
        if (!allItemsNull) {
            return new Range(minimum, maximum);
        } else {
            return null;
        }

    }
}