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