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