    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------------------
    028     * DatasetUtilities.java
    029     * ---------------------
    030     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Andrzej Porebski (bug fix);
    034     *                   Jonathan Nash (bug fix);
    035     *                   Richard Atkinson;
    036     *                   Andreas Schroeder;
    037     *
    038     * Changes (from 18-Sep-2001)
    039     * --------------------------
    040     * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
    041     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    042     * 15-Nov-2001 : Moved to package com.jrefinery.data.* in the JCommon class
    043     *               library (DG);
    044     *               Changed to handle null values from datasets (DG);
    045     *               Bug fix (thanks to Andrzej Porebski) - initial value now set
    046     *               to positive or negative infinity when iterating (DG);
    047     * 22-Nov-2001 : Datasets with containing no data now return null for min and
    048     *               max calculations (DG);
    049     * 13-Dec-2001 : Extended to handle HighLowDataset and IntervalXYDataset (DG);
    050     * 15-Feb-2002 : Added getMinimumStackedRangeValue() and
    051     *               getMaximumStackedRangeValue() (DG);
    052     * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
    053     * 18-Mar-2002 : Fixed bug in min/max domain calculation for datasets that
    054     *               implement the CategoryDataset interface AND the XYDataset
    055     *               interface at the same time.  Thanks to Jonathan Nash for the
    056     *               fix (DG);
    057     * 23-Apr-2002 : Added getDomainExtent() and getRangeExtent() methods (DG);
    058     * 13-Jun-2002 : Modified range measurements to handle
    059     *               IntervalCategoryDataset (DG);
    060     * 12-Jul-2002 : Method name change in DomainInfo interface (DG);
    061     * 30-Jul-2002 : Added pie dataset summation method (DG);
    062     * 01-Oct-2002 : Added a method for constructing an XYDataset from a Function2D
    063     *               instance (DG);
    064     * 24-Oct-2002 : Amendments required following changes to the CategoryDataset
    065     *               interface (DG);
    066     * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
    067     * 04-Mar-2003 : Added isEmpty(XYDataset) method (DG);
    068     * 05-Mar-2003 : Added a method for creating a CategoryDataset from a
    069     *               KeyedValues instance (DG);
    070     * 15-May-2003 : Renamed isEmpty --> isEmptyOrNull (DG);
    071     * 25-Jun-2003 : Added limitPieDataset methods (RA);
    072     * 26-Jun-2003 : Modified getDomainExtent() method to accept null datasets (DG);
    073     * 27-Jul-2003 : Added getStackedRangeExtent(TableXYDataset data) (RA);
    074     * 18-Aug-2003 : getStackedRangeExtent(TableXYDataset data) now handles null
    075     *               values (RA);
    076     * 02-Sep-2003 : Added method to check for null or empty PieDataset (DG);
    077     * 18-Sep-2003 : Fix for bug 803660 (getMaximumRangeValue for
    078     *               CategoryDataset) (DG);
    079     * 20-Oct-2003 : Added getCumulativeRangeExtent() method (DG);
    080     * 09-Jan-2003 : Added argument checking code to the createCategoryDataset()
    081     *               method (DG);
    082     * 23-Mar-2004 : Fixed bug in getMaximumStackedRangeValue() method (DG);
    083     * 31-Mar-2004 : Exposed the extent iteration algorithms to use one of them and
    084     *               applied noninstantiation pattern (AS);
    085     * 11-May-2004 : Renamed getPieDatasetTotal --> calculatePieDatasetTotal (DG);
    086     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue();
    087     * 24-Aug-2004 : Added argument checks to createCategoryDataset() method (DG);
    088     * 04-Oct-2004 : Renamed ArrayUtils --> ArrayUtilities (DG);
    089     * 06-Oct-2004 : Renamed findDomainExtent() --> findDomainBounds(),
    090     *               findRangeExtent() --> findRangeBounds() (DG);
    091     * 07-Jan-2005 : Renamed findStackedRangeExtent() --> findStackedRangeBounds(),
    092     *               findCumulativeRangeExtent() --> findCumulativeRangeBounds(),
    093     *               iterateXYRangeExtent() --> iterateXYRangeBounds(),
    094     *               removed deprecated methods (DG);
    095     * 03-Feb-2005 : The findStackedRangeBounds() methods now return null for
    096     *               empty datasets (DG);
    097     * 03-Mar-2005 : Moved createNumberArray() and createNumberArray2D() methods
    098     *               from DatasetUtilities --> DataUtilities (DG);
    099     * 22-Sep-2005 : Added new findStackedRangeBounds() method that takes base
    100     *               argument (DG);
    101     * ------------- JFREECHART 1.0.x ---------------------------------------------
    102     * 15-Mar-2007 : Added calculateStackTotal() method (DG);
    103     *
    104     */
    105
    106    package org.jfree.data.general;
    107
    108    import java.util.ArrayList;
    109    import java.util.Iterator;
    110    import java.util.List;
    111
    112    import org.jfree.data.DomainInfo;
    113    import org.jfree.data.KeyToGroupMap;
    114    import org.jfree.data.KeyedValues;
    115    import org.jfree.data.Range;
    116    import org.jfree.data.RangeInfo;
    117    import org.jfree.data.category.CategoryDataset;
    118    import org.jfree.data.category.DefaultCategoryDataset;
    119    import org.jfree.data.category.IntervalCategoryDataset;
    120    import org.jfree.data.function.Function2D;
    121    import org.jfree.data.xy.IntervalXYDataset;
    122    import org.jfree.data.xy.OHLCDataset;
    123    import org.jfree.data.xy.TableXYDataset;
    124    import org.jfree.data.xy.XYDataset;
    125    import org.jfree.data.xy.XYSeries;
    126    import org.jfree.data.xy.XYSeriesCollection;
    127    import org.jfree.util.ArrayUtilities;
    128
    129    /**
    130     * A collection of useful static methods relating to datasets.
    131     */
    132    public final class DatasetUtilities {
    133
    134        /**
    135         * Private constructor for non-instanceability.
    136         */
    137        private DatasetUtilities() {
    138            // now try to instantiate this ;-)
    139        }
    140
    141        /**
    142         * Calculates the total of all the values in a {@link PieDataset}.  If
    143         * the dataset contains negative or <code>null</code> values, they are
    144         * ignored.
    145         *
    146         * @param dataset  the dataset (<code>null</code> not permitted).
    147         *
    148         * @return The total.
    149         */
    150        public static double calculatePieDatasetTotal(PieDataset dataset) {
    151            if (dataset == null) {
    152                throw new IllegalArgumentException("Null 'dataset' argument.");
    153            }
    154            List keys = dataset.getKeys();
    155            double totalValue = 0;
    156            Iterator iterator = keys.iterator();
    157            while (iterator.hasNext()) {
    158                Comparable current = (Comparable) iterator.next();
    159                if (current != null) {
    160                    Number value = dataset.getValue(current);
    161                    double v = 0.0;
    162                    if (value != null) {
    163                        v = value.doubleValue();
    164                    }
    165                    if (v > 0) {
    166                        totalValue = totalValue + v;
    167                    }
    168                }
    169            }
    170            return totalValue;
    171        }
    172
    173        /**
    174         * Creates a pie dataset from a table dataset by taking all the values
    175         * for a single row.
    176         *
    177         * @param dataset  the dataset (<code>null</code> not permitted).
    178         * @param rowKey  the row key.
    179         *
    180         * @return A pie dataset.
    181         */
    182        public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
    183                                                        Comparable rowKey) {
    184            int row = dataset.getRowIndex(rowKey);
    185            return createPieDatasetForRow(dataset, row);
    186        }
    187
    188        /**
    189         * Creates a pie dataset from a table dataset by taking all the values
    190         * for a single row.
    191         *
    192         * @param dataset  the dataset (<code>null</code> not permitted).
    193         * @param row  the row (zero-based index).
    194         *
    195         * @return A pie dataset.
    196         */
    197        public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
    198                                                        int row) {
    199            DefaultPieDataset result = new DefaultPieDataset();
    200            int columnCount = dataset.getColumnCount();
    201            for (int current = 0; current < columnCount; current++) {
    202                Comparable columnKey = dataset.getColumnKey(current);
    203                result.setValue(columnKey, dataset.getValue(row, current));
    204            }
    205            return result;
    206        }
    207
    208        /**
    209         * Creates a pie dataset from a table dataset by taking all the values
    210         * for a single column.
    211         *
    212         * @param dataset  the dataset (<code>null</code> not permitted).
    213         * @param columnKey  the column key.
    214         *
    215         * @return A pie dataset.
    216         */
    217        public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
    218                                                           Comparable columnKey) {
    219            int column = dataset.getColumnIndex(columnKey);
    220            return createPieDatasetForColumn(dataset, column);
    221        }
    222
    223        /**
    224         * Creates a pie dataset from a {@link CategoryDataset} by taking all the
    225         * values for a single column.
    226         *
    227         * @param dataset  the dataset (<code>null</code> not permitted).
    228         * @param column  the column (zero-based index).
    229         *
    230         * @return A pie dataset.
    231         */
    232        public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
    233                                                           int column) {
    234            DefaultPieDataset result = new DefaultPieDataset();
    235            int rowCount = dataset.getRowCount();
    236            for (int i = 0; i < rowCount; i++) {
    237                Comparable rowKey = dataset.getRowKey(i);
    238                result.setValue(rowKey, dataset.getValue(i, column));
    239            }
    240            return result;
    241        }
    242
    243        /**
    244         * Creates a new pie dataset based on the supplied dataset, but modified
    245         * by aggregating all the low value items (those whose value is lower
    246         * than the <code>percentThreshold</code>) into a single item with the
    247         * key "Other".
    248         *
    249         * @param source  the source dataset (<code>null</code> not permitted).
    250         * @param key  a new key for the aggregated items (<code>null</code> not
    251         *             permitted).
    252         * @param minimumPercent  the percent threshold.
    253         *
    254         * @return The pie dataset with (possibly) aggregated items.
    255         */
    256        public static PieDataset createConsolidatedPieDataset(PieDataset source,
    257                                                              Comparable key,
    258                                                              double minimumPercent)
    259        {
    260            return DatasetUtilities.createConsolidatedPieDataset(
    261                source, key, minimumPercent, 2
    262            );
    263        }
    264
    265        /**
    266         * Creates a new pie dataset based on the supplied dataset, but modified
    267         * by aggregating all the low value items (those whose value is lower
    268         * than the <code>percentThreshold</code>) into a single item.  The
    269         * aggregated items are assigned the specified key.  Aggregation only
    270         * occurs if there are at least <code>minItems</code> items to aggregate.
    271         *
    272         * @param source  the source dataset (<code>null</code> not permitted).
    273         * @param key  the key to represent the aggregated items.
    274         * @param minimumPercent  the percent threshold (ten percent is 0.10).
    275         * @param minItems  only aggregate low values if there are at least this
    276         *                  many.
    277         *
    278         * @return The pie dataset with (possibly) aggregated items.
    279         */
    280        public static PieDataset createConsolidatedPieDataset(PieDataset source,
    281                                                              Comparable key,
    282                                                              double minimumPercent,
    283                                                              int minItems) {
    284
    285            DefaultPieDataset result = new DefaultPieDataset();
    286            double total = DatasetUtilities.calculatePieDatasetTotal(source);
    287
    288            //  Iterate and find all keys below threshold percentThreshold
    289            List keys = source.getKeys();
    290            ArrayList otherKeys = new ArrayList();
    291            Iterator iterator = keys.iterator();
    292            while (iterator.hasNext()) {
    293                Comparable currentKey = (Comparable) iterator.next();
    294                Number dataValue = source.getValue(currentKey);
    295                if (dataValue != null) {
    296                    double value = dataValue.doubleValue();
    297                    if (value / total < minimumPercent) {
    298                        otherKeys.add(currentKey);
    299                    }
    300                }
    301            }
    302
    303            //  Create new dataset with keys above threshold percentThreshold
    304            iterator = keys.iterator();
    305            double otherValue = 0;
    306            while (iterator.hasNext()) {
    307                Comparable currentKey = (Comparable) iterator.next();
    308                Number dataValue = source.getValue(currentKey);
    309                if (dataValue != null) {
    310                    if (otherKeys.contains(currentKey)
    311                        && otherKeys.size() >= minItems) {
    312                        //  Do not add key to dataset
    313                        otherValue += dataValue.doubleValue();
    314                    }
    315                    else {
    316                        //  Add key to dataset
    317                        result.setValue(currentKey, dataValue);
    318                    }
    319                }
    320            }
    321            //  Add other category if applicable
    322            if (otherKeys.size() >= minItems) {
    323                result.setValue(key, otherValue);
    324            }
    325            return result;
    326        }
    327
    328        /**
    329         * Creates a {@link CategoryDataset} that contains a copy of the data in an
    330         * array (instances of <code>Double</code> are created to represent the
    331         * data items).
    332         * <p>
    333         * Row and column keys are created by appending 0, 1, 2, ... to the
    334         * supplied prefixes.
    335         *
    336         * @param rowKeyPrefix  the row key prefix.
    337         * @param columnKeyPrefix  the column key prefix.
    338         * @param data  the data.
    339         *
    340         * @return The dataset.
    341         */
    342        public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
    343                                                            String columnKeyPrefix,
    344                                                            double[][] data) {
    345
    346            DefaultCategoryDataset result = new DefaultCategoryDataset();
    347            for (int r = 0; r < data.length; r++) {
    348                String rowKey = rowKeyPrefix + (r + 1);
    349                for (int c = 0; c < data[r].length; c++) {
    350                    String columnKey = columnKeyPrefix + (c + 1);
    351                    result.addValue(new Double(data[r][c]), rowKey, columnKey);
    352                }
    353            }
    354            return result;
    355
    356        }
    357
    358        /**
    359         * Creates a {@link CategoryDataset} that contains a copy of the data in
    360         * an array.
    361         * <p>
    362         * Row and column keys are created by appending 0, 1, 2, ... to the
    363         * supplied prefixes.
    364         *
    365         * @param rowKeyPrefix  the row key prefix.
    366         * @param columnKeyPrefix  the column key prefix.
    367         * @param data  the data.
    368         *
    369         * @return The dataset.
    370         */
    371        public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
    372                                                            String columnKeyPrefix,
    373                                                            Number[][] data) {
    374
    375            DefaultCategoryDataset result = new DefaultCategoryDataset();
    376            for (int r = 0; r < data.length; r++) {
    377                String rowKey = rowKeyPrefix + (r + 1);
    378                for (int c = 0; c < data[r].length; c++) {
    379                    String columnKey = columnKeyPrefix + (c + 1);
    380                    result.addValue(data[r][c], rowKey, columnKey);
    381                }
    382            }
    383            return result;
    384
    385        }
    386
    387        /**
    388         * Creates a {@link CategoryDataset} that contains a copy of the data in
    389         * an array (instances of <code>Double</code> are created to represent the
    390         * data items).
    391         * <p>
    392         * Row and column keys are taken from the supplied arrays.
    393         *
    394         * @param rowKeys  the row keys (<code>null</code> not permitted).
    395         * @param columnKeys  the column keys (<code>null</code> not permitted).
    396         * @param data  the data.
    397         *
    398         * @return The dataset.
    399         */
    400        public static CategoryDataset createCategoryDataset(Comparable[] rowKeys,
    401                                                            Comparable[] columnKeys,
    402                                                            double[][] data) {
    403
    404            // check arguments...
    405            if (rowKeys == null) {
    406                throw new IllegalArgumentException("Null 'rowKeys' argument.");
    407            }
    408            if (columnKeys == null) {
    409                throw new IllegalArgumentException("Null 'columnKeys' argument.");
    410            }
    411            if (ArrayUtilities.hasDuplicateItems(rowKeys)) {
    412                throw new IllegalArgumentException("Duplicate items in 'rowKeys'.");
    413            }
    414            if (ArrayUtilities.hasDuplicateItems(columnKeys)) {
    415                throw new IllegalArgumentException(
    416                    "Duplicate items in 'columnKeys'."
    417                );
    418            }
    419            if (rowKeys.length != data.length) {
    420                throw new IllegalArgumentException(
    421                    "The number of row keys does not match the number of rows in "
    422                    + "the data array."
    423                );
    424            }
    425            int columnCount = 0;
    426            for (int r = 0; r < data.length; r++) {
    427                columnCount = Math.max(columnCount, data[r].length);
    428            }
    429            if (columnKeys.length != columnCount) {
    430                throw new IllegalArgumentException(
    431                    "The number of column keys does not match the number of "
    432                    + "columns in the data array."
    433                );
    434            }
    435
    436            // now do the work...
    437            DefaultCategoryDataset result = new DefaultCategoryDataset();
    438            for (int r = 0; r < data.length; r++) {
    439                Comparable rowKey = rowKeys[r];
    440                for (int c = 0; c < data[r].length; c++) {
    441                    Comparable columnKey = columnKeys[c];
    442                    result.addValue(new Double(data[r][c]), rowKey, columnKey);
    443                }
    444            }
    445            return result;
    446
    447        }
    448
    449        /**
    450         * Creates a {@link CategoryDataset} by copying the data from the supplied
    451         * {@link KeyedValues} instance.
    452         *
    453         * @param rowKey  the row key (<code>null</code> not permitted).
    454         * @param rowData  the row data (<code>null</code> not permitted).
    455         *
    456         * @return A dataset.
    457         */
    458        public static CategoryDataset createCategoryDataset(Comparable rowKey,
    459                                                            KeyedValues rowData) {
    460
    461            if (rowKey == null) {
    462                throw new IllegalArgumentException("Null 'rowKey' argument.");
    463            }
    464            if (rowData == null) {
    465                throw new IllegalArgumentException("Null 'rowData' argument.");
    466            }
    467            DefaultCategoryDataset result = new DefaultCategoryDataset();
    468            for (int i = 0; i < rowData.getItemCount(); i++) {
    469                result.addValue(rowData.getValue(i), rowKey, rowData.getKey(i));
    470            }
    471            return result;
    472
    473        }
    474
    475        /**
    476         * Creates an {@link XYDataset} by sampling the specified function over a
    477         * fixed range.
    478         *
    479         * @param f  the function (<code>null</code> not permitted).
    480         * @param start  the start value for the range.
    481         * @param end  the end value for the range.
    482         * @param samples  the number of sample points (must be > 1).
    483         * @param seriesKey  the key to give the resulting series
    484         *                   (<code>null</code> not permitted).
    485         *
    486         * @return A dataset.
    487         */
    488        public static XYDataset sampleFunction2D(Function2D f,
    489                                                 double start,
    490                                                 double end,
    491                                                 int samples,
    492                                                 Comparable seriesKey) {
    493
    494            if (f == null) {
    495                throw new IllegalArgumentException("Null 'f' argument.");
    496            }
    497            if (seriesKey == null) {
    498                throw new IllegalArgumentException("Null 'seriesKey' argument.");
    499            }
    500            if (start >= end) {
    501                throw new IllegalArgumentException("Requires 'start' < 'end'.");
    502            }
    503            if (samples < 2) {
    504                throw new IllegalArgumentException("Requires 'samples' > 1");
    505            }
    506
    507            XYSeries series = new XYSeries(seriesKey);
    508            double step = (end - start) / samples;
    509            for (int i = 0; i <= samples; i++) {
    510                double x = start + (step * i);
    511                series.add(x, f.getValue(x));
    512            }
    513            XYSeriesCollection collection = new XYSeriesCollection(series);
    514            return collection;
    515
    516        }
    517
    518        /**
    519         * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
    520         * and <code>false</code> otherwise.
    521         *
    522         * @param dataset  the dataset (<code>null</code> permitted).
    523         *
    524         * @return A boolean.
    525         */
    526        public static boolean isEmptyOrNull(PieDataset dataset) {
    527
    528            if (dataset == null) {
    529                return true;
    530            }
    531
    532            int itemCount = dataset.getItemCount();
    533            if (itemCount == 0) {
    534                return true;
    535            }
    536
    537            for (int item = 0; item < itemCount; item++) {
    538                Number y = dataset.getValue(item);
    539                if (y != null) {
    540                    double yy = y.doubleValue();
    541                    if (yy > 0.0) {
    542                        return false;
    543                    }
    544                }
    545            }
    546
    547            return true;
    548
    549        }
    550
    551        /**
    552         * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
    553         * and <code>false</code> otherwise.
    554         *
    555         * @param dataset  the dataset (<code>null</code> permitted).
    556         *
    557         * @return A boolean.
    558         */
    559        public static boolean isEmptyOrNull(CategoryDataset dataset) {
    560
    561            if (dataset == null) {
    562                return true;
    563            }
    564
    565            int rowCount = dataset.getRowCount();
    566            int columnCount = dataset.getColumnCount();
    567            if (rowCount == 0 || columnCount == 0) {
    568                return true;
    569            }
    570
    571            for (int r = 0; r < rowCount; r++) {
    572                for (int c = 0; c < columnCount; c++) {
    573                    if (dataset.getValue(r, c) != null) {
    574                        return false;
    575                    }
    576
    577                }
    578            }
    579
    580            return true;
    581
    582        }
    583
    584        /**
    585         * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
    586         * and <code>false</code> otherwise.
    587         *
    588         * @param dataset  the dataset (<code>null</code> permitted).
    589         *
    590         * @return A boolean.
    591         */
    592        public static boolean isEmptyOrNull(XYDataset dataset) {
    593            if (dataset != null) {
    594                for (int s = 0; s < dataset.getSeriesCount(); s++) {
    595                    if (dataset.getItemCount(s) > 0) {
    596                        return false;
    597                    }
    598                }
    599            }
    600            return true;
    601        }
    602
    603        /**
    604         * Returns the range of values in the domain (x-values) of a dataset.
    605         *
    606         * @param dataset  the dataset (<code>null</code> not permitted).
    607         *
    608         * @return The range of values (possibly <code>null</code>).
    609         */
    610        public static Range findDomainBounds(XYDataset dataset) {
    611            return findDomainBounds(dataset, true);
    612        }
    613
    614        /**
    615         * Returns the range of values in the domain (x-values) of a dataset.
    616         *
    617         * @param dataset  the dataset (<code>null</code> not permitted).
    618         * @param includeInterval  determines whether or not the x-interval is taken
    619         *                         into account (only applies if the dataset is an
    620         *                         {@link IntervalXYDataset}).
    621         *
    622         * @return The range of values (possibly <code>null</code>).
    623         */
    624        public static Range findDomainBounds(XYDataset dataset,
    625                                             boolean includeInterval) {
    626
    627            if (dataset == null) {
    628                throw new IllegalArgumentException("Null 'dataset' argument.");
    629            }
    630
    631            Range result = null;
    632            // if the dataset implements DomainInfo, life is easier
    633            if (dataset instanceof DomainInfo) {
    634                DomainInfo info = (DomainInfo) dataset;
    635                result = info.getDomainBounds(includeInterval);
    636            }
    637            else {
    638                result = iterateDomainBounds(dataset, includeInterval);
    639            }
    640            return result;
    641
    642        }
    643
    644        /**
    645         * Iterates over the items in an {@link XYDataset} to find
    646         * the range of x-values.
    647         *
    648         * @param dataset  the dataset (<code>null</code> not permitted).
    649         *
    650         * @return The range (possibly <code>null</code>).
    651         */
    652        public static Range iterateDomainBounds(XYDataset dataset) {
    653            return iterateDomainBounds(dataset, true);
    654        }
    655
    656        /**
    657         * Iterates over the items in an {@link XYDataset} to find
    658         * the range of x-values.
    659         *
    660         * @param dataset  the dataset (<code>null</code> not permitted).
    661         * @param includeInterval  a flag that determines, for an IntervalXYDataset,
    662         *                         whether the x-interval or just the x-value is
    663         *                         used to determine the overall range.
    664         *
    665         * @return The range (possibly <code>null</code>).
    666         */
    667        public static Range iterateDomainBounds(XYDataset dataset,
    668                                                boolean includeInterval) {
    669            if (dataset == null) {
    670                throw new IllegalArgumentException("Null 'dataset' argument.");
    671            }
    672            double minimum = Double.POSITIVE_INFINITY;
    673            double maximum = Double.NEGATIVE_INFINITY;
    674            int seriesCount = dataset.getSeriesCount();
    675            double lvalue;
    676            double uvalue;
    677            if (includeInterval && dataset instanceof IntervalXYDataset) {
    678                IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
    679                for (int series = 0; series < seriesCount; series++) {
    680                    int itemCount = dataset.getItemCount(series);
    681                    for (int item = 0; item < itemCount; item++) {
    682                        lvalue = intervalXYData.getStartXValue(series, item);
    683                        uvalue = intervalXYData.getEndXValue(series, item);
    684                        minimum = Math.min(minimum, lvalue);
    685                        maximum = Math.max(maximum, uvalue);
    686                    }
    687                }
    688            }
    689            else {
    690                for (int series = 0; series < seriesCount; series++) {
    691                    int itemCount = dataset.getItemCount(series);
    692                    for (int item = 0; item < itemCount; item++) {
    693                        lvalue = dataset.getXValue(series, item);
    694                        uvalue = lvalue;
    695                        minimum = Math.min(minimum, lvalue);
    696                        maximum = Math.max(maximum, uvalue);
    697                    }
    698                }
    699            }
    700            if (minimum > maximum) {
    701                return null;
    702            }
    703            else {
    704                return new Range(minimum, maximum);
    705            }
    706        }
    707
    708        /**
    709         * Returns the range of values in the range for the dataset.
    710         *
    711         * @param dataset  the dataset (<code>null</code> not permitted).
    712         *
    713         * @return The range (possibly <code>null</code>).
    714         */
    715        public static Range findRangeBounds(CategoryDataset dataset) {
    716            return findRangeBounds(dataset, true);
    717        }
    718
    719        /**
    720         * Returns the range of values in the range for the dataset.
    721         *
    722         * @param dataset  the dataset (<code>null</code> not permitted).
    723         * @param includeInterval  a flag that determines whether or not the
    724         *                         y-interval is taken into account.
    725         *
    726         * @return The range (possibly <code>null</code>).
    727         */
    728        public static Range findRangeBounds(CategoryDataset dataset,
    729                                            boolean includeInterval) {
    730            if (dataset == null) {
    731                throw new IllegalArgumentException("Null 'dataset' argument.");
    732            }
    733            Range result = null;
    734            if (dataset instanceof RangeInfo) {
    735                RangeInfo info = (RangeInfo) dataset;
    736                result = info.getRangeBounds(includeInterval);
    737            }
    738            else {
    739                result = iterateCategoryRangeBounds(dataset, includeInterval);
    740            }
    741            return result;
    742        }
    743
    744        /**
    745         * Returns the range of values in the range for the dataset.  This method
    746         * is the partner for the {@link #findDomainBounds(XYDataset)} method.
    747         *
    748         * @param dataset  the dataset (<code>null</code> not permitted).
    749         *
    750         * @return The range (possibly <code>null</code>).
    751         */
    752        public static Range findRangeBounds(XYDataset dataset) {
    753            return findRangeBounds(dataset, true);
    754        }
    755
    756        /**
    757         * Returns the range of values in the range for the dataset.  This method
    758         * is the partner for the {@link #findDomainBounds(XYDataset)} method.
    759         *
    760         * @param dataset  the dataset (<code>null</code> not permitted).
    761         * @param includeInterval  a flag that determines whether or not the
    762         *                         y-interval is taken into account.
    763         *
    764         *
    765         * @return The range (possibly <code>null</code>).
    766         */
    767        public static Range findRangeBounds(XYDataset dataset,
    768                                            boolean includeInterval) {
    769            if (dataset == null) {
    770                throw new IllegalArgumentException("Null 'dataset' argument.");
    771            }
    772            Range result = null;
    773            if (dataset instanceof RangeInfo) {
    774                RangeInfo info = (RangeInfo) dataset;
    775                result = info.getRangeBounds(includeInterval);
    776            }
    777            else {
    778                result = iterateXYRangeBounds(dataset);
    779            }
    780            return result;
    781        }
    782
    783        /**
    784         * Iterates over the data item of the category dataset to find
    785         * the range bounds.
    786         *
    787         * @param dataset  the dataset (<code>null</code> not permitted).
    788         * @param includeInterval  a flag that determines whether or not the
    789         *                         y-interval is taken into account.
    790         *
    791         * @return The range (possibly <code>null</code>).
    792         */
    793        public static Range iterateCategoryRangeBounds(CategoryDataset dataset,
    794                boolean includeInterval) {
    795            double minimum = Double.POSITIVE_INFINITY;
    796            double maximum = Double.NEGATIVE_INFINITY;
    797            boolean interval = includeInterval
    798                               && dataset instanceof IntervalCategoryDataset;
    799            int rowCount = dataset.getRowCount();
    800            int columnCount = dataset.getColumnCount();
    801            for (int row = 0; row < rowCount; row++) {
    802                for (int column = 0; column < columnCount; column++) {
    803                    Number lvalue;
    804                    Number uvalue;
    805                    if (interval) {
    806                        IntervalCategoryDataset icd
    807                            = (IntervalCategoryDataset) dataset;
    808                        lvalue = icd.getStartValue(row, column);
    809                        uvalue = icd.getEndValue(row, column);
    810                    }
    811                    else {
    812                        lvalue = dataset.getValue(row, column);
    813                        uvalue = lvalue;
    814                    }
    815                    if (lvalue != null) {
    816                        minimum = Math.min(minimum, lvalue.doubleValue());
    817                    }
    818                    if (uvalue != null) {
    819                        maximum = Math.max(maximum, uvalue.doubleValue());
    820                    }
    821                }
    822            }
    823            if (minimum == Double.POSITIVE_INFINITY) {
    824                return null;
    825            }
    826            else {
    827                return new Range(minimum, maximum);
    828            }
    829        }
    830
    831        /**
    832         * Iterates over the data item of the xy dataset to find
    833         * the range bounds.
    834         *
    835         * @param dataset  the dataset (<code>null</code> not permitted).
    836         *
    837         * @return The range (possibly <code>null</code>).
    838         */
    839        public static Range iterateXYRangeBounds(XYDataset dataset) {
    840            double minimum = Double.POSITIVE_INFINITY;
    841            double maximum = Double.NEGATIVE_INFINITY;
    842            int seriesCount = dataset.getSeriesCount();
    843            for (int series = 0; series < seriesCount; series++) {
    844                int itemCount = dataset.getItemCount(series);
    845                for (int item = 0; item < itemCount; item++) {
    846                    double lvalue;
    847                    double uvalue;
    848                    if (dataset instanceof IntervalXYDataset) {
    849                        IntervalXYDataset intervalXYData
    850                            = (IntervalXYDataset) dataset;
    851                        lvalue = intervalXYData.getStartYValue(series, item);
    852                        uvalue = intervalXYData.getEndYValue(series, item);
    853                    }
    854                    else if (dataset instanceof OHLCDataset) {
    855                        OHLCDataset highLowData = (OHLCDataset) dataset;
    856                        lvalue = highLowData.getLowValue(series, item);
    857                        uvalue = highLowData.getHighValue(series, item);
    858                    }
    859                    else {
    860                        lvalue = dataset.getYValue(series, item);
    861                        uvalue = lvalue;
    862                    }
    863                    if (!Double.isNaN(lvalue)) {
    864                        minimum = Math.min(minimum, lvalue);
    865                    }
    866                    if (!Double.isNaN(uvalue)) {
    867                        maximum = Math.max(maximum, uvalue);
    868                    }
    869                }
    870            }
    871            if (minimum == Double.POSITIVE_INFINITY) {
    872                return null;
    873            }
    874            else {
    875                return new Range(minimum, maximum);
    876            }
    877        }
    878
    879        /**
    880         * Finds the minimum domain (or X) value for the specified dataset.  This
    881         * is easy if the dataset implements the {@link DomainInfo} interface (a
    882         * good idea if there is an efficient way to determine the minimum value).
    883         * Otherwise, it involves iterating over the entire data-set.
    884         * <p>
    885         * Returns <code>null</code> if all the data values in the dataset are
    886         * <code>null</code>.
    887         *
    888         * @param dataset  the dataset (<code>null</code> not permitted).
    889         *
    890         * @return The minimum value (possibly <code>null</code>).
    891         */
    892        public static Number findMinimumDomainValue(XYDataset dataset) {
    893            if (dataset == null) {
    894                throw new IllegalArgumentException("Null 'dataset' argument.");
    895            }
    896            Number result = null;
    897            // if the dataset implements DomainInfo, life is easy
    898            if (dataset instanceof DomainInfo) {
    899                DomainInfo info = (DomainInfo) dataset;
    900                return new Double(info.getDomainLowerBound(true));
    901            }
    902            else {
    903                double minimum = Double.POSITIVE_INFINITY;
    904                int seriesCount = dataset.getSeriesCount();
    905                for (int series = 0; series < seriesCount; series++) {
    906                    int itemCount = dataset.getItemCount(series);
    907                    for (int item = 0; item < itemCount; item++) {
    908
    909                        double value;
    910                        if (dataset instanceof IntervalXYDataset) {
    911                            IntervalXYDataset intervalXYData
    912                                = (IntervalXYDataset) dataset;
    913                            value = intervalXYData.getStartXValue(series, item);
    914                        }
    915                        else {
    916                            value = dataset.getXValue(series, item);
    917                        }
    918                        if (!Double.isNaN(value)) {
    919                            minimum = Math.min(minimum, value);
    920                        }
    921
    922                    }
    923                }
    924                if (minimum == Double.POSITIVE_INFINITY) {
    925                    result = null;
    926                }
    927                else {
    928                    result = new Double(minimum);
    929                }
    930            }
    931
    932            return result;
    933        }
    934
    935        /**
    936         * Returns the maximum domain value for the specified dataset.  This is
    937         * easy if the dataset implements the {@link DomainInfo} interface (a good
    938         * idea if there is an efficient way to determine the maximum value).
    939         * Otherwise, it involves iterating over the entire data-set.  Returns
    940         * <code>null</code> if all the data values in the dataset are
    941         * <code>null</code>.
    942         *
    943         * @param dataset  the dataset (<code>null</code> not permitted).
    944         *
    945         * @return The maximum value (possibly <code>null</code>).
    946         */
    947        public static Number findMaximumDomainValue(XYDataset dataset) {
    948            if (dataset == null) {
    949                throw new IllegalArgumentException("Null 'dataset' argument.");
    950            }
    951            Number result = null;
    952            // if the dataset implements DomainInfo, life is easy
    953            if (dataset instanceof DomainInfo) {
    954                DomainInfo info = (DomainInfo) dataset;
    955                return new Double(info.getDomainUpperBound(true));
    956            }
    957
    958            // hasn't implemented DomainInfo, so iterate...
    959            else {
    960                double maximum = Double.NEGATIVE_INFINITY;
    961                int seriesCount = dataset.getSeriesCount();
    962                for (int series = 0; series < seriesCount; series++) {
    963                    int itemCount = dataset.getItemCount(series);
    964                    for (int item = 0; item < itemCount; item++) {
    965
    966                        double value;
    967                        if (dataset instanceof IntervalXYDataset) {
    968                            IntervalXYDataset intervalXYData
    969                                = (IntervalXYDataset) dataset;
    970                            value = intervalXYData.getEndXValue(series, item);
    971                        }
    972                        else {
    973                            value = dataset.getXValue(series, item);
    974                        }
    975                        if (!Double.isNaN(value)) {
    976                            maximum = Math.max(maximum, value);
    977                        }
    978                    }
    979                }
    980                if (maximum == Double.NEGATIVE_INFINITY) {
    981                    result = null;
    982                }
    983                else {
    984                    result = new Double(maximum);
    985                }
    986
    987            }
    988
    989            return result;
    990        }
    991
    992        /**
    993         * Returns the minimum range value for the specified dataset.  This is
    994         * easy if the dataset implements the {@link RangeInfo} interface (a good
    995         * idea if there is an efficient way to determine the minimum value).
    996         * Otherwise, it involves iterating over the entire data-set.  Returns
    997         * <code>null</code> if all the data values in the dataset are
    998         * <code>null</code>.
    999         *
    1000         * @param dataset  the dataset (<code>null</code> not permitted).
    1001         *
    1002         * @return The minimum value (possibly <code>null</code>).
    1003         */
    1004        public static Number findMinimumRangeValue(CategoryDataset dataset) {
    1005
    1006            // check parameters...
    1007            if (dataset == null) {
    1008                throw new IllegalArgumentException("Null 'dataset' argument.");
    1009            }
    1010
    1011            // work out the minimum value...
    1012            if (dataset instanceof RangeInfo) {
    1013                RangeInfo info = (RangeInfo) dataset;
    1014                return new Double(info.getRangeLowerBound(true));
    1015            }
    1016
    1017            // hasn't implemented RangeInfo, so we'll have to iterate...
    1018            else {
    1019                double minimum = Double.POSITIVE_INFINITY;
    1020                int seriesCount = dataset.getRowCount();
    1021                int itemCount = dataset.getColumnCount();
    1022                for (int series = 0; series < seriesCount; series++) {
    1023                    for (int item = 0; item < itemCount; item++) {
    1024                        Number value;
    1025                        if (dataset instanceof IntervalCategoryDataset) {
    1026                            IntervalCategoryDataset icd
    1027                                = (IntervalCategoryDataset) dataset;
    1028                            value = icd.getStartValue(series, item);
    1029                        }
    1030                        else {
    1031                            value = dataset.getValue(series, item);
    1032                        }
    1033                        if (value != null) {
    1034                            minimum = Math.min(minimum, value.doubleValue());
    1035                        }
    1036                    }
    1037                }
    1038                if (minimum == Double.POSITIVE_INFINITY) {
    1039                    return null;
    1040                }
    1041                else {
    1042                    return new Double(minimum);
    1043                }
    1044
    1045            }
    1046
    1047        }
    1048
    1049        /**
    1050         * Returns the minimum range value for the specified dataset.  This is
    1051         * easy if the dataset implements the {@link RangeInfo} interface (a good
    1052         * idea if there is an efficient way to determine the minimum value).
    1053         * Otherwise, it involves iterating over the entire data-set.  Returns
    1054         * <code>null</code> if all the data values in the dataset are
    1055         * <code>null</code>.
    1056         *
    1057         * @param dataset  the dataset (<code>null</code> not permitted).
    1058         *
    1059         * @return The minimum value (possibly <code>null</code>).
    1060         */
    1061        public static Number findMinimumRangeValue(XYDataset dataset) {
    1062
    1063            if (dataset == null) {
    1064                throw new IllegalArgumentException("Null 'dataset' argument.");
    1065            }
    1066
    1067            // work out the minimum value...
    1068            if (dataset instanceof RangeInfo) {
    1069                RangeInfo info = (RangeInfo) dataset;
    1070                return new Double(info.getRangeLowerBound(true));
    1071            }
    1072
    1073            // hasn't implemented RangeInfo, so we'll have to iterate...
    1074            else {
    1075                double minimum = Double.POSITIVE_INFINITY;
    1076                int seriesCount = dataset.getSeriesCount();
    1077                for (int series = 0; series < seriesCount; series++) {
    1078                    int itemCount = dataset.getItemCount(series);
    1079                    for (int item = 0; item < itemCount; item++) {
    1080
    1081                        double value;
    1082                        if (dataset instanceof IntervalXYDataset) {
    1083                            IntervalXYDataset intervalXYData
    1084                                = (IntervalXYDataset) dataset;
    1085                            value = intervalXYData.getStartYValue(series, item);
    1086                        }
    1087                        else if (dataset instanceof OHLCDataset) {
    1088                            OHLCDataset highLowData = (OHLCDataset) dataset;
    1089                            value = highLowData.getLowValue(series, item);
    1090                        }
    1091                        else {
    1092                            value = dataset.getYValue(series, item);
    1093                        }
    1094                        if (!Double.isNaN(value)) {
    1095                            minimum = Math.min(minimum, value);
    1096                        }
    1097
    1098                    }
    1099                }
    1100                if (minimum == Double.POSITIVE_INFINITY) {
    1101                    return null;
    1102                }
    1103                else {
    1104                    return new Double(minimum);
    1105                }
    1106
    1107            }
    1108
    1109        }
    1110
    1111        /**
    1112         * Returns the maximum range value for the specified dataset.  This is easy
    1113         * if the dataset implements the {@link RangeInfo} interface (a good idea
    1114         * if there is an efficient way to determine the maximum value).
    1115         * Otherwise, it involves iterating over the entire data-set.  Returns
    1116         * <code>null</code> if all the data values are <code>null</code>.
    1117         *
    1118         * @param dataset  the dataset (<code>null</code> not permitted).
    1119         *
    1120         * @return The maximum value (possibly <code>null</code>).
    1121         */
    1122        public static Number findMaximumRangeValue(CategoryDataset dataset) {
    1123
    1124            if (dataset == null) {
    1125                throw new IllegalArgumentException("Null 'dataset' argument.");
    1126            }
    1127
    1128            // work out the minimum value...
    1129            if (dataset instanceof RangeInfo) {
    1130                RangeInfo info = (RangeInfo) dataset;
    1131                return new Double(info.getRangeUpperBound(true));
    1132            }
    1133
    1134            // hasn't implemented RangeInfo, so we'll have to iterate...
    1135            else {
    1136
    1137                double maximum = Double.NEGATIVE_INFINITY;
    1138                int seriesCount = dataset.getRowCount();
    1139                int itemCount = dataset.getColumnCount();
    1140                for (int series = 0; series < seriesCount; series++) {
    1141                    for (int item = 0; item < itemCount; item++) {
    1142                        Number value;
    1143                        if (dataset instanceof IntervalCategoryDataset) {
    1144                            IntervalCategoryDataset icd
    1145                                = (IntervalCategoryDataset) dataset;
    1146                            value = icd.getEndValue(series, item);
    1147                        }
    1148                        else {
    1149                            value = dataset.getValue(series, item);
    1150                        }
    1151                        if (value != null) {
    1152                            maximum = Math.max(maximum, value.doubleValue());
    1153                        }
    1154                    }
    1155                }
    1156                if (maximum == Double.NEGATIVE_INFINITY) {
    1157                    return null;
    1158                }
    1159                else {
    1160                    return new Double(maximum);
    1161                }
    1162
    1163            }
    1164
    1165        }
    1166
    1167        /**
    1168         * Returns the maximum range value for the specified dataset.  This is
    1169         * easy if the dataset implements the {@link RangeInfo} interface (a good
    1170         * idea if there is an efficient way to determine the maximum value).
    1171         * Otherwise, it involves iterating over the entire data-set.  Returns
    1172         * <code>null</code> if all the data values are <code>null</code>.
    1173         *
    1174         * @param dataset  the dataset (<code>null</code> not permitted).
    1175         *
    1176         * @return The maximum value (possibly <code>null</code>).
    1177         */
    1178        public static Number findMaximumRangeValue(XYDataset dataset) {
    1179
    1180            if (dataset == null) {
    1181                throw new IllegalArgumentException("Null 'dataset' argument.");
    1182            }
    1183
    1184            // work out the minimum value...
    1185            if (dataset instanceof RangeInfo) {
    1186                RangeInfo info = (RangeInfo) dataset;
    1187                return new Double(info.getRangeUpperBound(true));
    1188            }
    1189
    1190            // hasn't implemented RangeInfo, so we'll have to iterate...
    1191            else  {
    1192
    1193                double maximum = Double.NEGATIVE_INFINITY;
    1194                int seriesCount = dataset.getSeriesCount();
    1195                for (int series = 0; series < seriesCount; series++) {
    1196                    int itemCount = dataset.getItemCount(series);
    1197                    for (int item = 0; item < itemCount; item++) {
    1198                        double value;
    1199                        if (dataset instanceof IntervalXYDataset) {
    1200                            IntervalXYDataset intervalXYData
    1201                                = (IntervalXYDataset) dataset;
    1202                            value = intervalXYData.getEndYValue(series, item);
    1203                        }
    1204                        else if (dataset instanceof OHLCDataset) {
    1205                            OHLCDataset highLowData = (OHLCDataset) dataset;
    1206                            value = highLowData.getHighValue(series, item);
    1207                        }
    1208                        else {
    1209                            value = dataset.getYValue(series, item);
    1210                        }
    1211                        if (!Double.isNaN(value)) {
    1212                            maximum = Math.max(maximum, value);
    1213                        }
    1214                    }
    1215                }
    1216                if (maximum == Double.NEGATIVE_INFINITY) {
    1217                    return null;
    1218                }
    1219                else {
    1220                    return new Double(maximum);
    1221                }
    1222
    1223            }
    1224
    1225        }
    1226
    1227        /**
    1228         * Returns the minimum and maximum values for the dataset's range
    1229         * (y-values), assuming that the series in one category are stacked.
    1230         *
    1231         * @param dataset  the dataset (<code>null</code> not permitted).
    1232         *
    1233         * @return The range (<code>null</code> if the dataset contains no values).
    1234         */
    1235        public static Range findStackedRangeBounds(CategoryDataset dataset) {
    1236            return findStackedRangeBounds(dataset, 0.0);
    1237        }
    1238
    1239        /**
    1240         * Returns the minimum and maximum values for the dataset's range
    1241         * (y-values), assuming that the series in one category are stacked.
    1242         *
    1243         * @param dataset  the dataset (<code>null</code> not permitted).
    1244         * @param base  the base value for the bars.
    1245         *
    1246         * @return The range (<code>null</code> if the dataset contains no values).
    1247         */
    1248        public static Range findStackedRangeBounds(CategoryDataset dataset,
    1249                double base) {
    1250            if (dataset == null) {
    1251                throw new IllegalArgumentException("Null 'dataset' argument.");
    1252            }
    1253            Range result = null;
    1254            double minimum = Double.POSITIVE_INFINITY;
    1255            double maximum = Double.NEGATIVE_INFINITY;
    1256            int categoryCount = dataset.getColumnCount();
    1257            for (int item = 0; item < categoryCount; item++) {
    1258                double positive = base;
    1259                double negative = base;
    1260                int seriesCount = dataset.getRowCount();
    1261                for (int series = 0; series < seriesCount; series++) {
    1262                    Number number = dataset.getValue(series, item);
    1263                    if (number != null) {
    1264                        double value = number.doubleValue();
    1265                        if (value > 0.0) {
    1266                            positive = positive + value;
    1267                        }
    1268                        if (value < 0.0) {
    1269                            negative = negative + value;
    1270                            // '+', remember value is negative
    1271                        }
    1272                    }
    1273                }
    1274                minimum = Math.min(minimum, negative);
    1275                maximum = Math.max(maximum, positive);
    1276            }
    1277            if (minimum <= maximum) {
    1278                result = new Range(minimum, maximum);
    1279            }
    1280            return result;
    1281
    1282        }
    1283
    1284        /**
    1285         * Returns the minimum and maximum values for the dataset's range
    1286         * (y-values), assuming that the series in one category are stacked.
    1287         *
    1288         * @param dataset  the dataset.
    1289         * @param map  a structure that maps series to groups.
    1290         *
    1291         * @return The value range (<code>null</code> if the dataset contains no
    1292         *         values).
    1293         */
    1294        public static Range findStackedRangeBounds(CategoryDataset dataset,
    1295                                                   KeyToGroupMap map) {
    1296
    1297            Range result = null;
    1298            if (dataset != null) {
    1299
    1300                // create an array holding the group indices...
    1301                int[] groupIndex = new int[dataset.getRowCount()];
    1302                for (int i = 0; i < dataset.getRowCount(); i++) {
    1303                    groupIndex[i] = map.getGroupIndex(
    1304                        map.getGroup(dataset.getRowKey(i))
    1305                    );
    1306                }
    1307
    1308                // minimum and maximum for each group...
    1309                int groupCount = map.getGroupCount();
    1310                double[] minimum = new double[groupCount];
    1311                double[] maximum = new double[groupCount];
    1312
    1313                int categoryCount = dataset.getColumnCount();
    1314                for (int item = 0; item < categoryCount; item++) {
    1315                    double[] positive = new double[groupCount];
    1316                    double[] negative = new double[groupCount];
    1317                    int seriesCount = dataset.getRowCount();
    1318                    for (int series = 0; series < seriesCount; series++) {
    1319                        Number number = dataset.getValue(series, item);
    1320                        if (number != null) {
    1321                            double value = number.doubleValue();
    1322                            if (value > 0.0) {
    1323                                positive[groupIndex[series]]
    1324                                     = positive[groupIndex[series]] + value;
    1325                            }
    1326                            if (value < 0.0) {
    1327                                negative[groupIndex[series]]
    1328                                     = negative[groupIndex[series]] + value;
    1329                                     // '+', remember value is negative
    1330                            }
    1331                        }
    1332                    }
    1333                    for (int g = 0; g < groupCount; g++) {
    1334                        minimum[g] = Math.min(minimum[g], negative[g]);
    1335                        maximum[g] = Math.max(maximum[g], positive[g]);
    1336                    }
    1337                }
    1338                for (int j = 0; j < groupCount; j++) {
    1339                    result = Range.combine(
    1340                        result, new Range(minimum[j], maximum[j])
    1341                    );
    1342                }
    1343            }
    1344            return result;
    1345
    1346        }
    1347
    1348        /**
    1349         * Returns the minimum value in the dataset range, assuming that values in
    1350         * each category are "stacked".
    1351         *
    1352         * @param dataset  the dataset.
    1353         *
    1354         * @return The minimum value.
    1355         */
    1356        public static Number findMinimumStackedRangeValue(CategoryDataset dataset) {
    1357
    1358            Number result = null;
    1359            if (dataset != null) {
    1360                double minimum = 0.0;
    1361                int categoryCount = dataset.getRowCount();
    1362                for (int item = 0; item < categoryCount; item++) {
    1363                    double total = 0.0;
    1364
    1365                    int seriesCount = dataset.getColumnCount();
    1366                    for (int series = 0; series < seriesCount; series++) {
    1367                        Number number = dataset.getValue(series, item);
    1368                        if (number != null) {
    1369                            double value = number.doubleValue();
    1370                            if (value < 0.0) {
    1371                                total = total + value;
    1372                                // '+', remember value is negative
    1373                            }
    1374                        }
    1375                    }
    1376                    minimum = Math.min(minimum, total);
    1377
    1378                }
    1379                result = new Double(minimum);
    1380            }
    1381            return result;
    1382
    1383        }
    1384
    1385        /**
    1386         * Returns the maximum value in the dataset range, assuming that values in
    1387         * each category are "stacked".
    1388         *
    1389         * @param dataset  the dataset (<code>null</code> permitted).
    1390         *
    1391         * @return The maximum value (possibly <code>null</code>).
    1392         */
    1393        public static Number findMaximumStackedRangeValue(CategoryDataset dataset) {
    1394
    1395            Number result = null;
    1396
    1397            if (dataset != null) {
    1398                double maximum = 0.0;
    1399                int categoryCount = dataset.getColumnCount();
    1400                for (int item = 0; item < categoryCount; item++) {
    1401                    double total = 0.0;
    1402                    int seriesCount = dataset.getRowCount();
    1403                    for (int series = 0; series < seriesCount; series++) {
    1404                        Number number = dataset.getValue(series, item);
    1405                        if (number != null) {
    1406                            double value = number.doubleValue();
    1407                            if (value > 0.0) {
    1408                                total = total + value;
    1409                            }
    1410                        }
    1411                    }
    1412                    maximum = Math.max(maximum, total);
    1413                }
    1414                result = new Double(maximum);
    1415            }
    1416
    1417            return result;
    1418
    1419        }
    1420
    1421        /**
    1422         * Returns the minimum and maximum values for the dataset's range,
    1423         * assuming that the series are stacked.
    1424         *
    1425         * @param dataset  the dataset (<code>null</code> not permitted).
    1426         *
    1427         * @return The range ([0.0, 0.0] if the dataset contains no values).
    1428         */
    1429        public static Range findStackedRangeBounds(TableXYDataset dataset) {
    1430            return findStackedRangeBounds(dataset, 0.0);
    1431        }
    1432
    1433        /**
    1434         * Returns the minimum and maximum values for the dataset's range,
    1435         * assuming that the series are stacked, using the specified base value.
    1436         *
    1437         * @param dataset  the dataset (<code>null</code> not permitted).
    1438         * @param base  the base value.
    1439         *
    1440         * @return The range (<code>null</code> if the dataset contains no values).
    1441         */
    1442        public static Range findStackedRangeBounds(TableXYDataset dataset,
    1443                                                   double base) {
    1444            if (dataset == null) {
    1445                throw new IllegalArgumentException("Null 'dataset' argument.");
    1446            }
    1447            double minimum = base;
    1448            double maximum = base;
    1449            for (int itemNo = 0; itemNo < dataset.getItemCount(); itemNo++) {
    1450                double positive = base;
    1451                double negative = base;
    1452                int seriesCount = dataset.getSeriesCount();
    1453                for (int seriesNo = 0; seriesNo < seriesCount; seriesNo++) {
    1454                    double y = dataset.getYValue(seriesNo, itemNo);
    1455                    if (!Double.isNaN(y)) {
    1456                        if (y > 0.0) {
    1457                            positive += y;
    1458                        }
    1459                        else {
    1460                            negative += y;
    1461                        }
    1462                    }
    1463                }
    1464                if (positive > maximum) {
    1465                    maximum = positive;
    1466                }
    1467                if (negative < minimum) {
    1468                    minimum = negative;
    1469                }
    1470            }
    1471            if (minimum <= maximum) {
    1472                return new Range(minimum, maximum);
    1473            }
    1474            else {
    1475                return null;
    1476            }
    1477        }
    1478
    1479        /**
    1480         * Calculates the total for the y-values in all series for a given item
    1481         * index.
    1482         *
    1483         * @param dataset  the dataset.
    1484         * @param item  the item index.
    1485         *
    1486         * @return The total.
    1487         *
    1488         * @since 1.0.5
    1489         */
    1490        public static double calculateStackTotal(TableXYDataset dataset, int item) {
    1491            double total = 0.0;
    1492            int seriesCount = dataset.getSeriesCount();
    1493            for (int s = 0; s < seriesCount; s++) {
    1494                double value = dataset.getYValue(s, item);
    1495                if (!Double.isNaN(value)) {
    1496                    total = total + value;
    1497                }
    1498            }
    1499            return total;
    1500        }
    1501
    1502        /**
    1503         * Calculates the range of values for a dataset where each item is the
    1504         * running total of the items for the current series.
    1505         *
    1506         * @param dataset  the dataset (<code>null</code> not permitted).
    1507         *
    1508         * @return The range.
    1509         *
    1510         * @see #findRangeBounds(CategoryDataset)
    1511         */
    1512        public static Range findCumulativeRangeBounds(CategoryDataset dataset) {
    1513
    1514            if (dataset == null) {
    1515                throw new IllegalArgumentException("Null 'dataset' argument.");
    1516            }
    1517
    1518            boolean allItemsNull = true; // we'll set this to false if there is at
    1519                                         // least one non-null data item...
    1520            double minimum = 0.0;
    1521            double maximum = 0.0;
    1522            for (int row = 0; row < dataset.getRowCount(); row++) {
    1523                double runningTotal = 0.0;
    1524                for (int column = 0; column < dataset.getColumnCount() - 1;
    1525                     column++) {
    1526                    Number n = dataset.getValue(row, column);
    1527                    if (n != null) {
    1528                        allItemsNull = false;
    1529                        double value = n.doubleValue();
    1530                        runningTotal = runningTotal + value;
    1531                        minimum = Math.min(minimum, runningTotal);
    1532                        maximum = Math.max(maximum, runningTotal);
    1533                    }
    1534                }
    1535            }
    1536            if (!allItemsNull) {
    1537                return new Range(minimum, maximum);
    1538            }
    1539            else {
    1540                return null;
    1541            }
    1542
    1543        }
    1544
    1545    }