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