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