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