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