    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
    128package org.jfree.data.general;
    129
    130import java.util.ArrayList;
    131import java.util.Iterator;
    132import java.util.List;
    133
    134import org.jfree.data.DomainInfo;
    135import org.jfree.data.KeyToGroupMap;
    136import org.jfree.data.KeyedValues;
    137import org.jfree.data.Range;
    138import org.jfree.data.RangeInfo;
    139import org.jfree.data.category.CategoryDataset;
    140import org.jfree.data.category.CategoryRangeInfo;
    141import org.jfree.data.category.DefaultCategoryDataset;
    142import org.jfree.data.category.IntervalCategoryDataset;
    143import org.jfree.data.function.Function2D;
    144import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset;
    145import org.jfree.data.statistics.BoxAndWhiskerXYDataset;
    146import org.jfree.data.statistics.MultiValueCategoryDataset;
    147import org.jfree.data.statistics.StatisticalCategoryDataset;
    148import org.jfree.data.xy.IntervalXYDataset;
    149import org.jfree.data.xy.OHLCDataset;
    150import org.jfree.data.xy.TableXYDataset;
    151import org.jfree.data.xy.XYDataset;
    152import org.jfree.data.xy.XYDomainInfo;
    153import org.jfree.data.xy.XYRangeInfo;
    154import org.jfree.data.xy.XYSeries;
    155import org.jfree.data.xy.XYSeriesCollection;
    156import org.jfree.data.xy.XYZDataset;
    157import org.jfree.util.ArrayUtilities;
    158
    159/**
    160 * A collection of useful static methods relating to datasets.
    161 */
    162public 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}