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