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