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