001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ---------------------
028 * DatasetUtilities.java
029 * ---------------------
030 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Andrzej Porebski (bug fix);
034 * Jonathan Nash (bug fix);
035 * Richard Atkinson;
036 * Andreas Schroeder;
037 * Rafal Skalny (patch 1925366);
038 * 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 *
115 */
116
117 package org.jfree.data.general;
118
119 import java.util.ArrayList;
120 import java.util.Iterator;
121 import java.util.List;
122
123 import org.jfree.data.DomainInfo;
124 import org.jfree.data.KeyToGroupMap;
125 import org.jfree.data.KeyedValues;
126 import org.jfree.data.Range;
127 import org.jfree.data.RangeInfo;
128 import org.jfree.data.category.CategoryDataset;
129 import org.jfree.data.category.DefaultCategoryDataset;
130 import org.jfree.data.category.IntervalCategoryDataset;
131 import org.jfree.data.function.Function2D;
132 import org.jfree.data.xy.IntervalXYDataset;
133 import org.jfree.data.xy.OHLCDataset;
134 import org.jfree.data.xy.TableXYDataset;
135 import org.jfree.data.xy.XYDataset;
136 import org.jfree.data.xy.XYSeries;
137 import org.jfree.data.xy.XYSeriesCollection;
138 import org.jfree.util.ArrayUtilities;
139
140 /**
141 * A collection of useful static methods relating to datasets.
142 */
143 public final class DatasetUtilities {
144
145 /**
146 * Private constructor for non-instanceability.
147 */
148 private DatasetUtilities() {
149 // now try to instantiate this ;-)
150 }
151
152 /**
153 * Calculates the total of all the values in a {@link PieDataset}. If
154 * the dataset contains negative or <code>null</code> values, they are
155 * ignored.
156 *
157 * @param dataset the dataset (<code>null</code> not permitted).
158 *
159 * @return The total.
160 */
161 public static double calculatePieDatasetTotal(PieDataset dataset) {
162 if (dataset == null) {
163 throw new IllegalArgumentException("Null 'dataset' argument.");
164 }
165 List keys = dataset.getKeys();
166 double totalValue = 0;
167 Iterator iterator = keys.iterator();
168 while (iterator.hasNext()) {
169 Comparable current = (Comparable) iterator.next();
170 if (current != null) {
171 Number value = dataset.getValue(current);
172 double v = 0.0;
173 if (value != null) {
174 v = value.doubleValue();
175 }
176 if (v > 0) {
177 totalValue = totalValue + v;
178 }
179 }
180 }
181 return totalValue;
182 }
183
184 /**
185 * Creates a pie dataset from a table dataset by taking all the values
186 * for a single row.
187 *
188 * @param dataset the dataset (<code>null</code> not permitted).
189 * @param rowKey the row key.
190 *
191 * @return A pie dataset.
192 */
193 public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
194 Comparable rowKey) {
195 int row = dataset.getRowIndex(rowKey);
196 return createPieDatasetForRow(dataset, row);
197 }
198
199 /**
200 * Creates a pie dataset from a table dataset by taking all the values
201 * for a single row.
202 *
203 * @param dataset the dataset (<code>null</code> not permitted).
204 * @param row the row (zero-based index).
205 *
206 * @return A pie dataset.
207 */
208 public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
209 int row) {
210 DefaultPieDataset result = new DefaultPieDataset();
211 int columnCount = dataset.getColumnCount();
212 for (int current = 0; current < columnCount; current++) {
213 Comparable columnKey = dataset.getColumnKey(current);
214 result.setValue(columnKey, dataset.getValue(row, current));
215 }
216 return result;
217 }
218
219 /**
220 * Creates a pie dataset from a table dataset by taking all the values
221 * for a single column.
222 *
223 * @param dataset the dataset (<code>null</code> not permitted).
224 * @param columnKey the column key.
225 *
226 * @return A pie dataset.
227 */
228 public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
229 Comparable columnKey) {
230 int column = dataset.getColumnIndex(columnKey);
231 return createPieDatasetForColumn(dataset, column);
232 }
233
234 /**
235 * Creates a pie dataset from a {@link CategoryDataset} by taking all the
236 * values for a single column.
237 *
238 * @param dataset the dataset (<code>null</code> not permitted).
239 * @param column the column (zero-based index).
240 *
241 * @return A pie dataset.
242 */
243 public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
244 int column) {
245 DefaultPieDataset result = new DefaultPieDataset();
246 int rowCount = dataset.getRowCount();
247 for (int i = 0; i < rowCount; i++) {
248 Comparable rowKey = dataset.getRowKey(i);
249 result.setValue(rowKey, dataset.getValue(i, column));
250 }
251 return result;
252 }
253
254 /**
255 * Creates a new pie dataset based on the supplied dataset, but modified
256 * by aggregating all the low value items (those whose value is lower
257 * than the <code>percentThreshold</code>) into a single item with the
258 * key "Other".
259 *
260 * @param source the source dataset (<code>null</code> not permitted).
261 * @param key a new key for the aggregated items (<code>null</code> not
262 * permitted).
263 * @param minimumPercent the percent threshold.
264 *
265 * @return The pie dataset with (possibly) aggregated items.
266 */
267 public static PieDataset createConsolidatedPieDataset(PieDataset source,
268 Comparable key,
269 double minimumPercent)
270 {
271 return DatasetUtilities.createConsolidatedPieDataset(
272 source, key, minimumPercent, 2
273 );
274 }
275
276 /**
277 * Creates a new pie dataset based on the supplied dataset, but modified
278 * by aggregating all the low value items (those whose value is lower
279 * than the <code>percentThreshold</code>) into a single item. The
280 * aggregated items are assigned the specified key. Aggregation only
281 * occurs if there are at least <code>minItems</code> items to aggregate.
282 *
283 * @param source the source dataset (<code>null</code> not permitted).
284 * @param key the key to represent the aggregated items.
285 * @param minimumPercent the percent threshold (ten percent is 0.10).
286 * @param minItems only aggregate low values if there are at least this
287 * many.
288 *
289 * @return The pie dataset with (possibly) aggregated items.
290 */
291 public static PieDataset createConsolidatedPieDataset(PieDataset source,
292 Comparable key,
293 double minimumPercent,
294 int minItems) {
295
296 DefaultPieDataset result = new DefaultPieDataset();
297 double total = DatasetUtilities.calculatePieDatasetTotal(source);
298
299 // Iterate and find all keys below threshold percentThreshold
300 List keys = source.getKeys();
301 ArrayList otherKeys = new ArrayList();
302 Iterator iterator = keys.iterator();
303 while (iterator.hasNext()) {
304 Comparable currentKey = (Comparable) iterator.next();
305 Number dataValue = source.getValue(currentKey);
306 if (dataValue != null) {
307 double value = dataValue.doubleValue();
308 if (value / total < minimumPercent) {
309 otherKeys.add(currentKey);
310 }
311 }
312 }
313
314 // Create new dataset with keys above threshold percentThreshold
315 iterator = keys.iterator();
316 double otherValue = 0;
317 while (iterator.hasNext()) {
318 Comparable currentKey = (Comparable) iterator.next();
319 Number dataValue = source.getValue(currentKey);
320 if (dataValue != null) {
321 if (otherKeys.contains(currentKey)
322 && otherKeys.size() >= minItems) {
323 // Do not add key to dataset
324 otherValue += dataValue.doubleValue();
325 }
326 else {
327 // Add key to dataset
328 result.setValue(currentKey, dataValue);
329 }
330 }
331 }
332 // Add other category if applicable
333 if (otherKeys.size() >= minItems) {
334 result.setValue(key, otherValue);
335 }
336 return result;
337 }
338
339 /**
340 * Creates a {@link CategoryDataset} that contains a copy of the data in an
341 * array (instances of <code>Double</code> are created to represent the
342 * data items).
343 * <p>
344 * Row and column keys are created by appending 0, 1, 2, ... to the
345 * supplied prefixes.
346 *
347 * @param rowKeyPrefix the row key prefix.
348 * @param columnKeyPrefix the column key prefix.
349 * @param data the data.
350 *
351 * @return The dataset.
352 */
353 public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
354 String columnKeyPrefix,
355 double[][] data) {
356
357 DefaultCategoryDataset result = new DefaultCategoryDataset();
358 for (int r = 0; r < data.length; r++) {
359 String rowKey = rowKeyPrefix + (r + 1);
360 for (int c = 0; c < data[r].length; c++) {
361 String columnKey = columnKeyPrefix + (c + 1);
362 result.addValue(new Double(data[r][c]), rowKey, columnKey);
363 }
364 }
365 return result;
366
367 }
368
369 /**
370 * Creates a {@link CategoryDataset} that contains a copy of the data in
371 * an array.
372 * <p>
373 * Row and column keys are created by appending 0, 1, 2, ... to the
374 * supplied prefixes.
375 *
376 * @param rowKeyPrefix the row key prefix.
377 * @param columnKeyPrefix the column key prefix.
378 * @param data the data.
379 *
380 * @return The dataset.
381 */
382 public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
383 String columnKeyPrefix,
384 Number[][] data) {
385
386 DefaultCategoryDataset result = new DefaultCategoryDataset();
387 for (int r = 0; r < data.length; r++) {
388 String rowKey = rowKeyPrefix + (r + 1);
389 for (int c = 0; c < data[r].length; c++) {
390 String columnKey = columnKeyPrefix + (c + 1);
391 result.addValue(data[r][c], rowKey, columnKey);
392 }
393 }
394 return result;
395
396 }
397
398 /**
399 * Creates a {@link CategoryDataset} that contains a copy of the data in
400 * an array (instances of <code>Double</code> are created to represent the
401 * data items).
402 * <p>
403 * Row and column keys are taken from the supplied arrays.
404 *
405 * @param rowKeys the row keys (<code>null</code> not permitted).
406 * @param columnKeys the column keys (<code>null</code> not permitted).
407 * @param data the data.
408 *
409 * @return The dataset.
410 */
411 public static CategoryDataset createCategoryDataset(Comparable[] rowKeys,
412 Comparable[] columnKeys,
413 double[][] data) {
414
415 // check arguments...
416 if (rowKeys == null) {
417 throw new IllegalArgumentException("Null 'rowKeys' argument.");
418 }
419 if (columnKeys == null) {
420 throw new IllegalArgumentException("Null 'columnKeys' argument.");
421 }
422 if (ArrayUtilities.hasDuplicateItems(rowKeys)) {
423 throw new IllegalArgumentException("Duplicate items in 'rowKeys'.");
424 }
425 if (ArrayUtilities.hasDuplicateItems(columnKeys)) {
426 throw new IllegalArgumentException(
427 "Duplicate items in 'columnKeys'."
428 );
429 }
430 if (rowKeys.length != data.length) {
431 throw new IllegalArgumentException(
432 "The number of row keys does not match the number of rows in "
433 + "the data array."
434 );
435 }
436 int columnCount = 0;
437 for (int r = 0; r < data.length; r++) {
438 columnCount = Math.max(columnCount, data[r].length);
439 }
440 if (columnKeys.length != columnCount) {
441 throw new IllegalArgumentException(
442 "The number of column keys does not match the number of "
443 + "columns in the data array."
444 );
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 if (f == null) {
503 throw new IllegalArgumentException("Null 'f' argument.");
504 }
505 if (seriesKey == null) {
506 throw new IllegalArgumentException("Null 'seriesKey' argument.");
507 }
508 if (start >= end) {
509 throw new IllegalArgumentException("Requires 'start' < 'end'.");
510 }
511 if (samples < 2) {
512 throw new IllegalArgumentException("Requires 'samples' > 1");
513 }
514
515 XYSeries series = new XYSeries(seriesKey);
516 double step = (end - start) / (samples - 1);
517 for (int i = 0; i < samples; i++) {
518 double x = start + (step * i);
519 series.add(x, f.getValue(x));
520 }
521 XYSeriesCollection collection = new XYSeriesCollection(series);
522 return collection;
523 }
524
525 /**
526 * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
527 * and <code>false</code> otherwise.
528 *
529 * @param dataset the dataset (<code>null</code> permitted).
530 *
531 * @return A boolean.
532 */
533 public static boolean isEmptyOrNull(PieDataset dataset) {
534
535 if (dataset == null) {
536 return true;
537 }
538
539 int itemCount = dataset.getItemCount();
540 if (itemCount == 0) {
541 return true;
542 }
543
544 for (int item = 0; item < itemCount; item++) {
545 Number y = dataset.getValue(item);
546 if (y != null) {
547 double yy = y.doubleValue();
548 if (yy > 0.0) {
549 return false;
550 }
551 }
552 }
553
554 return true;
555
556 }
557
558 /**
559 * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
560 * and <code>false</code> otherwise.
561 *
562 * @param dataset the dataset (<code>null</code> permitted).
563 *
564 * @return A boolean.
565 */
566 public static boolean isEmptyOrNull(CategoryDataset dataset) {
567
568 if (dataset == null) {
569 return true;
570 }
571
572 int rowCount = dataset.getRowCount();
573 int columnCount = dataset.getColumnCount();
574 if (rowCount == 0 || columnCount == 0) {
575 return true;
576 }
577
578 for (int r = 0; r < rowCount; r++) {
579 for (int c = 0; c < columnCount; c++) {
580 if (dataset.getValue(r, c) != null) {
581 return false;
582 }
583
584 }
585 }
586
587 return true;
588
589 }
590
591 /**
592 * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
593 * and <code>false</code> otherwise.
594 *
595 * @param dataset the dataset (<code>null</code> permitted).
596 *
597 * @return A boolean.
598 */
599 public static boolean isEmptyOrNull(XYDataset dataset) {
600 if (dataset != null) {
601 for (int s = 0; s < dataset.getSeriesCount(); s++) {
602 if (dataset.getItemCount(s) > 0) {
603 return false;
604 }
605 }
606 }
607 return true;
608 }
609
610 /**
611 * Returns the range of values in the domain (x-values) of a dataset.
612 *
613 * @param dataset the dataset (<code>null</code> not permitted).
614 *
615 * @return The range of values (possibly <code>null</code>).
616 */
617 public static Range findDomainBounds(XYDataset dataset) {
618 return findDomainBounds(dataset, true);
619 }
620
621 /**
622 * Returns the range of values in the domain (x-values) of a dataset.
623 *
624 * @param dataset the dataset (<code>null</code> not permitted).
625 * @param includeInterval determines whether or not the x-interval is taken
626 * into account (only applies if the dataset is an
627 * {@link IntervalXYDataset}).
628 *
629 * @return The range of values (possibly <code>null</code>).
630 */
631 public static Range findDomainBounds(XYDataset dataset,
632 boolean includeInterval) {
633
634 if (dataset == null) {
635 throw new IllegalArgumentException("Null 'dataset' argument.");
636 }
637
638 Range result = null;
639 // if the dataset implements DomainInfo, life is easier
640 if (dataset instanceof DomainInfo) {
641 DomainInfo info = (DomainInfo) dataset;
642 result = info.getDomainBounds(includeInterval);
643 }
644 else {
645 result = iterateDomainBounds(dataset, includeInterval);
646 }
647 return result;
648
649 }
650
651 /**
652 * Iterates over the items in an {@link XYDataset} to find
653 * the range of x-values. If the dataset is an instance of
654 * {@link IntervalXYDataset}, the starting and ending x-values
655 * will be used for the bounds calculation.
656 *
657 * @param dataset the dataset (<code>null</code> not permitted).
658 *
659 * @return The range (possibly <code>null</code>).
660 */
661 public static Range iterateDomainBounds(XYDataset dataset) {
662 return iterateDomainBounds(dataset, true);
663 }
664
665 /**
666 * Iterates over the items in an {@link XYDataset} to find
667 * the range of x-values.
668 *
669 * @param dataset the dataset (<code>null</code> not permitted).
670 * @param includeInterval a flag that determines, for an
671 * {@link IntervalXYDataset}, whether the x-interval or just the
672 * x-value is used to determine the overall range.
673 *
674 * @return The range (possibly <code>null</code>).
675 */
676 public static Range iterateDomainBounds(XYDataset dataset,
677 boolean includeInterval) {
678 if (dataset == null) {
679 throw new IllegalArgumentException("Null 'dataset' argument.");
680 }
681 double minimum = Double.POSITIVE_INFINITY;
682 double maximum = Double.NEGATIVE_INFINITY;
683 int seriesCount = dataset.getSeriesCount();
684 double lvalue;
685 double uvalue;
686 if (includeInterval && dataset instanceof IntervalXYDataset) {
687 IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
688 for (int series = 0; series < seriesCount; series++) {
689 int itemCount = dataset.getItemCount(series);
690 for (int item = 0; item < itemCount; item++) {
691 lvalue = intervalXYData.getStartXValue(series, item);
692 uvalue = intervalXYData.getEndXValue(series, item);
693 if (!Double.isNaN(lvalue)) {
694 minimum = Math.min(minimum, lvalue);
695 }
696 if (!Double.isNaN(uvalue)) {
697 maximum = Math.max(maximum, uvalue);
698 }
699 }
700 }
701 }
702 else {
703 for (int series = 0; series < seriesCount; series++) {
704 int itemCount = dataset.getItemCount(series);
705 for (int item = 0; item < itemCount; item++) {
706 lvalue = dataset.getXValue(series, item);
707 uvalue = lvalue;
708 if (!Double.isNaN(lvalue)) {
709 minimum = Math.min(minimum, lvalue);
710 maximum = Math.max(maximum, uvalue);
711 }
712 }
713 }
714 }
715 if (minimum > maximum) {
716 return null;
717 }
718 else {
719 return new Range(minimum, maximum);
720 }
721 }
722
723 /**
724 * Returns the range of values in the range for the dataset.
725 *
726 * @param dataset the dataset (<code>null</code> not permitted).
727 *
728 * @return The range (possibly <code>null</code>).
729 */
730 public static Range findRangeBounds(CategoryDataset dataset) {
731 return findRangeBounds(dataset, true);
732 }
733
734 /**
735 * Returns the range of values in the range for the dataset.
736 *
737 * @param dataset the dataset (<code>null</code> not permitted).
738 * @param includeInterval a flag that determines whether or not the
739 * y-interval is taken into account.
740 *
741 * @return The range (possibly <code>null</code>).
742 */
743 public static Range findRangeBounds(CategoryDataset dataset,
744 boolean includeInterval) {
745 if (dataset == null) {
746 throw new IllegalArgumentException("Null 'dataset' argument.");
747 }
748 Range result = null;
749 if (dataset instanceof RangeInfo) {
750 RangeInfo info = (RangeInfo) dataset;
751 result = info.getRangeBounds(includeInterval);
752 }
753 else {
754 result = iterateRangeBounds(dataset, includeInterval);
755 }
756 return result;
757 }
758
759 /**
760 * Returns the range of values in the range for the dataset. This method
761 * is the partner for the {@link #findDomainBounds(XYDataset)} method.
762 *
763 * @param dataset the dataset (<code>null</code> not permitted).
764 *
765 * @return The range (possibly <code>null</code>).
766 */
767 public static Range findRangeBounds(XYDataset dataset) {
768 return findRangeBounds(dataset, true);
769 }
770
771 /**
772 * Returns the range of values in the range for the dataset. This method
773 * is the partner for the {@link #findDomainBounds(XYDataset, boolean)}
774 * method.
775 *
776 * @param dataset the dataset (<code>null</code> not permitted).
777 * @param includeInterval a flag that determines whether or not the
778 * y-interval is taken into account.
779 *
780 * @return The range (possibly <code>null</code>).
781 */
782 public static Range findRangeBounds(XYDataset dataset,
783 boolean includeInterval) {
784 if (dataset == null) {
785 throw new IllegalArgumentException("Null 'dataset' argument.");
786 }
787 Range result = null;
788 if (dataset instanceof RangeInfo) {
789 RangeInfo info = (RangeInfo) dataset;
790 result = info.getRangeBounds(includeInterval);
791 }
792 else {
793 result = iterateRangeBounds(dataset, includeInterval);
794 }
795 return result;
796 }
797
798 /**
799 * Iterates over the data item of the category dataset to find
800 * the range bounds.
801 *
802 * @param dataset the dataset (<code>null</code> not permitted).
803 * @param includeInterval a flag that determines whether or not the
804 * y-interval is taken into account.
805 *
806 * @return The range (possibly <code>null</code>).
807 *
808 * @deprecated As of 1.0.10, use
809 * {@link #iterateRangeBounds(CategoryDataset, boolean)}.
810 */
811 public static Range iterateCategoryRangeBounds(CategoryDataset dataset,
812 boolean includeInterval) {
813 return iterateRangeBounds(dataset, includeInterval);
814 }
815
816 /**
817 * Iterates over the data item of the category dataset to find
818 * the range bounds.
819 *
820 * @param dataset the dataset (<code>null</code> not permitted).
821 *
822 * @return The range (possibly <code>null</code>).
823 *
824 * @since 1.0.10
825 */
826 public static Range iterateRangeBounds(CategoryDataset dataset) {
827 return iterateRangeBounds(dataset, true);
828 }
829
830 /**
831 * Iterates over the data item of the category dataset to find
832 * the range bounds.
833 *
834 * @param dataset the dataset (<code>null</code> not permitted).
835 * @param includeInterval a flag that determines whether or not the
836 * y-interval is taken into account.
837 *
838 * @return The range (possibly <code>null</code>).
839 *
840 * @since 1.0.10
841 */
842 public static Range iterateRangeBounds(CategoryDataset dataset,
843 boolean includeInterval) {
844 double minimum = Double.POSITIVE_INFINITY;
845 double maximum = Double.NEGATIVE_INFINITY;
846 int rowCount = dataset.getRowCount();
847 int columnCount = dataset.getColumnCount();
848 if (includeInterval && dataset instanceof IntervalCategoryDataset) {
849 // handle the special case where the dataset has y-intervals that
850 // we want to measure
851 IntervalCategoryDataset icd = (IntervalCategoryDataset) dataset;
852 Number lvalue, uvalue;
853 for (int row = 0; row < rowCount; row++) {
854 for (int column = 0; column < columnCount; column++) {
855 lvalue = icd.getStartValue(row, column);
856 uvalue = icd.getEndValue(row, column);
857 if (lvalue != null && !Double.isNaN(lvalue.doubleValue())) {
858 minimum = Math.min(minimum, lvalue.doubleValue());
859 }
860 if (uvalue != null && !Double.isNaN(uvalue.doubleValue())) {
861 maximum = Math.max(maximum, uvalue.doubleValue());
862 }
863 }
864 }
865 }
866 else {
867 // handle the standard case (plain CategoryDataset)
868 for (int row = 0; row < rowCount; row++) {
869 for (int column = 0; column < columnCount; column++) {
870 Number value = dataset.getValue(row, column);
871 if (value != null) {
872 double v = value.doubleValue();
873 if (!Double.isNaN(v)) {
874 minimum = Math.min(minimum, v);
875 maximum = Math.max(maximum, v);
876 }
877 }
878 }
879 }
880 }
881 if (minimum == Double.POSITIVE_INFINITY) {
882 return null;
883 }
884 else {
885 return new Range(minimum, maximum);
886 }
887 }
888
889 /**
890 * Iterates over the data item of the xy dataset to find
891 * the range bounds.
892 *
893 * @param dataset the dataset (<code>null</code> not permitted).
894 *
895 * @return The range (possibly <code>null</code>).
896 *
897 * @deprecated As of 1.0.10, use {@link #iterateRangeBounds(XYDataset)}.
898 */
899 public static Range iterateXYRangeBounds(XYDataset dataset) {
900 return iterateRangeBounds(dataset);
901 }
902
903 /**
904 * Iterates over the data item of the xy dataset to find
905 * the range bounds.
906 *
907 * @param dataset the dataset (<code>null</code> not permitted).
908 *
909 * @return The range (possibly <code>null</code>).
910 *
911 * @since 1.0.10
912 */
913 public static Range iterateRangeBounds(XYDataset dataset) {
914 return iterateRangeBounds(dataset, true);
915 }
916
917 /**
918 * Iterates over the data items of the xy dataset to find
919 * the range bounds.
920 *
921 * @param dataset the dataset (<code>null</code> not permitted).
922 * @param includeInterval a flag that determines, for an
923 * {@link IntervalXYDataset}, whether the y-interval or just the
924 * y-value is used to determine the overall range.
925 *
926 * @return The range (possibly <code>null</code>).
927 *
928 * @since 1.0.10
929 */
930 public static Range iterateRangeBounds(XYDataset dataset,
931 boolean includeInterval) {
932 double minimum = Double.POSITIVE_INFINITY;
933 double maximum = Double.NEGATIVE_INFINITY;
934 int seriesCount = dataset.getSeriesCount();
935
936 // handle three cases by dataset type
937 if (includeInterval && dataset instanceof IntervalXYDataset) {
938 // handle special case of IntervalXYDataset
939 IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
940 for (int series = 0; series < seriesCount; series++) {
941 int itemCount = dataset.getItemCount(series);
942 for (int item = 0; item < itemCount; item++) {
943 double lvalue = ixyd.getStartYValue(series, item);
944 double uvalue = ixyd.getEndYValue(series, item);
945 if (!Double.isNaN(lvalue)) {
946 minimum = Math.min(minimum, lvalue);
947 }
948 if (!Double.isNaN(uvalue)) {
949 maximum = Math.max(maximum, uvalue);
950 }
951 }
952 }
953 }
954 else if (includeInterval && dataset instanceof OHLCDataset) {
955 // handle special case of OHLCDataset
956 OHLCDataset ohlc = (OHLCDataset) dataset;
957 for (int series = 0; series < seriesCount; series++) {
958 int itemCount = dataset.getItemCount(series);
959 for (int item = 0; item < itemCount; item++) {
960 double lvalue = ohlc.getLowValue(series, item);
961 double uvalue = ohlc.getHighValue(series, item);
962 if (!Double.isNaN(lvalue)) {
963 minimum = Math.min(minimum, lvalue);
964 }
965 if (!Double.isNaN(uvalue)) {
966 maximum = Math.max(maximum, uvalue);
967 }
968 }
969 }
970 }
971 else {
972 // standard case - plain XYDataset
973 for (int series = 0; series < seriesCount; series++) {
974 int itemCount = dataset.getItemCount(series);
975 for (int item = 0; item < itemCount; item++) {
976 double value = dataset.getYValue(series, item);
977 if (!Double.isNaN(value)) {
978 minimum = Math.min(minimum, value);
979 maximum = Math.max(maximum, value);
980 }
981 }
982 }
983 }
984 if (minimum == Double.POSITIVE_INFINITY) {
985 return null;
986 }
987 else {
988 return new Range(minimum, maximum);
989 }
990 }
991
992 /**
993 * Finds the minimum domain (or X) value for the specified dataset. This
994 * is easy if the dataset implements the {@link DomainInfo} interface (a
995 * good idea if there is an efficient way to determine the minimum value).
996 * Otherwise, it involves iterating over the entire data-set.
997 * <p>
998 * Returns <code>null</code> if all the data values in the dataset are
999 * <code>null</code>.
1000 *
1001 * @param dataset the dataset (<code>null</code> not permitted).
1002 *
1003 * @return The minimum value (possibly <code>null</code>).
1004 */
1005 public static Number findMinimumDomainValue(XYDataset dataset) {
1006 if (dataset == null) {
1007 throw new IllegalArgumentException("Null 'dataset' argument.");
1008 }
1009 Number result = null;
1010 // if the dataset implements DomainInfo, life is easy
1011 if (dataset instanceof DomainInfo) {
1012 DomainInfo info = (DomainInfo) dataset;
1013 return new Double(info.getDomainLowerBound(true));
1014 }
1015 else {
1016 double minimum = Double.POSITIVE_INFINITY;
1017 int seriesCount = dataset.getSeriesCount();
1018 for (int series = 0; series < seriesCount; series++) {
1019 int itemCount = dataset.getItemCount(series);
1020 for (int item = 0; item < itemCount; item++) {
1021
1022 double value;
1023 if (dataset instanceof IntervalXYDataset) {
1024 IntervalXYDataset intervalXYData
1025 = (IntervalXYDataset) dataset;
1026 value = intervalXYData.getStartXValue(series, item);
1027 }
1028 else {
1029 value = dataset.getXValue(series, item);
1030 }
1031 if (!Double.isNaN(value)) {
1032 minimum = Math.min(minimum, value);
1033 }
1034
1035 }
1036 }
1037 if (minimum == Double.POSITIVE_INFINITY) {
1038 result = null;
1039 }
1040 else {
1041 result = new Double(minimum);
1042 }
1043 }
1044
1045 return result;
1046 }
1047
1048 /**
1049 * Returns the maximum domain value for the specified dataset. This is
1050 * easy if the dataset implements the {@link DomainInfo} interface (a good
1051 * idea if there is an efficient way to determine the maximum value).
1052 * Otherwise, it involves iterating over the entire data-set. Returns
1053 * <code>null</code> if all the data values in the dataset are
1054 * <code>null</code>.
1055 *
1056 * @param dataset the dataset (<code>null</code> not permitted).
1057 *
1058 * @return The maximum value (possibly <code>null</code>).
1059 */
1060 public static Number findMaximumDomainValue(XYDataset dataset) {
1061 if (dataset == null) {
1062 throw new IllegalArgumentException("Null 'dataset' argument.");
1063 }
1064 Number result = null;
1065 // if the dataset implements DomainInfo, life is easy
1066 if (dataset instanceof DomainInfo) {
1067 DomainInfo info = (DomainInfo) dataset;
1068 return new Double(info.getDomainUpperBound(true));
1069 }
1070
1071 // hasn't implemented DomainInfo, so iterate...
1072 else {
1073 double maximum = Double.NEGATIVE_INFINITY;
1074 int seriesCount = dataset.getSeriesCount();
1075 for (int series = 0; series < seriesCount; series++) {
1076 int itemCount = dataset.getItemCount(series);
1077 for (int item = 0; item < itemCount; item++) {
1078
1079 double value;
1080 if (dataset instanceof IntervalXYDataset) {
1081 IntervalXYDataset intervalXYData
1082 = (IntervalXYDataset) dataset;
1083 value = intervalXYData.getEndXValue(series, item);
1084 }
1085 else {
1086 value = dataset.getXValue(series, item);
1087 }
1088 if (!Double.isNaN(value)) {
1089 maximum = Math.max(maximum, value);
1090 }
1091 }
1092 }
1093 if (maximum == Double.NEGATIVE_INFINITY) {
1094 result = null;
1095 }
1096 else {
1097 result = new Double(maximum);
1098 }
1099
1100 }
1101
1102 return result;
1103 }
1104
1105 /**
1106 * Returns the minimum range value for the specified dataset. This is
1107 * easy if the dataset implements the {@link RangeInfo} interface (a good
1108 * idea if there is an efficient way to determine the minimum value).
1109 * Otherwise, it involves iterating over the entire data-set. Returns
1110 * <code>null</code> if all the data values in the dataset are
1111 * <code>null</code>.
1112 *
1113 * @param dataset the dataset (<code>null</code> not permitted).
1114 *
1115 * @return The minimum value (possibly <code>null</code>).
1116 */
1117 public static Number findMinimumRangeValue(CategoryDataset dataset) {
1118
1119 // check parameters...
1120 if (dataset == null) {
1121 throw new IllegalArgumentException("Null 'dataset' argument.");
1122 }
1123
1124 // work out the minimum value...
1125 if (dataset instanceof RangeInfo) {
1126 RangeInfo info = (RangeInfo) dataset;
1127 return new Double(info.getRangeLowerBound(true));
1128 }
1129
1130 // hasn't implemented RangeInfo, so we'll have to iterate...
1131 else {
1132 double minimum = Double.POSITIVE_INFINITY;
1133 int seriesCount = dataset.getRowCount();
1134 int itemCount = dataset.getColumnCount();
1135 for (int series = 0; series < seriesCount; series++) {
1136 for (int item = 0; item < itemCount; item++) {
1137 Number value;
1138 if (dataset instanceof IntervalCategoryDataset) {
1139 IntervalCategoryDataset icd
1140 = (IntervalCategoryDataset) dataset;
1141 value = icd.getStartValue(series, item);
1142 }
1143 else {
1144 value = dataset.getValue(series, item);
1145 }
1146 if (value != null) {
1147 minimum = Math.min(minimum, value.doubleValue());
1148 }
1149 }
1150 }
1151 if (minimum == Double.POSITIVE_INFINITY) {
1152 return null;
1153 }
1154 else {
1155 return new Double(minimum);
1156 }
1157
1158 }
1159
1160 }
1161
1162 /**
1163 * Returns the minimum range value for the specified dataset. This is
1164 * easy if the dataset implements the {@link RangeInfo} interface (a good
1165 * idea if there is an efficient way to determine the minimum value).
1166 * Otherwise, it involves iterating over the entire data-set. Returns
1167 * <code>null</code> if all the data values in the dataset are
1168 * <code>null</code>.
1169 *
1170 * @param dataset the dataset (<code>null</code> not permitted).
1171 *
1172 * @return The minimum value (possibly <code>null</code>).
1173 */
1174 public static Number findMinimumRangeValue(XYDataset dataset) {
1175
1176 if (dataset == null) {
1177 throw new IllegalArgumentException("Null 'dataset' argument.");
1178 }
1179
1180 // work out the minimum value...
1181 if (dataset instanceof RangeInfo) {
1182 RangeInfo info = (RangeInfo) dataset;
1183 return new Double(info.getRangeLowerBound(true));
1184 }
1185
1186 // hasn't implemented RangeInfo, so we'll have to iterate...
1187 else {
1188 double minimum = Double.POSITIVE_INFINITY;
1189 int seriesCount = dataset.getSeriesCount();
1190 for (int series = 0; series < seriesCount; series++) {
1191 int itemCount = dataset.getItemCount(series);
1192 for (int item = 0; item < itemCount; item++) {
1193
1194 double value;
1195 if (dataset instanceof IntervalXYDataset) {
1196 IntervalXYDataset intervalXYData
1197 = (IntervalXYDataset) dataset;
1198 value = intervalXYData.getStartYValue(series, item);
1199 }
1200 else if (dataset instanceof OHLCDataset) {
1201 OHLCDataset highLowData = (OHLCDataset) dataset;
1202 value = highLowData.getLowValue(series, item);
1203 }
1204 else {
1205 value = dataset.getYValue(series, item);
1206 }
1207 if (!Double.isNaN(value)) {
1208 minimum = Math.min(minimum, value);
1209 }
1210
1211 }
1212 }
1213 if (minimum == Double.POSITIVE_INFINITY) {
1214 return null;
1215 }
1216 else {
1217 return new Double(minimum);
1218 }
1219
1220 }
1221
1222 }
1223
1224 /**
1225 * Returns the maximum range value for the specified dataset. This is easy
1226 * if the dataset implements the {@link RangeInfo} interface (a good idea
1227 * if there is an efficient way to determine the maximum value).
1228 * Otherwise, it involves iterating over the entire data-set. Returns
1229 * <code>null</code> if all the data values are <code>null</code>.
1230 *
1231 * @param dataset the dataset (<code>null</code> not permitted).
1232 *
1233 * @return The maximum value (possibly <code>null</code>).
1234 */
1235 public static Number findMaximumRangeValue(CategoryDataset dataset) {
1236
1237 if (dataset == null) {
1238 throw new IllegalArgumentException("Null 'dataset' argument.");
1239 }
1240
1241 // work out the minimum value...
1242 if (dataset instanceof RangeInfo) {
1243 RangeInfo info = (RangeInfo) dataset;
1244 return new Double(info.getRangeUpperBound(true));
1245 }
1246
1247 // hasn't implemented RangeInfo, so we'll have to iterate...
1248 else {
1249
1250 double maximum = Double.NEGATIVE_INFINITY;
1251 int seriesCount = dataset.getRowCount();
1252 int itemCount = dataset.getColumnCount();
1253 for (int series = 0; series < seriesCount; series++) {
1254 for (int item = 0; item < itemCount; item++) {
1255 Number value;
1256 if (dataset instanceof IntervalCategoryDataset) {
1257 IntervalCategoryDataset icd
1258 = (IntervalCategoryDataset) dataset;
1259 value = icd.getEndValue(series, item);
1260 }
1261 else {
1262 value = dataset.getValue(series, item);
1263 }
1264 if (value != null) {
1265 maximum = Math.max(maximum, value.doubleValue());
1266 }
1267 }
1268 }
1269 if (maximum == Double.NEGATIVE_INFINITY) {
1270 return null;
1271 }
1272 else {
1273 return new Double(maximum);
1274 }
1275
1276 }
1277
1278 }
1279
1280 /**
1281 * Returns the maximum range value for the specified dataset. This is
1282 * easy if the dataset implements the {@link RangeInfo} interface (a good
1283 * idea if there is an efficient way to determine the maximum value).
1284 * Otherwise, it involves iterating over the entire data-set. Returns
1285 * <code>null</code> if all the data values are <code>null</code>.
1286 *
1287 * @param dataset the dataset (<code>null</code> not permitted).
1288 *
1289 * @return The maximum value (possibly <code>null</code>).
1290 */
1291 public static Number findMaximumRangeValue(XYDataset dataset) {
1292
1293 if (dataset == null) {
1294 throw new IllegalArgumentException("Null 'dataset' argument.");
1295 }
1296
1297 // work out the minimum value...
1298 if (dataset instanceof RangeInfo) {
1299 RangeInfo info = (RangeInfo) dataset;
1300 return new Double(info.getRangeUpperBound(true));
1301 }
1302
1303 // hasn't implemented RangeInfo, so we'll have to iterate...
1304 else {
1305
1306 double maximum = Double.NEGATIVE_INFINITY;
1307 int seriesCount = dataset.getSeriesCount();
1308 for (int series = 0; series < seriesCount; series++) {
1309 int itemCount = dataset.getItemCount(series);
1310 for (int item = 0; item < itemCount; item++) {
1311 double value;
1312 if (dataset instanceof IntervalXYDataset) {
1313 IntervalXYDataset intervalXYData
1314 = (IntervalXYDataset) dataset;
1315 value = intervalXYData.getEndYValue(series, item);
1316 }
1317 else if (dataset instanceof OHLCDataset) {
1318 OHLCDataset highLowData = (OHLCDataset) dataset;
1319 value = highLowData.getHighValue(series, item);
1320 }
1321 else {
1322 value = dataset.getYValue(series, item);
1323 }
1324 if (!Double.isNaN(value)) {
1325 maximum = Math.max(maximum, value);
1326 }
1327 }
1328 }
1329 if (maximum == Double.NEGATIVE_INFINITY) {
1330 return null;
1331 }
1332 else {
1333 return new Double(maximum);
1334 }
1335
1336 }
1337
1338 }
1339
1340 /**
1341 * Returns the minimum and maximum values for the dataset's range
1342 * (y-values), assuming that the series in one category are stacked.
1343 *
1344 * @param dataset the dataset (<code>null</code> not permitted).
1345 *
1346 * @return The range (<code>null</code> if the dataset contains no values).
1347 */
1348 publ