    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2009, 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-2009, 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     *                   Rafal Skalny (patch 1925366);
    038     *                   Jerome David (patch 2131001);
    039     *
    040     * Changes (from 18-Sep-2001)
    041     * --------------------------
    042     * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
    043     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    044     * 15-Nov-2001 : Moved to package com.jrefinery.data.* in the JCommon class
    045     *               library (DG);
    046     *               Changed to handle null values from datasets (DG);
    047     *               Bug fix (thanks to Andrzej Porebski) - initial value now set
    048     *               to positive or negative infinity when iterating (DG);
    049     * 22-Nov-2001 : Datasets with containing no data now return null for min and
    050     *               max calculations (DG);
    051     * 13-Dec-2001 : Extended to handle HighLowDataset and IntervalXYDataset (DG);
    052     * 15-Feb-2002 : Added getMinimumStackedRangeValue() and
    053     *               getMaximumStackedRangeValue() (DG);
    054     * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
    055     * 18-Mar-2002 : Fixed bug in min/max domain calculation for datasets that
    056     *               implement the CategoryDataset interface AND the XYDataset
    057     *               interface at the same time.  Thanks to Jonathan Nash for the
    058     *               fix (DG);
    059     * 23-Apr-2002 : Added getDomainExtent() and getRangeExtent() methods (DG);
    060     * 13-Jun-2002 : Modified range measurements to handle
    061     *               IntervalCategoryDataset (DG);
    062     * 12-Jul-2002 : Method name change in DomainInfo interface (DG);
    063     * 30-Jul-2002 : Added pie dataset summation method (DG);
    064     * 01-Oct-2002 : Added a method for constructing an XYDataset from a Function2D
    065     *               instance (DG);
    066     * 24-Oct-2002 : Amendments required following changes to the CategoryDataset
    067     *               interface (DG);
    068     * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
    069     * 04-Mar-2003 : Added isEmpty(XYDataset) method (DG);
    070     * 05-Mar-2003 : Added a method for creating a CategoryDataset from a
    071     *               KeyedValues instance (DG);
    072     * 15-May-2003 : Renamed isEmpty --> isEmptyOrNull (DG);
    073     * 25-Jun-2003 : Added limitPieDataset methods (RA);
    074     * 26-Jun-2003 : Modified getDomainExtent() method to accept null datasets (DG);
    075     * 27-Jul-2003 : Added getStackedRangeExtent(TableXYDataset data) (RA);
    076     * 18-Aug-2003 : getStackedRangeExtent(TableXYDataset data) now handles null
    077     *               values (RA);
    078     * 02-Sep-2003 : Added method to check for null or empty PieDataset (DG);
    079     * 18-Sep-2003 : Fix for bug 803660 (getMaximumRangeValue for
    080     *               CategoryDataset) (DG);
    081     * 20-Oct-2003 : Added getCumulativeRangeExtent() method (DG);
    082     * 09-Jan-2003 : Added argument checking code to the createCategoryDataset()
    083     *               method (DG);
    084     * 23-Mar-2004 : Fixed bug in getMaximumStackedRangeValue() method (DG);
    085     * 31-Mar-2004 : Exposed the extent iteration algorithms to use one of them and
    086     *               applied noninstantiation pattern (AS);
    087     * 11-May-2004 : Renamed getPieDatasetTotal --> calculatePieDatasetTotal (DG);
    088     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue();
    089     * 24-Aug-2004 : Added argument checks to createCategoryDataset() method (DG);
    090     * 04-Oct-2004 : Renamed ArrayUtils --> ArrayUtilities (DG);
    091     * 06-Oct-2004 : Renamed findDomainExtent() --> findDomainBounds(),
    092     *               findRangeExtent() --> findRangeBounds() (DG);
    093     * 07-Jan-2005 : Renamed findStackedRangeExtent() --> findStackedRangeBounds(),
    094     *               findCumulativeRangeExtent() --> findCumulativeRangeBounds(),
    095     *               iterateXYRangeExtent() --> iterateXYRangeBounds(),
    096     *               removed deprecated methods (DG);
    097     * 03-Feb-2005 : The findStackedRangeBounds() methods now return null for
    098     *               empty datasets (DG);
    099     * 03-Mar-2005 : Moved createNumberArray() and createNumberArray2D() methods
    100     *               from DatasetUtilities --> DataUtilities (DG);
    101     * 22-Sep-2005 : Added new findStackedRangeBounds() method that takes base
    102     *               argument (DG);
    103     * ------------- JFREECHART 1.0.x ---------------------------------------------
    104     * 15-Mar-2007 : Added calculateStackTotal() method (DG);
    105     * 27-Mar-2008 : Fixed bug in findCumulativeRangeBounds() method (DG);
    106     * 28-Mar-2008 : Fixed sample count in sampleFunction2D() method, renamed
    107     *               iterateXYRangeBounds() --> iterateRangeBounds(XYDataset), and
    108     *               fixed a bug in findRangeBounds(XYDataset, false) (DG);
    109     * 28-Mar-2008 : Applied a variation of patch 1925366 (from Rafal Skalny) for
    110     *               slightly more efficient iterateRangeBounds() methods (DG);
    111     * 08-Apr-2008 : Fixed typo in iterateRangeBounds() (DG);
    112     * 08-Oct-2008 : Applied patch 2131001 by Jerome David, with some modifications
    113     *               and additions and some new unit tests (DG);
    114     * 12-Feb-2009 : Added sampleFunction2DToSeries() method (DG);
    115     * 27-Mar-2009 : Added new methods to find domain and range bounds taking into
    116     *               account hidden series (DG);
    117     * 01-Apr-2009 : Handle a StatisticalCategoryDataset in
    118     *               iterateToFindRangeBounds() (DG);
    119     *
    120     */
    121
    122    package org.jfree.data.general;
    123
    124    import java.util.ArrayList;
    125    import java.util.Iterator;
    126    import java.util.List;
    127
    128    import org.jfree.data.DomainInfo;
    129    import org.jfree.data.KeyToGroupMap;
    130    import org.jfree.data.KeyedValues;
    131    import org.jfree.data.Range;
    132    import org.jfree.data.RangeInfo;
    133    import org.jfree.data.category.CategoryDataset;
    134    import org.jfree.data.category.CategoryRangeInfo;
    135    import org.jfree.data.category.DefaultCategoryDataset;
    136    import org.jfree.data.category.IntervalCategoryDataset;
    137    import org.jfree.data.function.Function2D;
    138    import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset;
    139    import org.jfree.data.statistics.BoxAndWhiskerXYDataset;
    140    import org.jfree.data.statistics.StatisticalCategoryDataset;
    141    import org.jfree.data.xy.IntervalXYDataset;
    142    import org.jfree.data.xy.OHLCDataset;
    143    import org.jfree.data.xy.TableXYDataset;
    144    import org.jfree.data.xy.XYDataset;
    145    import org.jfree.data.xy.XYDomainInfo;
    146    import org.jfree.data.xy.XYRangeInfo;
    147    import org.jfree.data.xy.XYSeries;
    148    import org.jfree.data.xy.XYSeriesCollection;
    149    import org.jfree.util.ArrayUtilities;
    150
    151    /**
    152     * A collection of useful static methods relating to datasets.
    153     */
    154    public final class DatasetUtilities {
    155
    156        /**
    157         * Private constructor for non-instanceability.
    158         */
    159        private DatasetUtilities() {
    160            // now try to instantiate this ;-)
    161        }
    162
    163        /**
    164         * Calculates the total of all the values in a {@link PieDataset}.  If
    165         * the dataset contains negative or <code>null</code> values, they are
    166         * ignored.
    167         *
    168         * @param dataset  the dataset (<code>null</code> not permitted).
    169         *
    170         * @return The total.
    171         */
    172        public static double calculatePieDatasetTotal(PieDataset dataset) {
    173            if (dataset == null) {
    174                throw new IllegalArgumentException("Null 'dataset' argument.");
    175            }
    176            List keys = dataset.getKeys();
    177            double totalValue = 0;
    178            Iterator iterator = keys.iterator();
    179            while (iterator.hasNext()) {
    180                Comparable current = (Comparable) iterator.next();
    181                if (current != null) {
    182                    Number value = dataset.getValue(current);
    183                    double v = 0.0;
    184                    if (value != null) {
    185                        v = value.doubleValue();
    186                    }
    187                    if (v > 0) {
    188                        totalValue = totalValue + v;
    189                    }
    190                }
    191            }
    192            return totalValue;
    193        }
    194
    195        /**
    196         * Creates a pie dataset from a table dataset by taking all the values
    197         * for a single row.
    198         *
    199         * @param dataset  the dataset (<code>null</code> not permitted).
    200         * @param rowKey  the row key.
    201         *
    202         * @return A pie dataset.
    203         */
    204        public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
    205                                                        Comparable rowKey) {
    206            int row = dataset.getRowIndex(rowKey);
    207            return createPieDatasetForRow(dataset, row);
    208        }
    209
    210        /**
    211         * Creates a pie dataset from a table dataset by taking all the values
    212         * for a single row.
    213         *
    214         * @param dataset  the dataset (<code>null</code> not permitted).
    215         * @param row  the row (zero-based index).
    216         *
    217         * @return A pie dataset.
    218         */
    219        public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
    220                                                        int row) {
    221            DefaultPieDataset result = new DefaultPieDataset();
    222            int columnCount = dataset.getColumnCount();
    223            for (int current = 0; current < columnCount; current++) {
    224                Comparable columnKey = dataset.getColumnKey(current);
    225                result.setValue(columnKey, dataset.getValue(row, current));
    226            }
    227            return result;
    228        }
    229
    230        /**
    231         * Creates a pie dataset from a table dataset by taking all the values
    232         * for a single column.
    233         *
    234         * @param dataset  the dataset (<code>null</code> not permitted).
    235         * @param columnKey  the column key.
    236         *
    237         * @return A pie dataset.
    238         */
    239        public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
    240                                                           Comparable columnKey) {
    241            int column = dataset.getColumnIndex(columnKey);
    242            return createPieDatasetForColumn(dataset, column);
    243        }
    244
    245        /**
    246         * Creates a pie dataset from a {@link CategoryDataset} by taking all the
    247         * values for a single column.
    248         *
    249         * @param dataset  the dataset (<code>null</code> not permitted).
    250         * @param column  the column (zero-based index).
    251         *
    252         * @return A pie dataset.
    253         */
    254        public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
    255                                                           int column) {
    256            DefaultPieDataset result = new DefaultPieDataset();
    257            int rowCount = dataset.getRowCount();
    258            for (int i = 0; i < rowCount; i++) {
    259                Comparable rowKey = dataset.getRowKey(i);
    260                result.setValue(rowKey, dataset.getValue(i, column));
    261            }
    262            return result;
    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 with the
    269         * key "Other".
    270         *
    271         * @param source  the source dataset (<code>null</code> not permitted).
    272         * @param key  a new key for the aggregated items (<code>null</code> not
    273         *             permitted).
    274         * @param minimumPercent  the percent threshold.
    275         *
    276         * @return The pie dataset with (possibly) aggregated items.
    277         */
    278        public static PieDataset createConsolidatedPieDataset(PieDataset source,
    279                Comparable key, double minimumPercent) {
    280            return DatasetUtilities.createConsolidatedPieDataset(source, key,
    281                    minimumPercent, 2);
    282        }
    283
    284        /**
    285         * Creates a new pie dataset based on the supplied dataset, but modified
    286         * by aggregating all the low value items (those whose value is lower
    287         * than the <code>percentThreshold</code>) into a single item.  The
    288         * aggregated items are assigned the specified key.  Aggregation only
    289         * occurs if there are at least <code>minItems</code> items to aggregate.
    290         *
    291         * @param source  the source dataset (<code>null</code> not permitted).
    292         * @param key  the key to represent the aggregated items.
    293         * @param minimumPercent  the percent threshold (ten percent is 0.10).
    294         * @param minItems  only aggregate low values if there are at least this
    295         *                  many.
    296         *
    297         * @return The pie dataset with (possibly) aggregated items.
    298         */
    299        public static PieDataset createConsolidatedPieDataset(PieDataset source,
    300                Comparable key, double minimumPercent, int minItems) {
    301
    302            DefaultPieDataset result = new DefaultPieDataset();
    303            double total = DatasetUtilities.calculatePieDatasetTotal(source);
    304
    305            //  Iterate and find all keys below threshold percentThreshold
    306            List keys = source.getKeys();
    307            ArrayList otherKeys = new ArrayList();
    308            Iterator iterator = keys.iterator();
    309            while (iterator.hasNext()) {
    310                Comparable currentKey = (Comparable) iterator.next();
    311                Number dataValue = source.getValue(currentKey);
    312                if (dataValue != null) {
    313                    double value = dataValue.doubleValue();
    314                    if (value / total < minimumPercent) {
    315                        otherKeys.add(currentKey);
    316                    }
    317                }
    318            }
    319
    320            //  Create new dataset with keys above threshold percentThreshold
    321            iterator = keys.iterator();
    322            double otherValue = 0;
    323            while (iterator.hasNext()) {
    324                Comparable currentKey = (Comparable) iterator.next();
    325                Number dataValue = source.getValue(currentKey);
    326                if (dataValue != null) {
    327                    if (otherKeys.contains(currentKey)
    328                        && otherKeys.size() >= minItems) {
    329                        //  Do not add key to dataset
    330                        otherValue += dataValue.doubleValue();
    331                    }
    332                    else {
    333                        //  Add key to dataset
    334                        result.setValue(currentKey, dataValue);
    335                    }
    336                }
    337            }
    338            //  Add other category if applicable
    339            if (otherKeys.size() >= minItems) {
    340                result.setValue(key, otherValue);
    341            }
    342            return result;
    343        }
    344
    345        /**
    346         * Creates a {@link CategoryDataset} that contains a copy of the data in an
    347         * array (instances of <code>Double</code> are created to represent the
    348         * data items).
    349         * <p>
    350         * Row and column keys are created by appending 0, 1, 2, ... to the
    351         * supplied prefixes.
    352         *
    353         * @param rowKeyPrefix  the row key prefix.
    354         * @param columnKeyPrefix  the column key prefix.
    355         * @param data  the data.
    356         *
    357         * @return The dataset.
    358         */
    359        public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
    360                String columnKeyPrefix, double[][] data) {
    361
    362            DefaultCategoryDataset result = new DefaultCategoryDataset();
    363            for (int r = 0; r < data.length; r++) {
    364                String rowKey = rowKeyPrefix + (r + 1);
    365                for (int c = 0; c < data[r].length; c++) {
    366                    String columnKey = columnKeyPrefix + (c + 1);
    367                    result.addValue(new Double(data[r][c]), rowKey, columnKey);
    368                }
    369            }
    370            return result;
    371
    372        }
    373
    374        /**
    375         * Creates a {@link CategoryDataset} that contains a copy of the data in
    376         * an array.
    377         * <p>
    378         * Row and column keys are created by appending 0, 1, 2, ... to the
    379         * supplied prefixes.
    380         *
    381         * @param rowKeyPrefix  the row key prefix.
    382         * @param columnKeyPrefix  the column key prefix.
    383         * @param data  the data.
    384         *
    385         * @return The dataset.
    386         */
    387        public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
    388                String columnKeyPrefix, Number[][] data) {
    389
    390            DefaultCategoryDataset result = new DefaultCategoryDataset();
    391            for (int r = 0; r < data.length; r++) {
    392                String rowKey = rowKeyPrefix + (r + 1);
    393                for (int c = 0; c < data[r].length; c++) {
    394                    String columnKey = columnKeyPrefix + (c + 1);
    395                    result.addValue(data[r][c], rowKey, columnKey);
    396                }
    397            }
    398            return result;
    399
    400        }
    401
    402        /**
    403         * Creates a {@link CategoryDataset} that contains a copy of the data in
    404         * an array (instances of <code>Double</code> are created to represent the
    405         * data items).
    406         * <p>
    407         * Row and column keys are taken from the supplied arrays.
    408         *
    409         * @param rowKeys  the row keys (<code>null</code> not permitted).
    410         * @param columnKeys  the column keys (<code>null</code> not permitted).
    411         * @param data  the data.
    412         *
    413         * @return The dataset.
    414         */
    415        public static CategoryDataset createCategoryDataset(Comparable[] rowKeys,
    416                Comparable[] columnKeys, double[][] data) {
    417
    418            // check arguments...
    419            if (rowKeys == null) {
    420                throw new IllegalArgumentException("Null 'rowKeys' argument.");
    421            }
    422            if (columnKeys == null) {
    423                throw new IllegalArgumentException("Null 'columnKeys' argument.");
    424            }
    425            if (ArrayUtilities.hasDuplicateItems(rowKeys)) {
    426                throw new IllegalArgumentException("Duplicate items in 'rowKeys'.");
    427            }
    428            if (ArrayUtilities.hasDuplicateItems(columnKeys)) {
    429                throw new IllegalArgumentException(
    430                        "Duplicate items in 'columnKeys'.");
    431            }
    432            if (rowKeys.length != data.length) {
    433                throw new IllegalArgumentException(
    434                    "The number of row keys does not match the number of rows in "
    435                    + "the data array.");
    436            }
    437            int columnCount = 0;
    438            for (int r = 0; r < data.length; r++) {
    439                columnCount = Math.max(columnCount, data[r].length);
    440            }
    441            if (columnKeys.length != columnCount) {
    442                throw new IllegalArgumentException(
    443                    "The number of column keys does not match the number of "
    444                    + "columns in the data array.");
    445            }
    446
    447            // now do the work...
    448            DefaultCategoryDataset result = new DefaultCategoryDataset();
    449            for (int r = 0; r < data.length; r++) {
    450                Comparable rowKey = rowKeys[r];
    451                for (int c = 0; c < data[r].length; c++) {
    452                    Comparable columnKey = columnKeys[c];
    453                    result.addValue(new Double(data[r][c]), rowKey, columnKey);
    454                }
    455            }
    456            return result;
    457
    458        }
    459
    460        /**
    461         * Creates a {@link CategoryDataset} by copying the data from the supplied
    462         * {@link KeyedValues} instance.
    463         *
    464         * @param rowKey  the row key (<code>null</code> not permitted).
    465         * @param rowData  the row data (<code>null</code> not permitted).
    466         *
    467         * @return A dataset.
    468         */
    469        public static CategoryDataset createCategoryDataset(Comparable rowKey,
    470                                                            KeyedValues rowData) {
    471
    472            if (rowKey == null) {
    473                throw new IllegalArgumentException("Null 'rowKey' argument.");
    474            }
    475            if (rowData == null) {
    476                throw new IllegalArgumentException("Null 'rowData' argument.");
    477            }
    478            DefaultCategoryDataset result = new DefaultCategoryDataset();
    479            for (int i = 0; i < rowData.getItemCount(); i++) {
    480                result.addValue(rowData.getValue(i), rowKey, rowData.getKey(i));
    481            }
    482            return result;
    483
    484        }
    485
    486        /**
    487         * Creates an {@link XYDataset} by sampling the specified function over a
    488         * fixed range.
    489         *
    490         * @param f  the function (<code>null</code> not permitted).
    491         * @param start  the start value for the range.
    492         * @param end  the end value for the range.
    493         * @param samples  the number of sample points (must be > 1).
    494         * @param seriesKey  the key to give the resulting series
    495         *                   (<code>null</code> not permitted).
    496         *
    497         * @return A dataset.
    498         */
    499        public static XYDataset sampleFunction2D(Function2D f, double start,
    500                double end, int samples, Comparable seriesKey) {
    501
    502            // defer argument checking
    503            XYSeries series = sampleFunction2DToSeries(f, start, end, samples,
    504                    seriesKey);
    505            XYSeriesCollection collection = new XYSeriesCollection(series);
    506            return collection;
    507        }
    508
    509        /**
    510         * Creates an {@link XYSeries} by sampling the specified function over a
    511         * fixed range.
    512         *
    513         * @param f  the function (<code>null</code> not permitted).
    514         * @param start  the start value for the range.
    515         * @param end  the end value for the range.
    516         * @param samples  the number of sample points (must be > 1).
    517         * @param seriesKey  the key to give the resulting series
    518         *                   (<code>null</code> not permitted).
    519         *
    520         * @return A series.
    521         *
    522         * @since 1.0.13
    523         */
    524        public static XYSeries sampleFunction2DToSeries(Function2D f,
    525                double start, double end, int samples, Comparable seriesKey) {
    526
    527            if (f == null) {
    528                throw new IllegalArgumentException("Null 'f' argument.");
    529            }
    530            if (seriesKey == null) {
    531                throw new IllegalArgumentException("Null 'seriesKey' argument.");
    532            }
    533            if (start >= end) {
    534                throw new IllegalArgumentException("Requires 'start' < 'end'.");
    535            }
    536            if (samples < 2) {
    537                throw new IllegalArgumentException("Requires 'samples' > 1");
    538            }
    539
    540            XYSeries series = new XYSeries(seriesKey);
    541            double step = (end - start) / (samples - 1);
    542            for (int i = 0; i < samples; i++) {
    543                double x = start + (step * i);
    544                series.add(x, f.getValue(x));
    545            }
    546            return series;
    547        }
    548
    549        /**
    550         * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
    551         * and <code>false</code> otherwise.
    552         *
    553         * @param dataset  the dataset (<code>null</code> permitted).
    554         *
    555         * @return A boolean.
    556         */
    557        public static boolean isEmptyOrNull(PieDataset dataset) {
    558
    559            if (dataset == null) {
    560                return true;
    561            }
    562
    563            int itemCount = dataset.getItemCount();
    564            if (itemCount == 0) {
    565                return true;
    566            }
    567
    568            for (int item = 0; item < itemCount; item++) {
    569                Number y = dataset.getValue(item);
    570                if (y != null) {
    571                    double yy = y.doubleValue();
    572                    if (yy > 0.0) {
    573                        return false;
    574                    }
    575                }
    576            }
    577
    578            return true;
    579
    580        }
    581
    582        /**
    583         * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
    584         * and <code>false</code> otherwise.
    585         *
    586         * @param dataset  the dataset (<code>null</code> permitted).
    587         *
    588         * @return A boolean.
    589         */
    590        public static boolean isEmptyOrNull(CategoryDataset dataset) {
    591
    592            if (dataset == null) {
    593                return true;
    594            }
    595
    596            int rowCount = dataset.getRowCount();
    597            int columnCount = dataset.getColumnCount();
    598            if (rowCount == 0 || columnCount == 0) {
    599                return true;
    600            }
    601
    602            for (int r = 0; r < rowCount; r++) {
    603                for (int c = 0; c < columnCount; c++) {
    604                    if (dataset.getValue(r, c) != null) {
    605                        return false;
    606                    }
    607
    608                }
    609            }
    610
    611            return true;
    612
    613        }
    614
    615        /**
    616         * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
    617         * and <code>false</code> otherwise.
    618         *
    619         * @param dataset  the dataset (<code>null</code> permitted).
    620         *
    621         * @return A boolean.
    622         */
    623        public static boolean isEmptyOrNull(XYDataset dataset) {
    624            if (dataset != null) {
    625                for (int s = 0; s < dataset.getSeriesCount(); s++) {
    626                    if (dataset.getItemCount(s) > 0) {
    627                        return false;
    628                    }
    629                }
    630            }
    631            return true;
    632        }
    633
    634        /**
    635         * Returns the range of values in the domain (x-values) of a dataset.
    636         *
    637         * @param dataset  the dataset (<code>null</code> not permitted).
    638         *
    639         * @return The range of values (possibly <code>null</code>).
    640         */
    641        public static Range findDomainBounds(XYDataset dataset) {
    642            return findDomainBounds(dataset, true);
    643        }
    644
    645        /**
    646         * Returns the range of values in the domain (x-values) of a dataset.
    647         *
    648         * @param dataset  the dataset (<code>null</code> not permitted).
    649         * @param includeInterval  determines whether or not the x-interval is taken
    650         *                         into account (only applies if the dataset is an
    651         *                         {@link IntervalXYDataset}).
    652         *
    653         * @return The range of values (possibly <code>null</code>).
    654         */
    655        public static Range findDomainBounds(XYDataset dataset,
    656                                             boolean includeInterval) {
    657
    658            if (dataset == null) {
    659                throw new IllegalArgumentException("Null 'dataset' argument.");
    660            }
    661
    662            Range result = null;
    663            // if the dataset implements DomainInfo, life is easier
    664            if (dataset instanceof DomainInfo) {
    665                DomainInfo info = (DomainInfo) dataset;
    666                result = info.getDomainBounds(includeInterval);
    667            }
    668            else {
    669                result = iterateDomainBounds(dataset, includeInterval);
    670            }
    671            return result;
    672
    673        }
    674
    675        /**
    676         * Returns the bounds of the x-values in the specified <code>dataset</code>
    677         * taking into account only the visible series and including any x-interval
    678         * if requested.
    679         *
    680         * @param dataset  the dataset (<code>null</code> not permitted).
    681         * @param visibleSeriesKeys  the visible series keys (<code>null</code>
    682         *     not permitted).
    683         * @param includeInterval  include the x-interval (if any)?
    684         *
    685         * @return The bounds (or <code>null</code> if the dataset contains no
    686         *     values.
    687         *
    688         * @since 1.0.13
    689         */
    690        public static Range findDomainBounds(XYDataset dataset,
    691                List visibleSeriesKeys, boolean includeInterval) {
    692            if (dataset == null) {
    693                throw new IllegalArgumentException("Null 'dataset' argument.");
    694            }
    695            Range result = null;
    696            if (dataset instanceof XYDomainInfo) {
    697                XYDomainInfo info = (XYDomainInfo) dataset;
    698                result = info.getDomainBounds(visibleSeriesKeys, includeInterval);
    699            }
    700            else {
    701                result = iterateToFindDomainBounds(dataset, visibleSeriesKeys,
    702                        includeInterval);
    703            }
    704            return result;
    705        }
    706
    707        /**
    708         * Iterates over the items in an {@link XYDataset} to find
    709         * the range of x-values.  If the dataset is an instance of
    710         * {@link IntervalXYDataset}, the starting and ending x-values
    711         * will be used for the bounds calculation.
    712         *
    713         * @param dataset  the dataset (<code>null</code> not permitted).
    714         *
    715         * @return The range (possibly <code>null</code>).
    716         */
    717        public static Range iterateDomainBounds(XYDataset dataset) {
    718            return iterateDomainBounds(dataset, true);
    719        }
    720
    721        /**
    722         * Iterates over the items in an {@link XYDataset} to find
    723         * the range of x-values.
    724         *
    725         * @param dataset  the dataset (<code>null</code> not permitted).
    726         * @param includeInterval  a flag that determines, for an
    727         *          {@link IntervalXYDataset}, whether the x-interval or just the
    728         *          x-value is used to determine the overall range.
    729         *
    730         * @return The range (possibly <code>null</code>).
    731         */
    732        public static Range iterateDomainBounds(XYDataset dataset,
    733                                                boolean includeInterval) {
    734            if (dataset == null) {
    735                throw new IllegalArgumentException("Null 'dataset' argument.");
    736            }
    737            double minimum = Double.POSITIVE_INFINITY;
    738            double maximum = Double.NEGATIVE_INFINITY;
    739            int seriesCount = dataset.getSeriesCount();
    740            double lvalue;
    741            double uvalue;
    742            if (includeInterval && dataset instanceof IntervalXYDataset) {
    743                IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
    744                for (int series = 0; series < seriesCount; series++) {
    745                    int itemCount = dataset.getItemCount(series);
    746                    for (int item = 0; item < itemCount; item++) {
    747                        lvalue = intervalXYData.getStartXValue(series, item);
    748                        uvalue = intervalXYData.getEndXValue(series, item);
    749                        if (!Double.isNaN(lvalue)) {
    750                            minimum = Math.min(minimum, lvalue);
    751                        }
    752                        if (!Double.isNaN(uvalue)) {
    753                            maximum = Math.max(maximum, uvalue);
    754                        }
    755                    }
    756                }
    757            }
    758            else {
    759                for (int series = 0; series < seriesCount; series++) {
    760                    int itemCount = dataset.getItemCount(series);
    761                    for (int item = 0; item < itemCount; item++) {
    762                        lvalue = dataset.getXValue(series, item);
    763                        uvalue = lvalue;
    764                        if (!Double.isNaN(lvalue)) {
    765                            minimum = Math.min(minimum, lvalue);
    766                            maximum = Math.max(maximum, uvalue);
    767                        }
    768                    }
    769                }
    770            }
    771            if (minimum > maximum) {
    772                return null;
    773            }
    774            else {
    775                return new Range(minimum, maximum);
    776            }
    777        }
    778
    779        /**
    780         * Returns the range of values in the range for the dataset.
    781         *
    782         * @param dataset  the dataset (<code>null</code> not permitted).
    783         *
    784         * @return The range (possibly <code>null</code>).
    785         */
    786        public static Range findRangeBounds(CategoryDataset dataset) {
    787            return findRangeBounds(dataset, true);
    788        }
    789
    790        /**
    791         * Returns the range of values in the range for the dataset.
    792         *
    793         * @param dataset  the dataset (<code>null</code> not permitted).
    794         * @param includeInterval  a flag that determines whether or not the
    795         *                         y-interval is taken into account.
    796         *
    797         * @return The range (possibly <code>null</code>).
    798         */
    799        public static Range findRangeBounds(CategoryDataset dataset,
    800                                            boolean includeInterval) {
    801            if (dataset == null) {
    802                throw new IllegalArgumentException("Null 'dataset' argument.");
    803            }
    804            Range result = null;
    805            if (dataset instanceof RangeInfo) {
    806                RangeInfo info = (RangeInfo) dataset;
    807                result = info.getRangeBounds(includeInterval);
    808            }
    809            else {
    810                result = iterateRangeBounds(dataset, includeInterval);
    811            }
    812            return result;
    813        }
    814
    815        /**
    816         * Finds the bounds of the y-values in the specified dataset, including
    817         * only those series that are listed in visibleSeriesKeys.
    818         *
    819         * @param dataset  the dataset (<code>null</code> not permitted).
    820         * @param visibleSeriesKeys  the keys for the visible series
    821         *     (<code>null</code> not permitted).
    822         * @param includeInterval  include the y-interval (if the dataset has a
    823         *     y-interval).
    824         *
    825         * @return The data bounds.
    826         *
    827         * @since 1.0.13
    828         */
    829        public static Range findRangeBounds(CategoryDataset dataset,
    830                List visibleSeriesKeys, boolean includeInterval) {
    831            if (dataset == null) {
    832                throw new IllegalArgumentException("Null 'dataset' argument.");
    833            }
    834            Range result = null;
    835            if (dataset instanceof CategoryRangeInfo) {
    836                CategoryRangeInfo info = (CategoryRangeInfo) dataset;
    837                result = info.getRangeBounds(visibleSeriesKeys, includeInterval);
    838            }
    839            else {
    840                result = iterateToFindRangeBounds(dataset, visibleSeriesKeys,
    841                        includeInterval);
    842            }
    843            return result;
    844        }
    845
    846        /**
    847         * Returns the range of values in the range for the dataset.  This method
    848         * is the partner for the {@link #findDomainBounds(XYDataset)} method.
    849         *
    850         * @param dataset  the dataset (<code>null</code> not permitted).
    851         *
    852         * @return The range (possibly <code>null</code>).
    853         */
    854        public static Range findRangeBounds(XYDataset dataset) {
    855            return findRangeBounds(dataset, true);
    856        }
    857
    858        /**
    859         * Returns the range of values in the range for the dataset.  This method
    860         * is the partner for the {@link #findDomainBounds(XYDataset, boolean)}
    861         * method.
    862         *
    863         * @param dataset  the dataset (<code>null</code> not permitted).
    864         * @param includeInterval  a flag that determines whether or not the
    865         *                         y-interval is taken into account.
    866         *
    867         * @return The range (possibly <code>null</code>).
    868         */
    869        public static Range findRangeBounds(XYDataset dataset,
    870                                            boolean includeInterval) {
    871            if (dataset == null) {
    872                throw new IllegalArgumentException("Null 'dataset' argument.");
    873            }
    874            Range result = null;
    875            if (dataset instanceof RangeInfo) {
    876                RangeInfo info = (RangeInfo) dataset;
    877                result = info.getRangeBounds(includeInterval);
    878            }
    879            else {
    880                result = iterateRangeBounds(dataset, includeInterval);
    881            }
    882            return result;
    883        }
    884
    885        /**
    886         * Finds the bounds of the y-values in the specified dataset, including
    887         * only those series that are listed in visibleSeriesKeys, and those items
    888         * whose x-values fall within the specified range.
    889         *
    890         * @param dataset  the dataset (<code>null</code> not permitted).
    891         * @param visibleSeriesKeys  the keys for the visible series
    892         *     (<code>null</code> not permitted).
    893         * @param xRange  the x-range (<code>null</code> not permitted).
    894         * @param includeInterval  include the y-interval (if the dataset has a
    895         *     y-interval).
    896         *
    897         * @return The data bounds.
    898         *
    899         * @since 1.0.13
    900         */
    901        public static Range findRangeBounds(XYDataset dataset,
    902                List visibleSeriesKeys, Range xRange, boolean includeInterval) {
    903            if (dataset == null) {
    904                throw new IllegalArgumentException("Null 'dataset' argument.");
    905            }
    906            Range result = null;
    907            if (dataset instanceof XYRangeInfo) {
    908                XYRangeInfo info = (XYRangeInfo) dataset;
    909                result = info.getRangeBounds(visibleSeriesKeys, xRange,
    910                        includeInterval);
    911            }
    912            else {
    913                result = iterateToFindRangeBounds(dataset, visibleSeriesKeys,
    914                        xRange, includeInterval);
    915            }
    916            return result;
    917        }
    918
    919        /**
    920         * Iterates over the data item of the category dataset to find
    921         * the range bounds.
    922         *
    923         * @param dataset  the dataset (<code>null</code> not permitted).
    924         * @param includeInterval  a flag that determines whether or not the
    925         *                         y-interval is taken into account.
    926         *
    927         * @return The range (possibly <code>null</code>).
    928         *
    929         * @deprecated As of 1.0.10, use
    930         *         {@link #iterateRangeBounds(CategoryDataset, boolean)}.
    931         */
    932        public static Range iterateCategoryRangeBounds(CategoryDataset dataset,
    933                boolean includeInterval) {
    934            return iterateRangeBounds(dataset, includeInterval);
    935        }
    936
    937        /**
    938         * Iterates over the data item of the category dataset to find
    939         * the range bounds.
    940         *
    941         * @param dataset  the dataset (<code>null</code> not permitted).
    942         *
    943         * @return The range (possibly <code>null</code>).
    944         *
    945         * @since 1.0.10
    946         */
    947        public static Range iterateRangeBounds(CategoryDataset dataset) {
    948            return iterateRangeBounds(dataset, true);
    949        }
    950
    951        /**
    952         * Iterates over the data item of the category dataset to find
    953         * the range bounds.
    954         *
    955         * @param dataset  the dataset (<code>null</code> not permitted).
    956         * @param includeInterval  a flag that determines whether or not the
    957         *                         y-interval is taken into account.
    958         *
    959         * @return The range (possibly <code>null</code>).
    960         *
    961         * @since 1.0.10
    962         */
    963        public static Range iterateRangeBounds(CategoryDataset dataset,
    964                boolean includeInterval) {
    965            double minimum = Double.POSITIVE_INFINITY;
    966            double maximum = Double.NEGATIVE_INFINITY;
    967            int rowCount = dataset.getRowCount();
    968            int columnCount = dataset.getColumnCount();
    969            if (includeInterval && dataset instanceof IntervalCategoryDataset) {
    970                // handle the special case where the dataset has y-intervals that
    971                // we want to measure
    972                IntervalCategoryDataset icd = (IntervalCategoryDataset) dataset;
    973                Number lvalue, uvalue;
    974                for (int row = 0; row < rowCount; row++) {
    975                    for (int column = 0; column < columnCount; column++) {
    976                        lvalue = icd.getStartValue(row, column);
    977                        uvalue = icd.getEndValue(row, column);
    978                        if (lvalue != null && !Double.isNaN(lvalue.doubleValue())) {
    979                            minimum = Math.min(minimum, lvalue.doubleValue());
    980                        }
    981                        if (uvalue != null && !Double.isNaN(uvalue.doubleValue())) {
    982                            maximum = Math.max(maximum, uvalue.doubleValue());
    983                        }
    984                    }
    985                }
    986            }
    987            else {
    988                // handle the standard case (plain CategoryDataset)
    989                for (int row = 0; row < rowCount; row++) {
    990                    for (int column = 0; column < columnCount; column++) {
    991                        Number value = dataset.getValue(row, column);
    992                        if (value != null) {
    993                            double v = value.doubleValue();
    994                            if (!Double.isNaN(v)) {
    995                                minimum = Math.min(minimum, v);
    996                                maximum = Math.max(maximum, v);
    997                            }
    998                        }
    999                    }
    1000                }
    1001            }
    1002            if (minimum == Double.POSITIVE_INFINITY) {
    1003                return null;
    1004            }
    1005            else {
    1006                return new Range(minimum, maximum);
    1007            }
    1008        }
    1009
    1010        /**
    1011         * Iterates over the data item of the category dataset to find
    1012         * the range bounds.
    1013         *
    1014         * @param dataset  the dataset (<code>null</code> not permitted).
    1015         * @param includeInterval  a flag that determines whether or not the
    1016         *                         y-interval is taken into account.
    1017         * @param visibleSeriesKeys  the visible series keys.
    1018         *
    1019         * @return The range (possibly <code>null</code>).
    1020         *
    1021         * @since 1.0.13
    1022         */
    1023        public static Range iterateToFindRangeBounds(CategoryDataset dataset,
    1024                List visibleSeriesKeys, boolean includeInterval) {
    1025
    1026            if (dataset == null) {
    1027                throw new IllegalArgumentException("Null 'dataset' argument.");
    1028            }
    1029            if (visibleSeriesKeys == null) {
    1030                throw new IllegalArgumentException(
    1031                        "Null 'visibleSeriesKeys' argument.");
    1032            }
    1033
    1034            double minimum = Double.POSITIVE_INFINITY;
    1035            double maximum = Double.NEGATIVE_INFINITY;
    1036            int columnCount = dataset.getColumnCount();
    1037            if (includeInterval
    1038                    && dataset instanceof BoxAndWhiskerCategoryDataset) {
    1039                // handle special case of BoxAndWhiskerDataset
    1040                BoxAndWhiskerCategoryDataset bx
    1041                        = (BoxAndWhiskerCategoryDataset) dataset;
    1042                Iterator iterator = visibleSeriesKeys.iterator();
    1043                while (iterator.hasNext()) {
    1044                    Comparable seriesKey = (Comparable) iterator.next();
    1045                    int series = dataset.getRowIndex(seriesKey);
    1046                    int itemCount = dataset.getColumnCount();
    1047                    for (int item = 0; item < itemCount; item++) {
    1048                        Number lvalue = bx.getMinRegularValue(series, item);
    1049                        if (lvalue == null) {
    1050                            lvalue = bx.getValue(series, item);
    1051                        }
    1052                        Number uvalue = bx.getMaxRegularValue(series, item);
    1053                        if (uvalue == null) {
    1054                            uvalue = bx.getValue(series, item);
    1055                        }
    1056                        if (lvalue != null) {
    1057                            minimum = Math.min(minimum, lvalue.doubleValue());
    1058                        }
    1059                        if (uvalue != null) {
    1060                            maximum = Math.max(maximum, uvalue.doubleValue());
    1061                        }
    1062                    }
    1063                }
    1064            }
    1065            else if (includeInterval
    1066                    && dataset instanceof IntervalCategoryDataset) {
    1067                // handle the special case where the dataset has y-intervals that
    1068                // we want to measure
    1069                IntervalCategoryDataset icd = (IntervalCategoryDataset) dataset;
    1070                Number lvalue, uvalue;
    1071                Iterator iterator = visibleSeriesKeys.iterator();
    1072                while (iterator.hasNext()) {
    1073                    Comparable seriesKey = (Comparable) iterator.next();
    1074                    int series = dataset.getRowIndex(seriesKey);
    1075                    for (int column = 0; column < columnCount; column++) {
    1076                        lvalue = icd.getStartValue(series, column);
    1077                        uvalue = icd.getEndValue(series, column);
    1078                        if (lvalue != null && !Double.isNaN(lvalue.doubleValue())) {
    1079                            minimum = Math.min(minimum, lvalue.doubleValue());
    1080                        }
    1081                        if (uvalue != null && !Double.isNaN(uvalue.doubleValue())) {
    1082                            maximum = Math.max(maximum, uvalue.doubleValue());
    1083                        }
    1084                    }
    1085                }
    1086            }
    1087            else if (includeInterval
    1088                    && dataset instanceof StatisticalCategoryDataset) {
    1089                // handle the special case where the dataset has y-intervals that
    1090                // we want to measure
    1091                StatisticalCategoryDataset scd
    1092                        = (StatisticalCategoryDataset) dataset;
    1093                Iterator iterator = visibleSeriesKeys.iterator();
    1094                while (iterator.hasNext()) {
    1095                    Comparable seriesKey = (Comparable) iterator.next();
    1096                    int series = dataset.getRowIndex(seriesKey);
    1097                    for (int column = 0; column < columnCount; column++) {
    1098                        Number meanN = scd.getMeanValue(series, column);
    1099                        if (meanN != null) {
    1100                            double std = 0.0;
    1101                            Number stdN = scd.getStdDevValue(series, column);
    1102                            if (stdN != null) {
    1103                                std = stdN.doubleValue();
    1104                                if (Double.isNaN(std)) {
    1105                                    std = 0.0;
    1106                                }
    1107                            }
    1108                            double mean = meanN.doubleValue();
    1109                            if (!Double.isNaN(mean)) {
    1110                                minimum = Math.min(minimum, mean - std);
    1111                                maximum = Math.max(maximum, mean + std);
    1112                            }
    1113                        }
    1114                    }
    1115                }
    1116            }
    1117            else {
    1118                // handle the standard case (plain CategoryDataset)
    1119                Iterator iterator = visibleSeriesKeys.iterator();
    1120                while (iterator.hasNext()) {
    1121                    Comparable seriesKey = (Comparable) iterator.next();
    1122                    int series = dataset.getRowIndex(seriesKey);
    1123                    for (int column = 0; column < columnCount; column++) {
    1124                        Number value = dataset.getValue(series, column);
    1125                        if (value != null) {
    1126                            double v = value.doubleValue();
    1127                            if (!Double.isNaN(v)) {
    1128                                minimum = Math.min(minimum, v);
    1129                                maximum = Math.max(maximum, v);
    1130                            }
    1131                        }
    1132                    }
    1133                }
    1134            }
    1135            if (minimum == Double.POSITIVE_INFINITY) {
    1136                return null;
    1137            }
    1138            else {
    1139                return new Range(minimum, maximum);
    1140            }
    1141        }
    1142
    1143        /**
    1144         * Iterates over the data item of the xy dataset to find
    1145         * the range bounds.
    1146         *
    1147         * @param dataset  the dataset (<code>null</code> not permitted).
    1148         *
    1149         * @return The range (possibly <code>null</code>).
    1150         *
    1151         * @deprecated As of 1.0.10, use {@link #iterateRangeBounds(XYDataset)}.
    1152         */
    1153        public static Range iterateXYRangeBounds(XYDataset dataset) {
    1154            return iterateRangeBounds(dataset);
    1155        }
    1156
    1157        /**
    1158         * Iterates over the data item of the xy dataset to find
    1159         * the range bounds.
    1160         *
    1161         * @param dataset  the dataset (<code>null</code> not permitted).
    1162         *
    1163         * @return The range (possibly <code>null</code>).
    1164         *
    1165         * @since 1.0.10
    1166         */
    1167        public static Range iterateRangeBounds(XYDataset dataset) {
    1168            return iterateRangeBounds(dataset, true);
    1169        }
    1170
    1171        /**
    1172         * Iterates over the data items of the xy dataset to find
    1173         * the range bounds.
    1174         *
    1175         * @param dataset  the dataset (<code>null</code> not permitted).
    1176         * @param includeInterval  a flag that determines, for an
    1177         *          {@link IntervalXYDataset}, whether the y-interval or just the
    1178         *          y-value is used to determine the overall range.
    1179         *
    1180         * @return The range (possibly <code>null</code>).
    1181         *
    1182         * @since 1.0.10
    1183         */
    1184        public static Range iterateRangeBounds(XYDataset dataset,
    1185                boolean includeInterval) {
    1186            double minimum = Double.POSITIVE_INFINITY;
    1187            double maximum = Double.NEGATIVE_INFINITY;
    1188            int seriesCount = dataset.getSeriesCount();
    1189
    1190            // handle three cases by dataset type
    1191            if (includeInterval && dataset instanceof IntervalXYDataset) {
    1192                // handle special case of IntervalXYDataset
    1193                IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
    1194                for (int series = 0; series < seriesCount; series++) {
    1195                    int itemCount = dataset.getItemCount(series);
    1196                    for (int item = 0; item < itemCount; item++) {
    1197                        double lvalue = ixyd.getStartYValue(series, item);
    1198                        double uvalue = ixyd.getEndYValue(series, item);
    1199                        if (!Double.isNaN(lvalue)) {
    1200                            minimum = Math.min(minimum, lvalue);
    1201                        }
    1202                        if (!Double.isNaN(uvalue)) {
    1203                            maximum = Math.max(maximum, uvalue);
    1204                        }
    1205                    }
    1206                }
    1207            }
    1208            else if (includeInterval && dataset instanceof OHLCDataset) {
    1209                // handle special case of OHLCDataset
    1210                OHLCDataset ohlc = (OHLCDataset) dataset;
    1211                for (int series = 0; series < seriesCount; series++) {
    1212                    int itemCount = dataset.getItemCount(series);
    1213                    for (int item = 0; item < itemCount; item++) {
    1214                        double lvalue = ohlc.getLowValue(series, item);
    1215                        double uvalue = ohlc.getHighValue(series, item);
    1216                        if (!Double.isNaN(lvalue)) {
    1217                            minimum = Math.min(minimum, lvalue);
    1218                        }
    1219                        if (!Double.isNaN(uvalue)) {
    1220                            maximum = Math.max(maximum, uvalue);
    1221                        }
    1222                    }
    1223                }
    1224            }
    1225            else {
    1226                // standard case - plain XYDataset
    1227                for (int series = 0; series < seriesCount; series++) {
    1228                    int itemCount = dataset.getItemCount(series);
    1229                    for (int item = 0; item < itemCount; item++) {
    1230                        double value = dataset.getYValue(series, item);
    1231                        if (!Double.isNaN(value)) {
    1232                            minimum = Math.min(minimum, value);
    1233                            maximum = Math.max(maximum, value);
    1234                        }
    1235                    }
    1236                }
    1237            }
    1238            if (minimum == Double.POSITIVE_INFINITY) {
    1239                return null;
    1240            }
    1241            else {
    1242                return new Range(minimum, maximum);
    1243            }
    1244        }
    1245
    1246        /**
    1247         * Returns the range of x-values in the specified dataset for the
    1248         * data items belonging to the visible series.
    1249         *
    1250         * @param dataset  the dataset (<code>null</code> not permitted).
    1251         * @param visibleSeriesKeys  the visible series keys (<code>null</code> not
    1252         *     permitted).
    1253         * @param includeInterval  a flag that determines whether or not the
    1254         *     y-interval for the dataset is included (this only applies if the
    1255         *     dataset is an instance of IntervalXYDataset).
    1256         *
    1257         * @return The x-range (possibly <code>null</code>).
    1258         *
    1259         * @since 1.0.13
    1260         */
    1261        public static Range iterateToFindDomainBounds(XYDataset dataset,
    1262                List visibleSeriesKeys, boolean includeInterval) {
    1263
    1264            if (dataset == null) {
    1265                throw new IllegalArgumentException("Null 'dataset' argument.");
    1266            }
    1267            if (visibleSeriesKeys == null) {
    1268                throw new IllegalArgumentException(
    1269                        "Null 'visibleSeriesKeys' argument.");
    1270            }
    1271
    1272            double minimum = Double.POSITIVE_INFINITY;
    1273            double maximum = Double.NEGATIVE_INFINITY;
    1274
    1275            if (includeInterval && dataset instanceof IntervalXYDataset) {
    1276                // handle special case of IntervalXYDataset
    1277                IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
    1278                Iterator iterator = visibleSeriesKeys.iterator();
    1279                while (iterator.hasNext()) {
    1280                    Comparable seriesKey = (Comparable) iterator.next();
    1281                    int series = dataset.indexOf(seriesKey);
    1282                    int itemCount = dataset.getItemCount(series);
    1283                    for (int item = 0; item < itemCount; item++) {
    1284                        double lvalue = ixyd.getStartXValue(series, item);
    1285                        double uvalue = ixyd.getEndXValue(series, item);
    1286                        if (!Double.isNaN(lvalue)) {
    1287                            minimum = Math.min(minimum, lvalue);
    1288                        }
    1289                        if (!Double.isNaN(uvalue)) {
    1290                            maximum = Math.max(maximum, uvalue);
    1291                        }
    1292                    }
    1293                }
    1294            }
    1295            else {
    1296                // standard case - plain XYDataset
    1297                Iterator iterator = visibleSeriesKeys.iterator();
    1298                while (iterator.hasNext()) {
    1299                    Comparable seriesKey = (Comparable) iterator.next();
    1300                    int series = dataset.indexOf(seriesKey);
    1301                    int itemCount = dataset.getItemCount(series);
    1302                    for (int item = 0; item < itemCount; item++) {
    1303                        double x = dataset.getXValue(series, item);
    1304                        if (!Double.isNaN(x)) {
    1305                            minimum = Math.min(minimum, x);
    1306                            maximum = Math.max(maximum, x);
    1307                        }
    1308                    }
    1309                }
    1310            }
    1311
    1312            if (minimum == Double.POSITIVE_INFINITY) {
    1313                return null;
    1314            }
    1315            else {
    1316                return new Range(minimum, maximum);
    1317            }
    1318        }
    1319
    1320        /**
    1321         * Returns the range of y-values in the specified dataset for the
    1322         * data items belonging to the visible series and with x-values in the
    1323         * given range.
    1324         *
    1325         * @param dataset  the dataset (<code>null</code> not permitted).
    1326         * @param visibleSeriesKeys  the visible series keys (<code>null</code> not
    1327         *     permitted).
    1328         * @param xRange  the x-range (<code>null</code> not permitted).
    1329         * @param includeInterval  a flag that determines whether or not the
    1330         *     y-interval for the dataset is included (this only applies if the
    1331         *     dataset is an instance of IntervalXYDataset).
    1332         *
    1333         * @return The y-range (possibly <code>null</code>).
    1334         *
    1335         * @since 1.0.13
    1336         */
    1337        public static Range iterateToFindRangeBounds(XYDataset dataset,
    1338                List visibleSeriesKeys, Range xRange, boolean includeInterval) {
    1339
    1340            if (dataset == null) {
    1341                throw new IllegalArgumentException("Null 'dataset' argument.");
    1342            }
    1343            if (visibleSeriesKeys == null) {
    1344                throw new IllegalArgumentException(
    1345                        "Null 'visibleSeriesKeys' argument.");
    1346            }
    1347            if (xRange == null) {
    1348                throw new IllegalArgumentException("Null 'xRange' argument");
    1349            }
    1350
    1351            double minimum = Double.POSITIVE_INFINITY;
    1352            double maximum = Double.NEGATIVE_INFINITY;
    1353
    1354            // handle three cases by dataset type
    1355            if (includeInterval && dataset instanceof OHLCDataset) {
    1356                // handle special case of OHLCDataset
    1357                OHLCDataset ohlc = (OHLCDataset) dataset;
    1358                Iterator iterator = visibleSeriesKeys.iterator();
    1359                while (iterator.hasNext()) {
    1360                    Comparable seriesKey = (Comparable) iterator.next();
    1361                    int series = dataset.indexOf(seriesKey);
    1362                    int itemCount = dataset.getItemCount(series);
    1363                    for (int item = 0; item < itemCount; item++) {
    1364                        double x = ohlc.getXValue(series, item);
    1365                        if (xRange.contains(x)) {
    1366                            double lvalue = ohlc.getLowValue(series, item);
    1367                            double uvalue = ohlc.getHighValue(series, item);
    1368                            if (!Double.isNaN(lvalue)) {
    1369                                minimum = Math.min(minimum, lvalue);
    1370                            }
    1371                            if (!Double.isNaN(uvalue)) {
    1372                                maximum = Math.max(maximum, uvalue);
    1373                            }
    1374                        }
    1375                    }
    1376                }
    1377            }
    1378            else if (includeInterval && dataset instanceof BoxAndWhiskerXYDataset) {
    1379                // handle special case of BoxAndWhiskerXYDataset
    1380                BoxAndWhiskerXYDataset bx = (BoxAndWhiskerXYDataset) dataset;
    1381                Iterator iterator = visibleSeriesKeys.iterator();
    1382                while (iterator.hasNext()) {
    1383                    Comparable seriesKey = (Comparable) iterator.next();
    1384                    int series = dataset.indexOf(seriesKey);
    1385                    int itemCount = dataset.getItemCount(series);
    1386                    for (int item = 0; item < itemCount; item++) {
    1387                        double x = bx.getXValue(series, item);
    1388                        if (xRange.contains(x)) {
    1389                            Number lvalue = bx.getMinRegularValue(series, item);
    1390                            Number uvalue = bx.getMaxRegularValue(series, item);
    1391                            if (lvalue != null) {
    1392                                minimum = Math.min(minimum, lvalue.doubleValue());
    1393                            }
    1394                            if (uvalue != null) {
    1395                                maximum = Math.max(maximum, uvalue.doubleValue());
    1396                            }
    1397                        }
    1398                    }
    1399                }
    1400            }
    1401            else if (includeInterval && dataset instanceof IntervalXYDataset) {
    1402                // handle special case of IntervalXYDataset
    1403                IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
    1404                Iterator iterator = visibleSeriesKeys.iterator();
    1405                while (iterator.hasNext()) {
    1406                    Comparable seriesKey = (Comparable) iterator.next();
    1407                    int series = dataset.indexOf(seriesKey);
    1408                    int itemCount = dataset.getItemCount(series);
    1409                    for (int item = 0; item < itemCount; item++) {
    1410                        double x = ixyd.getXValue(series, item);
    1411                        if (xRange.contains(x)) {
    1412                            double lvalue = ixyd.getStartYValue(series, item);
    1413                            double uvalue = ixyd.getEndYValue(series, item);
    1414                            if (!Double.isNaN(lvalue)) {
    1415                                minimum = Math.min(minimum, lvalue);
    1416                            }
    1417                            if (!Double.isNaN(uvalue)) {
    1418                                maximum = Math.max(maximum, uvalue);
    1419                            }
    1420                        }
    1421                    }
    1422                }
    1423            }
    1424            else {
    1425                // standard case - plain XYDataset
    1426                Iterator iterator = visibleSeriesKeys.iterator();
    1427                while (iterator.hasNext()) {
    1428                    Comparable seriesKey = (Comparable) iterator.next();
    1429                    int series = dataset.indexOf(seriesKey);
    1430                    int itemCount = dataset.getItemCount(series);
    1431                    for (int item = 0; item < itemCount; item++) {
    1432                        double x = dataset.getXValue(series, item);
    1433                        double y = dataset.getYValue(series, item);
    1434                        if (xRange.contains(x)) {
    1435                            if (!Double.isNaN(y)) {
    1436                                minimum = Math.min(minimum, y);
    1437                                maximum = Math.max(maximum, y);
    1438                            }
    1439                        }
    1440                    }
    1441                }
    1442            }
    1443            if (minimum == Double.POSITIVE_INFINITY) {
    1444                return null;
    1445            }
    1446            else {
    1447                return new Range(minimum, maximum);
    1448            }
    1449        }
    1450
    1451        /**
    1452         * Finds the minimum domain (or X) value for the specified dataset.  This
    1453         * is easy if the dataset implements the {@link DomainInfo} interface (a
    1454         * good idea if there is an efficient way to determine the minimum value).
    1455         * Otherwise, it involves iterating over the entire data-set.
    1456         * <p>
    1457         * Returns <code>null</code> if all the data values in the dataset are
    1458         * <code>null</code>.
    1459         *
    1460         * @param dataset  the dataset (<code>null</code> not permitted).
    1461         *
    1462         * @return The minimum value (possibly <code>null</code>).
    1463         */
    1464        public static Number findMinimumDomainValue(XYDataset dataset) {
    1465            if (dataset == null) {
    1466                throw new IllegalArgumentException("Null 'dataset' argument.");
    1467            }
    1468            Number result = null;
    1469            // if the dataset implements DomainInfo, life is easy
    1470            if (dataset instanceof DomainInfo) {
    1471                DomainInfo info = (DomainInfo) dataset;
    1472                return new Double(info.getDomainLowerBound(true));
    1473            }
    1474            else {
    1475                double minimum = Double.POSITIVE_INFINITY;
    1476                int seriesCount = dataset.getSeriesCount();
    1477                for (int series = 0; series < seriesCount; series++) {
    1478                    int itemCount = dataset.getItemCount(series);
    1479                    for (int item = 0; item < itemCount; item++) {
    1480
    1481                        double value;
    1482                        if (dataset instanceof IntervalXYDataset) {
    1483                            IntervalXYDataset intervalXYData
    1484                                = (IntervalXYDataset) dataset;
    1485                            value = intervalXYData.getStartXValue(series, item);
    1486                        }
    1487                        else {
    1488                            value = dataset.getXValue(series, item);
    1489                        }
    1490                        if (!Double.isNaN(value)) {
    1491                            minimum = Math.min(minimum, value);
    1492                        }
    1493
    1494                    }
    1495                }
    1496                if (minimum == Double.POSITIVE_INFINITY) {
    1497                    result = null;
    1498                }
    1499                else {
    1500                    result = new Double(minimum);
    1501                }
    1502            }
    1503
    1504            return result;
    1505        }
    1506
    1507        /**
    1508         * Returns the maximum domain value for the specified dataset.  This is
    1509         * easy if the dataset implements the {@link DomainInfo} interface (a good
    1510         * idea if there is an efficient way to determine the maximum value).
    1511         * Otherwise, it involves iterating over the entire data-set.  Returns
    1512         * <code>null</code> if all the data values in the dataset are
    1513         * <code>null</code>.
    1514         *
    1515         * @param dataset  the dataset (<code>null</code> not permitted).
    1516         *
    1517         * @return The maximum value (possibly <code>null</code>).
    1518         */
    1519        public static Number findMaximumDomainValue(XYDataset dataset) {
    1520            if (dataset == null) {
    1521                throw new IllegalArgumentException("Null 'dataset' argument.");
    1522            }
    1523            Number result = null;
    1524            // if the dataset implements DomainInfo, life is easy
    1525            if (dataset instanceof DomainInfo) {
    1526                DomainInfo info = (DomainInfo) dataset;
    1527                return new Double(info.getDomainUpperBound(true));
    1528            }
    1529
    1530            // hasn't implemented DomainInfo, so iterate...
    1531            else {
    1532                double maximum = Double.NEGATIVE_INFINITY;
    1533                int seriesCount = dataset.getSeriesCount();
    1534                for (int series = 0; series < seriesCount; series++) {
    1535                    int itemCount = dataset.getItemCount(series);
    1536                    for (int item = 0; item < itemCount; item++) {
    1537
    1538                        double value;
    1539                        if (dataset instanceof IntervalXYDataset) {
    1540                            IntervalXYDataset intervalXYData
    1541                                = (IntervalXYDataset) dataset;
    1542                            value = intervalXYData.getEndXValue(series, item);
    1543                        }
    1544                        else {
    1545                            value = dataset.getXValue(series, item);
    1546                        }
    1547                        if (!Double.isNaN(value)) {
    1548                            maximum = Math.max(maximum, value);
    1549                        }
    1550                    }
    1551                }
    1552                if (maximum == Double.NEGATIVE_INFINITY) {
    1553                    result = null;
    1554                }
    1555                else {
    1556                    result = new Double(maximum);
    1557                }
    1558
    1559            }
    1560
    1561            return result;
    1562        }
    1563
    1564        /**
    1565         * Returns the minimum range value for the specified dataset.  This is
    1566         * easy if the dataset implements the {@link RangeInfo} interface (a good
    1567         * idea if there is an efficient way to determine the minimum value).
    1568         * Otherwise, it involves iterating over the entire data-set.  Returns
    1569         * <code>null</code> if all the data values in the dataset are
    1570         * <code>null</code>.
    1571         *
    1572         * @param dataset  the dataset (<code>null</code> not permitted).
    1573         *
    1574         * @return The minimum value (possibly <code>null</code>).
    1575         */
    1576        public static Number findMinimumRangeValue(CategoryDataset dataset) {
    1577
    1578            if (dataset == null) {
    1579                throw new IllegalArgumentException("Null 'dataset' argument.");
    1580            }
    1581
    1582            if (dataset instanceof RangeInfo) {
    1583                RangeInfo info = (RangeInfo) dataset;
    1584                return new Double(info.getRangeLowerBound(true));
    1585            }
    1586
    1587            // hasn't implemented RangeInfo, so we'll have to iterate...
    1588            else {
    1589                double minimum = Double.POSITIVE_INFINITY;
    1590                int seriesCount = dataset.getRowCount();
    1591                int itemCount = dataset.getColumnCount();
    1592                for (int series = 0; series < seriesCount; series++) {
    1593                    for (int item = 0; item < itemCount; item++) {
    1594                        Number value;
    1595                        if (dataset instanceof IntervalCategoryDataset) {
    1596                            IntervalCategoryDataset icd
    1597                                    = (IntervalCategoryDataset) dataset;
    1598                            value = icd.getStartValue(series, item);
    1599                        }
    1600                        else {
    1601                            value = dataset.getValue(series, item);
    1602                        }
    1603                        if (value != null) {
    1604                            minimum = Math.min(minimum, value.doubleValue());
    1605                        }
    1606                    }
    1607                }
    1608                if (minimum == Double.POSITIVE_INFINITY) {
    1609                    return null;
    1610                }
    1611                else {
    1612                    return new Double(minimum);
    1613                }
    1614
    1615            }
    1616
    1617        }
    1618
    1619        /**
    1620         * Returns the minimum range value for the specified dataset.  This is
    1621         * easy if the dataset implements the {@link RangeInfo} interface (a good
    1622         * idea if there is an efficient way to determine the minimum value).
    1623         * Otherwise, it involves iterating over the entire data-set.  Returns
    1624         * <code>null</code> if all the data values in the dataset are
    1625         * <code>null</code>.
    1626         *
    1627         * @param dataset  the dataset (<code>null</code> not permitted).
    1628         *
    1629         * @return The minimum value (possibly <code>null</code>).
    1630         */
    1631        public static Number findMinimumRangeValue(XYDataset dataset) {
    1632
    1633            if (dataset == null) {
    1634                throw new IllegalArgumentException("Null 'dataset' argument.");
    1635            }
    1636
    1637            // work out the minimum value...
    1638            if (dataset instanceof RangeInfo) {
    1639                RangeInfo info = (RangeInfo) dataset;
    1640                return new Double(info.getRangeLowerBound(true));
    1641            }
    1642
    1643            // hasn't implemented RangeInfo, so we'll have to iterate...
    1644            else {
    1645                double minimum = Double.POSITIVE_INFINITY;
    1646                int seriesCount = dataset.getSeriesCount();
    1647                for (int series = 0; series < seriesCount; series++) {
    1648                    int itemCount = dataset.getItemCount(series);
    1649                    for (int item = 0; item < itemCount; item++) {
    1650
    1651                        double value;
    1652                        if (dataset instanceof IntervalXYDataset) {
    1653                            IntervalXYDataset intervalXYData
    1654                                    = (IntervalXYDataset) dataset;
    1655                            value = intervalXYData.getStartYValue(series, item);
    1656                        }
    1657                        else if (dataset instanceof OHLCDataset) {
    1658                            OHLCDataset highLowData = (OHLCDataset) dataset;
    1659                            value = highLowData.getLowValue(series, item);
    1660                        }
    1661                        else {
    1662                            value = dataset.getYValue(series, item);
    1663                        }
    1664                        if (!Double.isNaN(value)) {
    1665                            minimum = Math.min(minimum, value);
    1666                        }
    1667
    1668                    }
    1669                }
    1670                if (minimum == Double.POSITIVE_INFINITY) {
    1671                    return null;
    1672                }
    1673                else {
    1674                    return new Double(minimum);
    1675                }
    1676
    1677            }
    1678
    1679        }
    1680
    1681        /**
    1682         * Returns the maximum range value for the specified dataset.  This is easy
    1683         * if the dataset implements the {@link RangeInfo} interface (a good idea
    1684         * if there is an efficient way to determine the maximum value).
    1685         * Otherwise, it involves iterating over the entire data-set.  Returns
    1686         * <code>null</code> if all the data values are <code>null</code>.
    1687         *
    1688         * @param dataset  the dataset (<code>null</code> not permitted).
    1689         *
    1690         * @return The maximum value (possibly <code>null</code>).
    1691         */
    1692        public static Number findMaximumRangeValue(CategoryDataset dataset) {
    1693
    1694            if (dataset == null) {
    1695                throw new IllegalArgumentException("Null 'dataset' argument.");
    1696            }
    1697
    1698            // work out the minimum value...
    1699            if (dataset instanceof RangeInfo) {
    1700                RangeInfo info = (RangeInfo) dataset;
    1701                return new Double(info.getRangeUpperBound(true));
    1702            }
    1703
    1704            // hasn't implemented RangeInfo, so we'll have to iterate...
    1705            else {
    1706
    1707                double maximum = Double.NEGATIVE_INFINITY;
    1708                int seriesCount = dataset.getRowCount();
    1709                int itemCount = dataset.getColumnCount();
    1710                for (int series = 0; series < seriesCount; series++) {
    1711                    for (int item = 0; item < itemCount; item++) {
    1712                        Number value;
    1713                        if (dataset instanceof IntervalCategoryDataset) {
    1714                            IntervalCategoryDataset icd
    1715                                = (IntervalCategoryDataset) dataset;
    1716                            value = icd.getEndValue(series, item);
    1717                        }
    1718                        else {
    1719                            value = dataset.getValue(series, item);
    1720                        }
    1721                        if (value != null) {
    1722                            maximum = Math.max(maximum, value.doubleValue());
    1723                        }
    1724                    }
    1725                }
    1726                if (maximum == Double.NEGATIVE_INFINITY) {
    1727                    return null;
    1728                }
    1729                else {
    1730                    return new Double(maximum);
    1731                }
    1732
    1733            }
    1734
    1735        }
    1736
    1737        /**
    1738         * Returns the maximum range value for the specified dataset.  This is
    1739         * easy if the dataset implements the {@link RangeInfo} interface (a good
    1740         * idea if there is an efficient way to determine the maximum value).
    1741         * Otherwise, it involves iterating over the entire data-set.  Returns
    1742         * <code>null</code> if all the data values are <code>null</code>.
    1743         *
    1744         * @param dataset  the dataset (<code>null</code> not permitted).
    1745         *
    1746         * @return The maximum value (possibly <code>null</code>).
    1747         */
    1748        public static Number findMaximumRangeValue(XYDataset dataset) {
    1749
    1750            if (dataset == null) {
    1751                throw new IllegalArgumentException("Null 'dataset' argument.");
    1752            }
    1753
    1754            // work out the minimum value...
    1755            if (dataset instanceof RangeInfo) {
    1756                RangeInfo info = (RangeInfo) dataset;
    1757                return new Double(info.getRangeUpperBound(true));
    1758            }
    1759
    1760            // hasn't implemented RangeInfo, so we'll have to iterate...
    1761            else  {
    1762
    1763                double maximum = Double.NEGATIVE_INFINITY;
    1764                int seriesCount = dataset.getSeriesCount();
    1765                for (int series = 0; series < seriesCount; series++) {
    1766                    int itemCount = dataset.getItemCount(series);
    1767                    for (int item = 0; item < itemCount; item++) {
    1768                        double value;
    1769                        if (dataset instanceof IntervalXYDataset) {
    1770                            IntervalXYDataset intervalXYData
    1771                                    = (IntervalXYDataset) dataset;
    1772                            value = intervalXYData.getEndYValue(series, item);
    1773                        }
    1774                        else if (dataset instanceof OHLCDataset) {
    1775                            OHLCDataset highLowData = (OHLCDataset) dataset;
    1776                            value = highLowData.getHighValue(series, item);
    1777                        }
    1778                        else {
    1779                            value = dataset.getYValue(series, item);
    1780                        }
    1781                        if (!Double.isNaN(value)) {
    1782                            maximum = Math.max(maximum, value);
    1783                        }
    1784                    }
    1785                }
    1786                if (maximum == Double.NEGATIVE_INFINITY) {
    1787                    return null;
    1788                }
    1789                else {
    1790                    return new Double(maximum);
    1791                }
    1792
    1793            }
    1794
    1795        }
    1796
    1797        /**
    1798         * Returns the minimum and maximum values for the dataset's range
    1799         * (y-values), assuming that the series in one category are stacked.
    1800         *
    1801         * @param dataset  the dataset (<code>null</code> not permitted).
    1802         *
    1803         * @return The range (<code>null</code> if the dataset contains no values).
    1804         */
    1805        public static Range findStackedRangeBounds(CategoryDataset dataset) {
    1806            return findStackedRangeBounds(dataset, 0.0);
    1807        }
    1808
    1809        /**
    1810         * Returns the minimum and maximum values for the dataset's range
    1811         * (y-values), assuming that the series in one category are stacked.
    1812         *
    1813         * @param dataset  the dataset (<code>null</code> not permitted).
    1814         * @param base  the base value for the bars.
    1815         *
    1816         * @return The range (<code>null</code> if the dataset contains no values).
    1817         */
    1818        public static Range findStackedRangeBounds(CategoryDataset dataset,
    1819                double base) {
    1820            if (dataset == null) {
    1821                throw new IllegalArgumentException("Null 'dataset' argument.");
    1822            }
    1823            Range result = null;
    1824            double minimum = Double.POSITIVE_INFINITY;
    1825            double maximum = Double.NEGATIVE_INFINITY;
    1826            int categoryCount = dataset.getColumnCount();
    1827            for (int item = 0; item < categoryCount; item++) {
    1828                double positive = base;
    1829                double negative = base;
    1830                int seriesCount = dataset.getRowCount();
    1831                for (int series = 0; series < seriesCount; series++) {
    1832                    Number number = dataset.getValue(series, item);
    1833                    if (number != null) {
    1834                        double value = number.doubleValue();
    1835                        if (value > 0.0) {
    1836                            positive = positive + value;
    1837                        }
    1838                        if (value < 0.0) {
    1839                            negative = negative + value;
    1840                            // '+', remember value is negative
    1841                        }
    1842                    }
    1843                }
    1844                minimum = Math.min(minimum, negative);
    1845                maximum = Math.max(maximum, positive);
    1846            }
    1847            if (minimum <= maximum) {
    1848                result = new Range(minimum, maximum);
    1849            }
    1850            return result;
    1851
    1852        }
    1853
    1854        /**
    1855         * Returns the minimum and maximum values for the dataset's range
    1856         * (y-values), assuming that the series in one category are stacked.
    1857         *
    1858         * @param dataset  the dataset.
    1859         * @param map  a structure that maps series to groups.
    1860         *
    1861         * @return The value range (<code>null</code> if the dataset contains no
    1862         *         values).
    1863         */
    1864        public static Range findStackedRangeBounds(CategoryDataset dataset,
    1865                                                   KeyToGroupMap map) {
    1866            if (dataset == null) {
    1867                throw new IllegalArgumentException("Null 'dataset' argument.");
    1868            }
    1869            boolean hasValidData = false;
    1870            Range result = null;
    1871
    1872            // create an array holding the group indices for each series...
    1873            int[] groupIndex = new int[dataset.getRowCount()];
    1874            for (int i = 0; i < dataset.getRowCount(); i++) {
    1875                groupIndex[i] = map.getGroupIndex(map.getGroup(
    1876                        dataset.getRowKey(i)));
    1877            }
    1878
    1879            // minimum and maximum for each group...
    1880            int groupCount = map.getGroupCount();
    1881            double[] minimum = new double[groupCount];
    1882            double[] maximum = new double[groupCount];
    1883
    1884            int categoryCount = dataset.getColumnCount();
    1885            for (int item = 0; item < categoryCount; item++) {
    1886                double[] positive = new double[groupCount];
    1887                double[] negative = new double[groupCount];
    1888                int seriesCount = dataset.getRowCount();
    1889                for (int series = 0; series < seriesCount; series++) {
    1890                    Number number = dataset.getValue(series, item);
    1891                    if (number != null) {
    1892                        hasValidData = true;
    1893                        double value = number.doubleValue();
    1894                        if (value > 0.0) {
    1895                            positive[groupIndex[series]]
    1896                                     = positive[groupIndex[series]] + value;
    1897                        }
    1898                        if (value < 0.0) {
    1899                            negative[groupIndex[series]]
    1900                                     = negative[groupIndex[series]] + value;
    1901                                     // '+', remember value is negative
    1902                        }
    1903                    }
    1904                }
    1905                for (int g = 0; g < groupCount; g++) {
    1906                    minimum[g] = Math.min(minimum[g], negative[g]);
    1907                    maximum[g] = Math.max(maximum[g], positive[g]);
    1908                }
    1909            }
    1910            if (hasValidData) {
    1911                for (int j = 0; j < groupCount; j++) {
    1912                    result = Range.combine(result, new Range(minimum[j],
    1913                            maximum[j]));
    1914                }
    1915            }
    1916            return result;
    1917        }
    1918
    1919        /**
    1920         * Returns the minimum value in the dataset range, assuming that values in
    1921         * each category are "stacked".
    1922         *
    1923         * @param dataset  the dataset (<code>null</code> not permitted).
    1924         *
    1925         * @return The minimum value.
    1926         *
    1927         * @see #findMaximumStackedRangeValue(CategoryDataset)
    1928         */
    1929        public static Number findMinimumStackedRangeValue(CategoryDataset dataset) {
    1930            if (dataset == null) {
    1931                throw new IllegalArgumentException("Null 'dataset' argument.");
    1932            }
    1933            Number result = null;
    1934            boolean hasValidData = false;
    1935            double minimum = 0.0;
    1936            int categoryCount = dataset.getColumnCount();
    1937            for (int item = 0; item < categoryCount; item++) {
    1938                double total = 0.0;
    1939                int seriesCount = dataset.getRowCount();
    1940                for (int series = 0; series < seriesCount; series++) {
    1941                    Number number = dataset.getValue(series, item);
    1942                    if (number != null) {
    1943                        hasValidData = true;
    1944                        double value = number.doubleValue();
    1945                        if (value < 0.0) {
    1946                            total = total + value;
    1947                            // '+', remember value is negative
    1948                        }
    1949                    }
    1950                }
    1951                minimum = Math.min(minimum, total);
    1952            }
    1953            if (hasValidData) {
    1954                result = new Double(minimum);
    1955            }
    1956            return result;
    1957        }
    1958
    1959        /**
    1960         * Returns the maximum value in the dataset range, assuming that values in
    1961         * each category are "stacked".
    1962         *
    1963         * @param dataset  the dataset (<code>null</code> not permitted).
    1964         *
    1965         * @return The maximum value (possibly <code>null</code>).
    1966         *
    1967         * @see #findMinimumStackedRangeValue(CategoryDataset)
    1968         */
    1969        public static Number findMaximumStackedRangeValue(CategoryDataset dataset) {
    1970            if (dataset == null) {
    1971                throw new IllegalArgumentException("Null 'dataset' argument.");
    1972            }
    1973            Number result = null;
    1974            boolean hasValidData = false;
    1975            double maximum = 0.0;
    1976            int categoryCount = dataset.getColumnCount();
    1977            for (int item = 0; item < categoryCount; item++) {
    1978                double total = 0.0;
    1979                int seriesCount = dataset.getRowCount();
    1980                for (int series = 0; series < seriesCount; series++) {
    1981                    Number number = dataset.getValue(series, item);
    1982                    if (number != null) {
    1983                        hasValidData = true;
    1984                        double value = number.doubleValue();
    1985                        if (value > 0.0) {
    1986                            total = total + value;
    1987                        }
    1988                    }
    1989                }
    1990                maximum = Math.max(maximum, total);
    1991            }
    1992            if (hasValidData) {
    1993                result = new Double(maximum);
    1994            }
    1995            return result;
    1996        }
    1997
    1998        /**
    1999         * Returns the minimum and maximum values for the dataset's range,
    2000         * assuming that the series are stacked.
    2001         *
    2002         * @param dataset  the dataset (<code>null</code> not permitted).
    2003         *
    2004         * @return The range ([0.0, 0.0] if the dataset contains no values).
    2005         */
    2006        public static Range findStackedRangeBounds(TableXYDataset dataset) {
    2007            return findStackedRangeBounds(dataset, 0.0);
    2008        }
    2009
    2010        /**
    2011         * Returns the minimum and maximum values for the dataset's range,
    2012         * assuming that the series are stacked, using the specified base value.
    2013         *
    2014         * @param dataset  the dataset (<code>null</code> not permitted).
    2015         * @param base  the base value.
    2016         *
    2017         * @return The range (<code>null</code> if the dataset contains no values).
    2018         */
    2019        public static Range findStackedRangeBounds(TableXYDataset dataset,
    2020                                                   double base) {
    2021            if (dataset == null) {
    2022                throw new IllegalArgumentException("Null 'dataset' argument.");
    2023            }
    2024            double minimum = base;
    2025            double maximum = base;
    2026            for (int itemNo = 0; itemNo < dataset.getItemCount(); itemNo++) {
    2027                double positive = base;
    2028                double negative = base;
    2029                int seriesCount = dataset.getSeriesCount();
    2030                for (int seriesNo = 0; seriesNo < seriesCount; seriesNo++) {
    2031                    double y = dataset.getYValue(seriesNo, itemNo);
    2032                    if (!Double.isNaN(y)) {
    2033                        if (y > 0.0) {
    2034                            positive += y;
    2035                        }
    2036                        else {
    2037                            negative += y;
    2038                        }
    2039                    }
    2040                }
    2041                if (positive > maximum) {
    2042                    maximum = positive;
    2043                }
    2044                if (negative < minimum) {
    2045                    minimum = negative;
    2046                }
    2047            }
    2048            if (minimum <= maximum) {
    2049                return new Range(minimum, maximum);
    2050            }
    2051            else {
    2052                return null;
    2053            }
    2054        }
    2055
    2056        /**
    2057         * Calculates the total for the y-values in all series for a given item
    2058         * index.
    2059         *
    2060         * @param dataset  the dataset.
    2061         * @param item  the item index.
    2062         *
    2063         * @return The total.
    2064         *
    2065         * @since 1.0.5
    2066         */
    2067        public static double calculateStackTotal(TableXYDataset dataset, int item) {
    2068            double total = 0.0;
    2069            int seriesCount = dataset.getSeriesCount();
    2070            for (int s = 0; s < seriesCount; s++) {
    2071                double value = dataset.getYValue(s, item);
    2072                if (!Double.isNaN(value)) {
    2073                    total = total + value;
    2074                }
    2075            }
    2076            return total;
    2077        }
    2078
    2079        /**
    2080         * Calculates the range of values for a dataset where each item is the
    2081         * running total of the items for the current series.
    2082         *
    2083         * @param dataset  the dataset (<code>null</code> not permitted).
    2084         *
    2085         * @return The range.
    2086         *
    2087         * @see #findRangeBounds(CategoryDataset)
    2088         */
    2089        public static Range findCumulativeRangeBounds(CategoryDataset dataset) {
    2090            if (dataset == null) {
    2091                throw new IllegalArgumentException("Null 'dataset' argument.");
    2092            }
    2093            boolean allItemsNull = true; // we'll set this to false if there is at
    2094                                         // least one non-null data item...
    2095            double minimum = 0.0;
    2096            double maximum = 0.0;
    2097            for (int row = 0; row < dataset.getRowCount(); row++) {
    2098                double runningTotal = 0.0;
    2099                for (int column = 0; column <= dataset.getColumnCount() - 1;
    2100                     column++) {
    2101                    Number n = dataset.getValue(row, column);
    2102                    if (n != null) {
    2103                        allItemsNull = false;
    2104                        double value = n.doubleValue();
    2105                        if (!Double.isNaN(value)) {
    2106                            runningTotal = runningTotal + value;
    2107                            minimum = Math.min(minimum, runningTotal);
    2108                            maximum = Math.max(maximum, runningTotal);
    2109                        }
    2110                    }
    2111                }
    2112            }
    2113            if (!allItemsNull) {
    2114                return new Range(minimum, maximum);
    2115            }
    2116            else {
    2117                return null;
    2118            }
    2119        }
    2120
    2121    }