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