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