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 *
039 * Changes (from 18-Sep-2001)
040 * --------------------------
041 * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
042 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
043 * 15-Nov-2001 : Moved to package com.jrefinery.data.* in the JCommon class
044 * library (DG);
045 * Changed to handle null values from datasets (DG);
046 * Bug fix (thanks to Andrzej Porebski) - initial value now set
047 * to positive or negative infinity when iterating (DG);
048 * 22-Nov-2001 : Datasets with containing no data now return null for min and
049 * max calculations (DG);
050 * 13-Dec-2001 : Extended to handle HighLowDataset and IntervalXYDataset (DG);
051 * 15-Feb-2002 : Added getMinimumStackedRangeValue() and
052 * getMaximumStackedRangeValue() (DG);
053 * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
054 * 18-Mar-2002 : Fixed bug in min/max domain calculation for datasets that
055 * implement the CategoryDataset interface AND the XYDataset
056 * interface at the same time. Thanks to Jonathan Nash for the
057 * fix (DG);
058 * 23-Apr-2002 : Added getDomainExtent() and getRangeExtent() methods (DG);
059 * 13-Jun-2002 : Modified range measurements to handle
060 * IntervalCategoryDataset (DG);
061 * 12-Jul-2002 : Method name change in DomainInfo interface (DG);
062 * 30-Jul-2002 : Added pie dataset summation method (DG);
063 * 01-Oct-2002 : Added a method for constructing an XYDataset from a Function2D
064 * instance (DG);
065 * 24-Oct-2002 : Amendments required following changes to the CategoryDataset
066 * interface (DG);
067 * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
068 * 04-Mar-2003 : Added isEmpty(XYDataset) method (DG);
069 * 05-Mar-2003 : Added a method for creating a CategoryDataset from a
070 * KeyedValues instance (DG);
071 * 15-May-2003 : Renamed isEmpty --> isEmptyOrNull (DG);
072 * 25-Jun-2003 : Added limitPieDataset methods (RA);
073 * 26-Jun-2003 : Modified getDomainExtent() method to accept null datasets (DG);
074 * 27-Jul-2003 : Added getStackedRangeExtent(TableXYDataset data) (RA);
075 * 18-Aug-2003 : getStackedRangeExtent(TableXYDataset data) now handles null
076 * values (RA);
077 * 02-Sep-2003 : Added method to check for null or empty PieDataset (DG);
078 * 18-Sep-2003 : Fix for bug 803660 (getMaximumRangeValue for
079 * CategoryDataset) (DG);
080 * 20-Oct-2003 : Added getCumulativeRangeExtent() method (DG);
081 * 09-Jan-2003 : Added argument checking code to the createCategoryDataset()
082 * method (DG);
083 * 23-Mar-2004 : Fixed bug in getMaximumStackedRangeValue() method (DG);
084 * 31-Mar-2004 : Exposed the extent iteration algorithms to use one of them and
085 * applied noninstantiation pattern (AS);
086 * 11-May-2004 : Renamed getPieDatasetTotal --> calculatePieDatasetTotal (DG);
087 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue();
088 * 24-Aug-2004 : Added argument checks to createCategoryDataset() method (DG);
089 * 04-Oct-2004 : Renamed ArrayUtils --> ArrayUtilities (DG);
090 * 06-Oct-2004 : Renamed findDomainExtent() --> findDomainBounds(),
091 * findRangeExtent() --> findRangeBounds() (DG);
092 * 07-Jan-2005 : Renamed findStackedRangeExtent() --> findStackedRangeBounds(),
093 * findCumulativeRangeExtent() --> findCumulativeRangeBounds(),
094 * iterateXYRangeExtent() --> iterateXYRangeBounds(),
095 * removed deprecated methods (DG);
096 * 03-Feb-2005 : The findStackedRangeBounds() methods now return null for
097 * empty datasets (DG);
098 * 03-Mar-2005 : Moved createNumberArray() and createNumberArray2D() methods
099 * from DatasetUtilities --> DataUtilities (DG);
100 * 22-Sep-2005 : Added new findStackedRangeBounds() method that takes base
101 * argument (DG);
102 * ------------- JFREECHART 1.0.x ---------------------------------------------
103 * 15-Mar-2007 : Added calculateStackTotal() method (DG);
104 * 27-Mar-2008 : Fixed bug in findCumulativeRangeBounds() method (DG);
105 * 28-Mar-2008 : Fixed sample count in sampleFunction2D() method, renamed
106 * iterateXYRangeBounds() --> iterateRangeBounds(XYDataset), and
107 * fixed a bug in findRangeBounds(XYDataset, false) (DG);
108 * 28-Mar-2008 : Applied a variation of patch 1925366 (from Rafal Skalny) for
109 * slightly more efficient iterateRangeBounds() methods (DG);
110 * 08-Apr-2008 : Fixed typo in iterateRangeBounds() (DG);
111 *
112 */
113
114 package org.jfree.data.general;
115
116 import java.util.ArrayList;
117 import java.util.Iterator;
118 import java.util.List;
119
120 import org.jfree.data.DomainInfo;
121 import org.jfree.data.KeyToGroupMap;
122 import org.jfree.data.KeyedValues;
123 import org.jfree.data.Range;
124 import org.jfree.data.RangeInfo;
125 import org.jfree.data.category.CategoryDataset;
126 import org.jfree.data.category.DefaultCategoryDataset;
127 import org.jfree.data.category.IntervalCategoryDataset;
128 import org.jfree.data.function.Function2D;
129 import org.jfree.data.xy.IntervalXYDataset;
130 import org.jfree.data.xy.OHLCDataset;
131 import org.jfree.data.xy.TableXYDataset;
132 import org.jfree.data.xy.XYDataset;
133 import org.jfree.data.xy.XYSeries;
134 import org.jfree.data.xy.XYSeriesCollection;
135 import org.jfree.util.ArrayUtilities;
136
137 /**
138 * A collection of useful static methods relating to datasets.
139 */
140 public final class DatasetUtilities {
141
142 /**
143 * Private constructor for non-instanceability.
144 */
145 private DatasetUtilities() {
146 // now try to instantiate this ;-)
147 }
148
149 /**
150 * Calculates the total of all the values in a {@link PieDataset}. If
151 * the dataset contains negative or <code>null</code> values, they are
152 * ignored.
153 *
154 * @param dataset the dataset (<code>null</code> not permitted).
155 *
156 * @return The total.
157 */
158 public static double calculatePieDatasetTotal(PieDataset dataset) {
159 if (dataset == null) {
160 throw new IllegalArgumentException("Null 'dataset' argument.");
161 }
162 List keys = dataset.getKeys();
163 double totalValue = 0;
164 Iterator iterator = keys.iterator();
165 while (iterator.hasNext()) {
166 Comparable current = (Comparable) iterator.next();
167 if (current != null) {
168 Number value = dataset.getValue(current);
169 double v = 0.0;
170 if (value != null) {
171 v = value.doubleValue();
172 }
173 if (v > 0) {
174 totalValue = totalValue + v;
175 }
176 }
177 }
178 return totalValue;
179 }
180
181 /**
182 * Creates a pie dataset from a table dataset by taking all the values
183 * for a single row.
184 *
185 * @param dataset the dataset (<code>null</code> not permitted).
186 * @param rowKey the row key.
187 *
188 * @return A pie dataset.
189 */
190 public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
191 Comparable rowKey) {
192 int row = dataset.getRowIndex(rowKey);
193 return createPieDatasetForRow(dataset, row);
194 }
195
196 /**
197 * Creates a pie dataset from a table dataset by taking all the values
198 * for a single row.
199 *
200 * @param dataset the dataset (<code>null</code> not permitted).
201 * @param row the row (zero-based index).
202 *
203 * @return A pie dataset.
204 */
205 public static PieDataset createPieDatasetForRow(CategoryDataset dataset,
206 int row) {
207 DefaultPieDataset result = new DefaultPieDataset();
208 int columnCount = dataset.getColumnCount();
209 for (int current = 0; current < columnCount; current++) {
210 Comparable columnKey = dataset.getColumnKey(current);
211 result.setValue(columnKey, dataset.getValue(row, current));
212 }
213 return result;
214 }
215
216 /**
217 * Creates a pie dataset from a table dataset by taking all the values
218 * for a single column.
219 *
220 * @param dataset the dataset (<code>null</code> not permitted).
221 * @param columnKey the column key.
222 *
223 * @return A pie dataset.
224 */
225 public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
226 Comparable columnKey) {
227 int column = dataset.getColumnIndex(columnKey);
228 return createPieDatasetForColumn(dataset, column);
229 }
230
231 /**
232 * Creates a pie dataset from a {@link CategoryDataset} by taking all the
233 * values for a single column.
234 *
235 * @param dataset the dataset (<code>null</code> not permitted).
236 * @param column the column (zero-based index).
237 *
238 * @return A pie dataset.
239 */
240 public static PieDataset createPieDatasetForColumn(CategoryDataset dataset,
241 int column) {
242 DefaultPieDataset result = new DefaultPieDataset();
243 int rowCount = dataset.getRowCount();
244 for (int i = 0; i < rowCount; i++) {
245 Comparable rowKey = dataset.getRowKey(i);
246 result.setValue(rowKey, dataset.getValue(i, column));
247 }
248 return result;
249 }
250
251 /**
252 * Creates a new pie dataset based on the supplied dataset, but modified
253 * by aggregating all the low value items (those whose value is lower
254 * than the <code>percentThreshold</code>) into a single item with the
255 * key "Other".
256 *
257 * @param source the source dataset (<code>null</code> not permitted).
258 * @param key a new key for the aggregated items (<code>null</code> not
259 * permitted).
260 * @param minimumPercent the percent threshold.
261 *
262 * @return The pie dataset with (possibly) aggregated items.
263 */
264 public static PieDataset createConsolidatedPieDataset(PieDataset source,
265 Comparable key,
266 double minimumPercent)
267 {
268 return DatasetUtilities.createConsolidatedPieDataset(
269 source, key, minimumPercent, 2
270 );
271 }
272
273 /**
274 * Creates a new pie dataset based on the supplied dataset, but modified
275 * by aggregating all the low value items (those whose value is lower
276 * than the <code>percentThreshold</code>) into a single item. The
277 * aggregated items are assigned the specified key. Aggregation only
278 * occurs if there are at least <code>minItems</code> items to aggregate.
279 *
280 * @param source the source dataset (<code>null</code> not permitted).
281 * @param key the key to represent the aggregated items.
282 * @param minimumPercent the percent threshold (ten percent is 0.10).
283 * @param minItems only aggregate low values if there are at least this
284 * many.
285 *
286 * @return The pie dataset with (possibly) aggregated items.
287 */
288 public static PieDataset createConsolidatedPieDataset(PieDataset source,
289 Comparable key,
290 double minimumPercent,
291 int minItems) {
292
293 DefaultPieDataset result = new DefaultPieDataset();
294 double total = DatasetUtilities.calculatePieDatasetTotal(source);
295
296 // Iterate and find all keys below threshold percentThreshold
297 List keys = source.getKeys();
298 ArrayList otherKeys = new ArrayList();
299 Iterator iterator = keys.iterator();
300 while (iterator.hasNext()) {
301 Comparable currentKey = (Comparable) iterator.next();
302 Number dataValue = source.getValue(currentKey);
303 if (dataValue != null) {
304 double value = dataValue.doubleValue();
305 if (value / total < minimumPercent) {
306 otherKeys.add(currentKey);
307 }
308 }
309 }
310
311 // Create new dataset with keys above threshold percentThreshold
312 iterator = keys.iterator();
313 double otherValue = 0;
314 while (iterator.hasNext()) {
315 Comparable currentKey = (Comparable) iterator.next();
316 Number dataValue = source.getValue(currentKey);
317 if (dataValue != null) {
318 if (otherKeys.contains(currentKey)
319 && otherKeys.size() >= minItems) {
320 // Do not add key to dataset
321 otherValue += dataValue.doubleValue();
322 }
323 else {
324 // Add key to dataset
325 result.setValue(currentKey, dataValue);
326 }
327 }
328 }
329 // Add other category if applicable
330 if (otherKeys.size() >= minItems) {
331 result.setValue(key, otherValue);
332 }
333 return result;
334 }
335
336 /**
337 * Creates a {@link CategoryDataset} that contains a copy of the data in an
338 * array (instances of <code>Double</code> are created to represent the
339 * data items).
340 * <p>
341 * Row and column keys are created by appending 0, 1, 2, ... to the
342 * supplied prefixes.
343 *
344 * @param rowKeyPrefix the row key prefix.
345 * @param columnKeyPrefix the column key prefix.
346 * @param data the data.
347 *
348 * @return The dataset.
349 */
350 public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
351 String columnKeyPrefix,
352 double[][] data) {
353
354 DefaultCategoryDataset result = new DefaultCategoryDataset();
355 for (int r = 0; r < data.length; r++) {
356 String rowKey = rowKeyPrefix + (r + 1);
357 for (int c = 0; c < data[r].length; c++) {
358 String columnKey = columnKeyPrefix + (c + 1);
359 result.addValue(new Double(data[r][c]), rowKey, columnKey);
360 }
361 }
362 return result;
363
364 }
365
366 /**
367 * Creates a {@link CategoryDataset} that contains a copy of the data in
368 * an array.
369 * <p>
370 * Row and column keys are created by appending 0, 1, 2, ... to the
371 * supplied prefixes.
372 *
373 * @param rowKeyPrefix the row key prefix.
374 * @param columnKeyPrefix the column key prefix.
375 * @param data the data.
376 *
377 * @return The dataset.
378 */
379 public static CategoryDataset createCategoryDataset(String rowKeyPrefix,
380 String columnKeyPrefix,
381 Number[][] data) {
382
383 DefaultCategoryDataset result = new DefaultCategoryDataset();
384 for (int r = 0; r < data.length; r++) {
385 String rowKey = rowKeyPrefix + (r + 1);
386 for (int c = 0; c < data[r].length; c++) {
387 String columnKey = columnKeyPrefix + (c + 1);
388 result.addValue(data[r][c], rowKey, columnKey);
389 }
390 }
391 return result;
392
393 }
394
395 /**
396 * Creates a {@link CategoryDataset} that contains a copy of the data in
397 * an array (instances of <code>Double</code> are created to represent the
398 * data items).
399 * <p>
400 * Row and column keys are taken from the supplied arrays.
401 *
402 * @param rowKeys the row keys (<code>null</code> not permitted).
403 * @param columnKeys the column keys (<code>null</code> not permitted).
404 * @param data the data.
405 *
406 * @return The dataset.
407 */
408 public static CategoryDataset createCategoryDataset(Comparable[] rowKeys,
409 Comparable[] columnKeys,
410 double[][] data) {
411
412 // check arguments...
413 if (rowKeys == null) {
414 throw new IllegalArgumentException("Null 'rowKeys' argument.");
415 }
416 if (columnKeys == null) {
417 throw new IllegalArgumentException("Null 'columnKeys' argument.");
418 }
419 if (ArrayUtilities.hasDuplicateItems(rowKeys)) {
420 throw new IllegalArgumentException("Duplicate items in 'rowKeys'.");
421 }
422 if (ArrayUtilities.hasDuplicateItems(columnKeys)) {
423 throw new IllegalArgumentException(
424 "Duplicate items in 'columnKeys'."
425 );
426 }
427 if (rowKeys.length != data.length) {
428 throw new IllegalArgumentException(
429 "The number of row keys does not match the number of rows in "
430 + "the data array."
431 );
432 }
433 int columnCount = 0;
434 for (int r = 0; r < data.length; r++) {
435 columnCount = Math.max(columnCount, data[r].length);
436 }
437 if (columnKeys.length != columnCount) {
438 throw new IllegalArgumentException(
439 "The number of column keys does not match the number of "
440 + "columns in the data array."
441 );
442 }
443
444 // now do the work...
445 DefaultCategoryDataset result = new DefaultCategoryDataset();
446 for (int r = 0; r < data.length; r++) {
447 Comparable rowKey = rowKeys[r];
448 for (int c = 0; c < data[r].length; c++) {
449 Comparable columnKey = columnKeys[c];
450 result.addValue(new Double(data[r][c]), rowKey, columnKey);
451 }
452 }
453 return result;
454
455 }
456
457 /**
458 * Creates a {@link CategoryDataset} by copying the data from the supplied
459 * {@link KeyedValues} instance.
460 *
461 * @param rowKey the row key (<code>null</code> not permitted).
462 * @param rowData the row data (<code>null</code> not permitted).
463 *
464 * @return A dataset.
465 */
466 public static CategoryDataset createCategoryDataset(Comparable rowKey,
467 KeyedValues rowData) {
468
469 if (rowKey == null) {
470 throw new IllegalArgumentException("Null 'rowKey' argument.");
471 }
472 if (rowData == null) {
473 throw new IllegalArgumentException("Null 'rowData' argument.");
474 }
475 DefaultCategoryDataset result = new DefaultCategoryDataset();
476 for (int i = 0; i < rowData.getItemCount(); i++) {
477 result.addValue(rowData.getValue(i), rowKey, rowData.getKey(i));
478 }
479 return result;
480
481 }
482
483 /**
484 * Creates an {@link XYDataset} by sampling the specified function over a
485 * fixed range.
486 *
487 * @param f the function (<code>null</code> not permitted).
488 * @param start the start value for the range.
489 * @param end the end value for the range.
490 * @param samples the number of sample points (must be > 1).
491 * @param seriesKey the key to give the resulting series
492 * (<code>null</code> not permitted).
493 *
494 * @return A dataset.
495 */
496 public static XYDataset sampleFunction2D(Function2D f, double start,
497 double end, int samples, Comparable seriesKey) {
498
499 if (f == null) {
500 throw new IllegalArgumentException("Null 'f' argument.");
501 }
502 if (seriesKey == null) {
503 throw new IllegalArgumentException("Null 'seriesKey' argument.");
504 }
505 if (start >= end) {
506 throw new IllegalArgumentException("Requires 'start' < 'end'.");
507 }
508 if (samples < 2) {
509 throw new IllegalArgumentException("Requires 'samples' > 1");
510 }
511
512 XYSeries series = new XYSeries(seriesKey);
513 double step = (end - start) / (samples - 1);
514 for (int i = 0; i < samples; i++) {
515 double x = start + (step * i);
516 series.add(x, f.getValue(x));
517 }
518 XYSeriesCollection collection = new XYSeriesCollection(series);
519 return collection;
520 }
521
522 /**
523 * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
524 * and <code>false</code> otherwise.
525 *
526 * @param dataset the dataset (<code>null</code> permitted).
527 *
528 * @return A boolean.
529 */
530 public static boolean isEmptyOrNull(PieDataset dataset) {
531
532 if (dataset == null) {
533 return true;
534 }
535
536 int itemCount = dataset.getItemCount();
537 if (itemCount == 0) {
538 return true;
539 }
540
541 for (int item = 0; item < itemCount; item++) {
542 Number y = dataset.getValue(item);
543 if (y != null) {
544 double yy = y.doubleValue();
545 if (yy > 0.0) {
546 return false;
547 }
548 }
549 }
550
551 return true;
552
553 }
554
555 /**
556 * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
557 * and <code>false</code> otherwise.
558 *
559 * @param dataset the dataset (<code>null</code> permitted).
560 *
561 * @return A boolean.
562 */
563 public static boolean isEmptyOrNull(CategoryDataset dataset) {
564
565 if (dataset == null) {
566 return true;
567 }
568
569 int rowCount = dataset.getRowCount();
570 int columnCount = dataset.getColumnCount();
571 if (rowCount == 0 || columnCount == 0) {
572 return true;
573 }
574
575 for (int r = 0; r < rowCount; r++) {
576 for (int c = 0; c < columnCount; c++) {
577 if (dataset.getValue(r, c) != null) {
578 return false;
579 }
580
581 }
582 }
583
584 return true;
585
586 }
587
588 /**
589 * Returns <code>true</code> if the dataset is empty (or <code>null</code>),
590 * and <code>false</code> otherwise.
591 *
592 * @param dataset the dataset (<code>null</code> permitted).
593 *
594 * @return A boolean.
595 */
596 public static boolean isEmptyOrNull(XYDataset dataset) {
597 if (dataset != null) {
598 for (int s = 0; s < dataset.getSeriesCount(); s++) {
599 if (dataset.getItemCount(s) > 0) {
600 return false;
601 }
602 }
603 }
604 return true;
605 }
606
607 /**
608 * Returns the range of values in the domain (x-values) of a dataset.
609 *
610 * @param dataset the dataset (<code>null</code> not permitted).
611 *
612 * @return The range of values (possibly <code>null</code>).
613 */
614 public static Range findDomainBounds(XYDataset dataset) {
615 return findDomainBounds(dataset, true);
616 }
617
618 /**
619 * Returns the range of values in the domain (x-values) of a dataset.
620 *
621 * @param dataset the dataset (<code>null</code> not permitted).
622 * @param includeInterval determines whether or not the x-interval is taken
623 * into account (only applies if the dataset is an
624 * {@link IntervalXYDataset}).
625 *
626 * @return The range of values (possibly <code>null</code>).
627 */
628 public static Range findDomainBounds(XYDataset dataset,
629 boolean includeInterval) {
630
631 if (dataset == null) {
632 throw new IllegalArgumentException("Null 'dataset' argument.");
633 }
634
635 Range result = null;
636 // if the dataset implements DomainInfo, life is easier
637 if (dataset instanceof DomainInfo) {
638 DomainInfo info = (DomainInfo) dataset;
639 result = info.getDomainBounds(includeInterval);
640 }
641 else {
642 result = iterateDomainBounds(dataset, includeInterval);
643 }
644 return result;
645
646 }
647
648 /**
649 * Iterates over the items in an {@link XYDataset} to find
650 * the range of x-values. If the dataset is an instance of
651 * {@link IntervalXYDataset}, the starting and ending x-values
652 * will be used for the bounds calculation.
653 *
654 * @param dataset the dataset (<code>null</code> not permitted).
655 *
656 * @return The range (possibly <code>null</code>).
657 */
658 public static Range iterateDomainBounds(XYDataset dataset) {
659 return iterateDomainBounds(dataset, true);
660 }
661
662 /**
663 * Iterates over the items in an {@link XYDataset} to find
664 * the range of x-values.
665 *
666 * @param dataset the dataset (<code>null</code> not permitted).
667 * @param includeInterval a flag that determines, for an
668 * {@link IntervalXYDataset}, whether the x-interval or just the
669 * x-value is used to determine the overall range.
670 *
671 * @return The range (possibly <code>null</code>).
672 */
673 public static Range iterateDomainBounds(XYDataset dataset,
674 boolean includeInterval) {
675 if (dataset == null) {
676 throw new IllegalArgumentException("Null 'dataset' argument.");
677 }
678 double minimum = Double.POSITIVE_INFINITY;
679 double maximum = Double.NEGATIVE_INFINITY;
680 int seriesCount = dataset.getSeriesCount();
681 double lvalue;
682 double uvalue;
683 if (includeInterval && dataset instanceof IntervalXYDataset) {
684 IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
685 for (int series = 0; series < seriesCount; series++) {
686 int itemCount = dataset.getItemCount(series);
687 for (int item = 0; item < itemCount; item++) {
688 lvalue = intervalXYData.getStartXValue(series, item);
689 uvalue = intervalXYData.getEndXValue(series, item);
690 minimum = Math.min(minimum, lvalue);
691 maximum = Math.max(maximum, uvalue);
692 }
693 }
694 }
695 else {
696 for (int series = 0; series < seriesCount; series++) {
697 int itemCount = dataset.getItemCount(series);
698 for (int item = 0; item < itemCount; item++) {
699 lvalue = dataset.getXValue(series, item);
700 uvalue = lvalue;
701 minimum = Math.min(minimum, lvalue);
702 maximum = Math.max(maximum, uvalue);
703 }
704 }
705 }
706 if (minimum > maximum) {
707 return null;
708 }
709 else {
710 return new Range(minimum, maximum);
711 }
712 }
713
714 /**
715 * Returns the range of values in the range for the dataset.
716 *
717 * @param dataset the dataset (<code>null</code> not permitted).
718 *
719 * @return The range (possibly <code>null</code>).
720 */
721 public static Range findRangeBounds(CategoryDataset dataset) {
722 return findRangeBounds(dataset, true);
723 }
724
725 /**
726 * Returns the range of values in the range for the dataset.
727 *
728 * @param dataset the dataset (<code>null</code> not permitted).
729 * @param includeInterval a flag that determines whether or not the
730 * y-interval is taken into account.
731 *
732 * @return The range (possibly <code>null</code>).
733 */
734 public static Range findRangeBounds(CategoryDataset dataset,
735 boolean includeInterval) {
736 if (dataset == null) {
737 throw new IllegalArgumentException("Null 'dataset' argument.");
738 }
739 Range result = null;
740 if (dataset instanceof RangeInfo) {
741 RangeInfo info = (RangeInfo) dataset;
742 result = info.getRangeBounds(includeInterval);
743 }
744 else {
745 result = iterateRangeBounds(dataset, includeInterval);
746 }
747 return result;
748 }
749
750 /**
751 * Returns the range of values in the range for the dataset. This method
752 * is the partner for the {@link #findDomainBounds(XYDataset)} method.
753 *
754 * @param dataset the dataset (<code>null</code> not permitted).
755 *
756 * @return The range (possibly <code>null</code>).
757 */
758 public static Range findRangeBounds(XYDataset dataset) {
759 return findRangeBounds(dataset, true);
760 }
761
762 /**
763 * Returns the range of values in the range for the dataset. This method
764 * is the partner for the {@link #findDomainBounds(XYDataset, boolean)}
765 * method.
766 *
767 * @param dataset the dataset (<code>null</code> not permitted).
768 * @param includeInterval a flag that determines whether or not the
769 * y-interval is taken into account.
770 *
771 * @return The range (possibly <code>null</code>).
772 */
773 public static Range findRangeBounds(XYDataset dataset,
774 boolean includeInterval) {
775 if (dataset == null) {
776 throw new IllegalArgumentException("Null 'dataset' argument.");
777 }
778 Range result = null;
779 if (dataset instanceof RangeInfo) {
780 RangeInfo info = (RangeInfo) dataset;
781 result = info.getRangeBounds(includeInterval);
782 }
783 else {
784 result = iterateRangeBounds(dataset, includeInterval);
785 }
786 return result;
787 }
788
789 /**
790 * Iterates over the data item of the category dataset to find
791 * the range bounds.
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 * @deprecated As of 1.0.10, use
800 * {@link #iterateRangeBounds(CategoryDataset, boolean)}.
801 */
802 public static Range iterateCategoryRangeBounds(CategoryDataset dataset,
803 boolean includeInterval) {
804 return iterateRangeBounds(dataset, includeInterval);
805 }
806
807 /**
808 * Iterates over the data item of the category dataset to find
809 * the range bounds.
810 *
811 * @param dataset the dataset (<code>null</code> not permitted).
812 *
813 * @return The range (possibly <code>null</code>).
814 *
815 * @since 1.0.10
816 */
817 public static Range iterateRangeBounds(CategoryDataset dataset) {
818 return iterateRangeBounds(dataset, true);
819 }
820
821 /**
822 * Iterates over the data item of the category dataset to find
823 * the range bounds.
824 *
825 * @param dataset the dataset (<code>null</code> not permitted).
826 * @param includeInterval a flag that determines whether or not the
827 * y-interval is taken into account.
828 *
829 * @return The range (possibly <code>null</code>).
830 *
831 * @since 1.0.10
832 */
833 public static Range iterateRangeBounds(CategoryDataset dataset,
834 boolean includeInterval) {
835 double minimum = Double.POSITIVE_INFINITY;
836 double maximum = Double.NEGATIVE_INFINITY;
837 int rowCount = dataset.getRowCount();
838 int columnCount = dataset.getColumnCount();
839 if (includeInterval && dataset instanceof IntervalCategoryDataset) {
840 // handle the special case where the dataset has y-intervals that
841 // we want to measure
842 IntervalCategoryDataset icd = (IntervalCategoryDataset) dataset;
843 Number lvalue, uvalue;
844 for (int row = 0; row < rowCount; row++) {
845 for (int column = 0; column < columnCount; column++) {
846 lvalue = icd.getStartValue(row, column);
847 uvalue = icd.getEndValue(row, column);
848 if (lvalue != null) {
849 minimum = Math.min(minimum, lvalue.doubleValue());
850 }
851 if (uvalue != null) {
852 maximum = Math.max(maximum, uvalue.doubleValue());
853 }
854 }
855 }
856 }
857 else {
858 // handle the standard case (plain CategoryDataset)
859 for (int row = 0; row < rowCount; row++) {
860 for (int column = 0; column < columnCount; column++) {
861 Number value = dataset.getValue(row, column);
862 if (value != null) {
863 double v = value.doubleValue();
864 minimum = Math.min(minimum, v);
865 maximum = Math.max(maximum, v);
866 }
867 }
868 }
869 }
870 if (minimum == Double.POSITIVE_INFINITY) {
871 return null;
872 }
873 else {
874 return new Range(minimum, maximum);
875 }
876 }
877
878 /**
879 * Iterates over the data item of the xy dataset to find
880 * the range bounds.
881 *
882 * @param dataset the dataset (<code>null</code> not permitted).
883 *
884 * @return The range (possibly <code>null</code>).
885 *
886 * @deprecated As of 1.0.10, use {@link #iterateRangeBounds(XYDataset)}.
887 */
888 public static Range iterateXYRangeBounds(XYDataset dataset) {
889 return iterateRangeBounds(dataset);
890 }
891
892 /**
893 * Iterates over the data item of the xy dataset to find
894 * the range bounds.
895 *
896 * @param dataset the dataset (<code>null</code> not permitted).
897 *
898 * @return The range (possibly <code>null</code>).
899 *
900 * @since 1.0.10
901 */
902 public static Range iterateRangeBounds(XYDataset dataset) {
903 return iterateRangeBounds(dataset, true);
904 }
905
906 /**
907 * Iterates over the data items of the xy dataset to find
908 * the range bounds.
909 *
910 * @param dataset the dataset (<code>null</code> not permitted).
911 * @param includeInterval a flag that determines, for an
912 * {@link IntervalXYDataset}, whether the y-interval or just the
913 * y-value is used to determine the overall range.
914 *
915 * @return The range (possibly <code>null</code>).
916 *
917 * @since 1.0.10
918 */
919 public static Range iterateRangeBounds(XYDataset dataset,
920 boolean includeInterval) {
921 double minimum = Double.POSITIVE_INFINITY;
922 double maximum = Double.NEGATIVE_INFINITY;
923 int seriesCount = dataset.getSeriesCount();
924
925 // handle three cases by dataset type
926 if (includeInterval && dataset instanceof IntervalXYDataset) {
927 // handle special case of IntervalXYDataset
928 IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
929 for (int series = 0; series < seriesCount; series++) {
930 int itemCount = dataset.getItemCount(series);
931 for (int item = 0; item < itemCount; item++) {
932 double lvalue = ixyd.getStartYValue(series, item);
933 double uvalue = ixyd.getEndYValue(series, item);
934 if (!Double.isNaN(lvalue)) {
935 minimum = Math.min(minimum, lvalue);
936 }
937 if (!Double.isNaN(uvalue)) {
938 maximum = Math.max(maximum, uvalue);
939 }
940 }
941 }
942 }
943 else if (includeInterval && dataset instanceof OHLCDataset) {
944 // handle special case of OHLCDataset
945 OHLCDataset ohlc = (OHLCDataset) dataset;
946 for (int series = 0; series < seriesCount; series++) {
947 int itemCount = dataset.getItemCount(series);
948 for (int item = 0; item < itemCount; item++) {
949 double lvalue = ohlc.getLowValue(series, item);
950 double uvalue = ohlc.getHighValue(series, item);
951 if (!Double.isNaN(lvalue)) {
952 minimum = Math.min(minimum, lvalue);
953 }
954 if (!Double.isNaN(uvalue)) {
955 maximum = Math.max(maximum, uvalue);
956 }
957 }
958 }
959 }
960 else {
961 // standard case - plain XYDataset
962 for (int series = 0; series < seriesCount; series++) {
963 int itemCount = dataset.getItemCount(series);
964 for (int item = 0; item < itemCount; item++) {
965 double value = dataset.getYValue(series, item);
966 if (!Double.isNaN(value)) {
967 minimum = Math.min(minimum, value);
968 maximum = Math.max(maximum, value);
969 }
970 }
971 }
972 }
973 if (minimum == Double.POSITIVE_INFINITY) {
974 return null;
975 }
976 else {
977 return new Range(minimum, maximum);
978 }
979 }
980
981 /**
982 * Finds the minimum domain (or X) value for the specified dataset. This
983 * is easy if the dataset implements the {@link DomainInfo} interface (a
984 * good idea if there is an efficient way to determine the minimum value).
985 * Otherwise, it involves iterating over the entire data-set.
986 * <p>
987 * Returns <code>null</code> if all the data values in the dataset are
988 * <code>null</code>.
989 *
990 * @param dataset the dataset (<code>null</code> not permitted).
991 *
992 * @return The minimum value (possibly <code>null</code>).
993 */
994 public static Number findMinimumDomainValue(XYDataset dataset) {
995 if (dataset == null) {
996 throw new IllegalArgumentException("Null 'dataset' argument.");
997 }
998 Number result = null;
999 // if the dataset implements DomainInfo, life is easy
1000 if (dataset instanceof DomainInfo) {
1001 DomainInfo info = (DomainInfo) dataset;
1002 return new Double(info.getDomainLowerBound(true));
1003 }
1004 else {
1005 double minimum = Double.POSITIVE_INFINITY;
1006 int seriesCount = dataset.getSeriesCount();
1007 for (int series = 0; series < seriesCount; series++) {
1008 int itemCount = dataset.getItemCount(series);
1009 for (int item = 0; item < itemCount; item++) {
1010
1011 double value;
1012 if (dataset instanceof IntervalXYDataset) {
1013 IntervalXYDataset intervalXYData
1014 = (IntervalXYDataset) dataset;
1015 value = intervalXYData.getStartXValue(series, item);
1016 }
1017 else {
1018 value = dataset.getXValue(series, item);
1019 }
1020 if (!Double.isNaN(value)) {
1021 minimum = Math.min(minimum, value);
1022 }
1023
1024 }
1025 }
1026 if (minimum == Double.POSITIVE_INFINITY) {
1027 result = null;
1028 }
1029 else {
1030 result = new Double(minimum);
1031 }
1032 }
1033
1034 return result;
1035 }
1036
1037 /**
1038 * Returns the maximum domain value for the specified dataset. This is
1039 * easy if the dataset implements the {@link DomainInfo} interface (a good
1040 * idea if there is an efficient way to determine the maximum value).
1041 * Otherwise, it involves iterating over the entire data-set. Returns
1042 * <code>null</code> if all the data values in the dataset are
1043 * <code>null</code>.
1044 *
1045 * @param dataset the dataset (<code>null</code> not permitted).
1046 *
1047 * @return The maximum value (possibly <code>null</code>).
1048 */
1049 public static Number findMaximumDomainValue(XYDataset dataset) {
1050 if (dataset == null) {
1051 throw new IllegalArgumentException("Null 'dataset' argument.");
1052 }
1053 Number result = null;
1054 // if the dataset implements DomainInfo, life is easy
1055 if (dataset instanceof DomainInfo) {
1056 DomainInfo info = (DomainInfo) dataset;
1057 return new Double(info.getDomainUpperBound(true));
1058 }
1059
1060 // hasn't implemented DomainInfo, so iterate...
1061 else {
1062 double maximum = Double.NEGATIVE_INFINITY;
1063 int seriesCount = dataset.getSeriesCount();
1064 for (int series = 0; series < seriesCount; series++) {
1065 int itemCount = dataset.getItemCount(series);
1066 for (int item = 0; item < itemCount; item++) {
1067
1068 double value;
1069 if (dataset instanceof IntervalXYDataset) {
1070 IntervalXYDataset intervalXYData
1071 = (IntervalXYDataset) dataset;
1072 value = intervalXYData.getEndXValue(series, item);
1073 }
1074 else {
1075 value = dataset.getXValue(series, item);
1076 }
1077 if (!Double.isNaN(value)) {
1078 maximum = Math.max(maximum, value);
1079 }
1080 }
1081 }
1082 if (maximum == Double.NEGATIVE_INFINITY) {
1083 result = null;
1084 }
1085 else {
1086 result = new Double(maximum);
1087 }
1088
1089 }
1090
1091 return result;
1092 }
1093
1094 /**
1095 * Returns the minimum range value for the specified dataset. This is
1096 * easy if the dataset implements the {@link RangeInfo} interface (a good
1097 * idea if there is an efficient way to determine the minimum value).
1098 * Otherwise, it involves iterating over the entire data-set. Returns
1099 * <code>null</code> if all the data values in the dataset are
1100 * <code>null</code>.
1101 *
1102 * @param dataset the dataset (<code>null</code> not permitted).
1103 *
1104 * @return The minimum value (possibly <code>null</code>).
1105 */
1106 public static Number findMinimumRangeValue(CategoryDataset dataset) {
1107
1108 // check parameters...
1109 if (dataset == null) {
1110 throw new IllegalArgumentException("Null 'dataset' argument.");
1111 }
1112
1113 // work out the minimum value...
1114 if (dataset instanceof RangeInfo) {
1115 RangeInfo info = (RangeInfo) dataset;
1116 return new Double(info.getRangeLowerBound(true));
1117 }
1118
1119 // hasn't implemented RangeInfo, so we'll have to iterate...
1120 else {
1121 double minimum = Double.POSITIVE_INFINITY;
1122 int seriesCount = dataset.getRowCount();
1123 int itemCount = dataset.getColumnCount();
1124 for (int series = 0; series < seriesCount; series++) {
1125 for (int item = 0; item < itemCount; item++) {
1126 Number value;
1127 if (dataset instanceof IntervalCategoryDataset) {
1128 IntervalCategoryDataset icd
1129 = (IntervalCategoryDataset) dataset;
1130 value = icd.getStartValue(series, item);
1131 }
1132 else {
1133 value = dataset.getValue(series, item);
1134 }
1135 if (value != null) {
1136 minimum = Math.min(minimum, value.doubleValue());
1137 }
1138 }
1139 }
1140 if (minimum == Double.POSITIVE_INFINITY) {
1141 return null;
1142 }
1143 else {
1144 return new Double(minimum);
1145 }
1146
1147 }
1148
1149 }
1150
1151 /**
1152 * Returns the minimum range value for the specified dataset. This is
1153 * easy if the dataset implements the {@link RangeInfo} interface (a good
1154 * idea if there is an efficient way to determine the minimum value).
1155 * Otherwise, it involves iterating over the entire data-set. Returns
1156 * <code>null</code> if all the data values in the dataset are
1157 * <code>null</code>.
1158 *
1159 * @param dataset the dataset (<code>null</code> not permitted).
1160 *
1161 * @return The minimum value (possibly <code>null</code>).
1162 */
1163 public static Number findMinimumRangeValue(XYDataset dataset) {
1164
1165 if (dataset == null) {
1166 throw new IllegalArgumentException("Null 'dataset' argument.");
1167 }
1168
1169 // work out the minimum value...
1170 if (dataset instanceof RangeInfo) {
1171 RangeInfo info = (RangeInfo) dataset;
1172 return new Double(info.getRangeLowerBound(true));
1173 }
1174
1175 // hasn't implemented RangeInfo, so we'll have to iterate...
1176 else {
1177 double minimum = Double.POSITIVE_INFINITY;
1178 int seriesCount = dataset.getSeriesCount();
1179 for (int series = 0; series < seriesCount; series++) {
1180 int itemCount = dataset.getItemCount(series);
1181 for (int item = 0; item < itemCount; item++) {
1182
1183 double value;
1184 if (dataset instanceof IntervalXYDataset) {
1185 IntervalXYDataset intervalXYData
1186 = (IntervalXYDataset) dataset;
1187 value = intervalXYData.getStartYValue(series, item);
1188 }
1189 else if (dataset instanceof OHLCDataset) {
1190 OHLCDataset highLowData = (OHLCDataset) dataset;
1191 value = highLowData.getLowValue(series, item);
1192 }
1193 else {
1194 value = dataset.getYValue(series, item);
1195 }
1196 if (!Double.isNaN(value)) {
1197 minimum = Math.min(minimum, value);
1198 }
1199
1200 }
1201 }
1202 if (minimum == Double.POSITIVE_INFINITY) {
1203 return null;
1204 }
1205 else {
1206 return new Double(minimum);
1207 }
1208
1209 }
1210
1211 }
1212
1213 /**
1214 * Returns the maximum range value for the specified dataset. This is easy
1215 * if the dataset implements the {@link RangeInfo} interface (a good idea
1216 * if there is an efficient way to determine the maximum value).
1217 * Otherwise, it involves iterating over the entire data-set. Returns
1218 * <code>null</code> if all the data values are <code>null</code>.
1219 *
1220 * @param dataset the dataset (<code>null</code> not permitted).
1221 *
1222 * @return The maximum value (possibly <code>null</code>).
1223 */
1224 public static Number findMaximumRangeValue(CategoryDataset dataset) {
1225
1226 if (dataset == null) {
1227 throw new IllegalArgumentException("Null 'dataset' argument.");
1228 }
1229
1230 // work out the minimum value...
1231 if (dataset instanceof RangeInfo) {
1232 RangeInfo info = (RangeInfo) dataset;
1233 return new Double(info.getRangeUpperBound(true));
1234 }
1235
1236 // hasn't implemented RangeInfo, so we'll have to iterate...
1237 else {
1238
1239 double maximum = Double.NEGATIVE_INFINITY;
1240 int seriesCount = dataset.getRowCount();
1241 int itemCount = dataset.getColumnCount();
1242 for (int series = 0; series < seriesCount; series++) {
1243 for (int item = 0; item < itemCount; item++) {
1244 Number value;
1245 if (dataset instanceof IntervalCategoryDataset) {
1246 IntervalCategoryDataset icd
1247 = (IntervalCategoryDataset) dataset;
1248 value = icd.getEndValue(series, item);
1249 }
1250 else {
1251 value = dataset.getValue(series, item);
1252 }
1253 if (value != null) {
1254 maximum = Math.max(maximum, value.doubleValue());
1255 }
1256 }
1257 }
1258 if (maximum == Double.NEGATIVE_INFINITY) {
1259 return null;
1260 }
1261 else {
1262 return new Double(maximum);
1263 }
1264
1265 }
1266
1267 }
1268
1269 /**
1270 * Returns the maximum range value for the specified dataset. This is
1271 * easy if the dataset implements the {@link RangeInfo} interface (a good
1272 * idea if there is an efficient way to determine the maximum value).
1273 * Otherwise, it involves iterating over the entire data-set. Returns
1274 * <code>null</code> if all the data values are <code>null</code>.
1275 *
1276 * @param dataset the dataset (<code>null</code> not permitted).
1277 *
1278 * @return The maximum value (possibly <code>null</code>).
1279 */
1280 public static Number findMaximumRangeValue(XYDataset dataset) {
1281
1282 if (dataset == null) {
1283 throw new IllegalArgumentException("Null 'dataset' argument.");
1284 }
1285
1286 // work out the minimum value...
1287 if (dataset instanceof RangeInfo) {
1288 RangeInfo info = (RangeInfo) dataset;
1289 return new Double(info.getRangeUpperBound(true));
1290 }
1291
1292 // hasn't implemented RangeInfo, so we'll have to iterate...
1293 else {
1294
1295 double maximum = Double.NEGATIVE_INFINITY;
1296 int seriesCount = dataset.getSeriesCount();
1297 for (int series = 0; series < seriesCount; series++) {
1298 int itemCount = dataset.getItemCount(series);
1299 for (int item = 0; item < itemCount; item++) {
1300 double value;
1301 if (dataset instanceof IntervalXYDataset) {
1302 IntervalXYDataset intervalXYData
1303 = (IntervalXYDataset) dataset;
1304 value = intervalXYData.getEndYValue(series, item);
1305 }
1306 else if (dataset instanceof OHLCDataset) {
1307 OHLCDataset highLowData = (OHLCDataset) dataset;
1308 value = highLowData.getHighValue(series, item);
1309 }
1310 else {
1311 value = dataset.getYValue(series, item);
1312 }
1313 if (!Double.isNaN(value)) {
1314 maximum = Math.max(maximum, value);
1315 }
1316 }
1317 }
1318 if (maximum == Double.NEGATIVE_INFINITY) {
1319 return null;
1320 }
1321 else {
1322 return new Double(maximum);
1323 }
1324
1325 }
1326
1327 }
1328
1329 /**
1330 * Returns the minimum and maximum values for the dataset's range
1331 * (y-values), assuming that the series in one category are stacked.
1332 *
1333 * @param dataset the dataset (<code>null</code> not permitted).
1334 *
1335 * @return The range (<code>null</code> if the dataset contains no values).
1336 */
1337 public static Range findStackedRangeBounds(CategoryDataset dataset) {
1338 return findStackedRangeBounds(dataset, 0.0);
1339 }
1340
1341 /**
1342 * Returns the minimum and maximum values for the dataset's range
1343 * (y-values), assuming that the series in one category are stacked.
1344 *
1345 * @param dataset the dataset (<code>null</code> not permitted).
1346 * @param base the base value for the bars.
1347 *
1348 * @return The range (<code>null</code> if the dataset contains no values).
1349 */
1350 public static Range findStackedRangeBounds(CategoryDataset dataset,
1351 double base) {
1352 if (dataset == null) {
1353 throw new IllegalArgumentException("Null 'dataset' argument.");
1354 }
1355 Range result = null;
1356 double minimum = Double.POSITIVE_INFINITY;
1357 double maximum = Double.NEGATIVE_INFINITY;
1358 int categoryCount = dataset.getColumnCount();
1359 for (int item = 0; item < categoryCount; item++) {
1360 double positive = base;
1361 double negative = base;
1362 int seriesCount = dataset.getRowCount();
1363 for (int series = 0; series < seriesCount; series++) {
1364 Number number = dataset.getValue(series, item);
1365 if (number != null) {
1366 double value = number.doubleValue();
1367 if (value > 0.0) {
1368 positive = positive + value;
1369 }
1370 if (value < 0.0) {
1371 negative = negative + value;
1372 // '+', remember value is negative
1373 }
1374 }
1375 }
1376 minimum = Math.min(minimum, negative);
1377 maximum = Math.max(maximum, positive);
1378 }
1379 if (minimum <= maximum) {
1380 result = new Range(minimum, maximum);
1381 }
1382 return result;
1383
1384 }
1385
1386 /**
1387 * Returns the minimum and maximum values for the dataset's range
1388 * (y-values), assuming that the series in one category are stacked.
1389 *
1390 * @param dataset the dataset.
1391 * @param map a structure that maps series to groups.
1392 *
1393 * @return The value range (<code>null</code> if the dataset contains no
1394 * values).
1395 */
1396 public static Range findStackedRangeBounds(CategoryDataset dataset,
1397 KeyToGroupMap map) {
1398
1399 Range result = null;
1400 if (dataset != null) {
1401
1402 // create an array holding the group indices...
1403 int[] groupIndex = new int[dataset.getRowCount()];
1404 for (int i = 0; i < dataset.getRowCount(); i++) {
1405 groupIndex[i] = map.getGroupIndex(
1406 map.getGroup(dataset.getRowKey(i))
1407 );
1408 }
1409
1410 // minimum and maximum for each group...
1411 int groupCount = map.getGroupCount();
1412 double[] minimum = new double[groupCount];
1413 double[] maximum = new double[groupCount];
1414
1415 int categoryCount = dataset.getColumnCount();
1416 for (int item = 0; item < categoryCount; item++) {
1417 double[] positive = new double[groupCount];
1418 double[] negative = new double[groupCount];
1419 int seriesCount = dataset.getRowCount();
1420 for (int series = 0; series < seriesCount; series++) {
1421 Number number = dataset.getValue(series, item);
1422 if (number != null) {
1423 double value = number.doubleValue();
1424 if (value > 0.0) {
1425 positive[groupIndex[series]]
1426 = positive[groupIndex[series]] + value;
1427 }
1428 if (value < 0.0) {
1429 negative[groupIndex[series]]
1430 = negative[groupIndex[series]] + value;
1431 // '+', remember value is negative
1432 }
1433 }
1434 }
1435 for (int g = 0; g < groupCount; g++) {
1436 minimum[g] = Math.min(minimum[g], negative[g]);
1437 maximum[g] = Math.max(maximum[g], positive[g]);
1438 }
1439 }
1440 for (int j = 0; j < groupCount; j++) {
1441 result = Range.combine(
1442 result, new Range(minimum[j], maximum[j])
1443 );
1444 }
1445 }
1446 return result;
1447
1448 }
1449
1450 /**
1451 * Returns the minimum value in the dataset range, assuming that values in
1452 * each category are "stacked".
1453 *
1454 * @param dataset the dataset.
1455 *
1456 * @return The minimum value.
1457 */
1458 public static Number findMinimumStackedRangeValue(CategoryDataset dataset) {
1459
1460 Number result = null;
1461 if (dataset != null) {
1462 double minimum = 0.0;
1463 int categoryCount = dataset.getRowCount();
1464 for (int item = 0; item < categoryCount; item++) {
1465 double total = 0.0;
1466
1467 int seriesCount = dataset.getColumnCount();
1468 for (int series = 0; series < seriesCount; series++) {
1469 Number number = dataset.getValue(series, item);
1470 if (number != null) {
1471 double value = number.doubleValue();
1472 if (value < 0.0) {
1473 total = total + value;
1474 // '+', remember value is negative
1475 }
1476 }
1477 }
1478 minimum = Math.min(minimum, total);
1479
1480 }
1481 result = new Double(minimum);
1482 }
1483 return result;
1484
1485 }
1486
1487 /**
1488 * Returns the maximum value in the dataset range, assuming that values in
1489 * each category are "stacked".
1490 *
1491 * @param dataset the dataset (<code>null</code> permitted).
1492 *
1493 * @return The maximum value (possibly <code>null</code>).
1494 */
1495 public static Number findMaximumStackedRangeValue(CategoryDataset dataset) {
1496
1497 Number result = null;
1498
1499 if (dataset != null) {
1500 double maximum = 0.0;
1501 int categoryCount = dataset.getColumnCount();
1502 for (int item = 0; item < categoryCount; item++) {
1503 double total = 0.0;
1504 int seriesCount = dataset.getRowCount();
1505 for (int series = 0; series < seriesCount; series++) {
1506 Number number = dataset.getValue(series, item);
1507 if (number != null) {
1508 double value = number.doubleValue();
1509 if (value > 0.0) {
1510 total = total + value;
1511 }
1512 }
1513 }
1514 maximum = Math.max(maximum, total);
1515 }
1516 result = new Double(maximum);
1517 }
1518
1519 return result;
1520
1521 }
1522
1523 /**
1524 * Returns the minimum and maximum values for the dataset's range,
1525 * assuming that the series are stacked.
1526 *
1527 * @param dataset the dataset (<code>null</code> not permitted).
1528 *
1529 * @return The range ([0.0, 0.0] if the dataset contains no values).
1530 */
1531 public static Range findStackedRangeBounds(TableXYDataset dataset) {
1532 return findStackedRangeBounds(dataset, 0.0);
1533 }
1534
1535 /**
1536 * Returns the minimum and maximum values for the dataset's range,
1537 * assuming that the series are stacked, using the specified base value.
1538 *
1539 * @param dataset the dataset (<code>null</code> not permitted).
1540 * @param base the base value.
1541 *
1542 * @return The range (<code>null</code> if the dataset contains no values).
1543 */
1544 public static Range findStackedRangeBounds(TableXYDataset dataset,
1545 double base) {
1546 if (dataset == null) {
1547 throw new IllegalArgumentException("Null 'dataset' argument.");
1548 }
1549 double minimum = base;
1550 double maximum = base;
1551 for (int itemNo = 0; itemNo < dataset.getItemCount(); itemNo++) {
1552 double positive = base;
1553 double negative = base;
1554 int seriesCount = dataset.getSeriesCount();
1555 for (int seriesNo = 0; seriesNo < seriesCount; seriesNo++) {
1556 double y = dataset.getYValue(seriesNo, itemNo);
1557 if (!Double.isNaN(y)) {
1558 if (y > 0.0) {
1559 positive += y;
1560 }
1561 else {
1562 negative += y;
1563 }
1564 }
1565 }
1566 if (positive > maximum) {
1567 maximum = positive;
1568 }
1569 if (negative < minimum) {
1570 minimum = negative;
1571 }
1572 }
1573 if (minimum <= maximum) {
1574 return new Range(minimum, maximum);
1575 }
1576 else {
1577 return null;
1578 }
1579 }
1580
1581 /**
1582 * Calculates the total for the y-values in all series for a given item
1583 * index.
1584 *
1585 * @param dataset the dataset.
1586 * @param item the item index.
1587 *
1588 * @return The total.
1589 *
1590 * @since 1.0.5
1591 */
1592 public static double calculateStackTotal(TableXYDataset dataset, int item) {
1593 double total = 0.0;
1594 int seriesCount = dataset.getSeriesCount();
1595 for (int s = 0; s < seriesCount; s++) {
1596 double value = dataset.getYValue(s, item);
1597 if (!Double.isNaN(value)) {
1598 total = total + value;
1599 }
1600 }
1601 return total;
1602 }
1603
1604 /**
1605 * Calculates the range of values for a dataset where each item is the
1606 * running total of the items for the current series.
1607 *
1608 * @param dataset the dataset (<code>null</code> not permitted).
1609 *
1610 * @return The range.
1611 *
1612 * @see #findRangeBounds(CategoryDataset)
1613 */
1614 public static Range findCumulativeRangeBounds(CategoryDataset dataset) {
1615
1616 if (dataset == null) {
1617 throw new IllegalArgumentException("Null 'dataset' argument.");
1618 }
1619
1620 boolean allItemsNull = true; // we'll set this to false if there is at
1621 // least one non-null data item...
1622 double minimum = 0.0;
1623 double maximum = 0.0;
1624 for (int row = 0; row < dataset.getRowCount(); row++) {
1625 double runningTotal = 0.0;
1626 for (int column = 0; column <= dataset.getColumnCount() - 1;
1627 column++) {
1628 Number n = dataset.getValue(row, column);
1629 if (n != null) {
1630 allItemsNull = false;
1631 double value = n.doubleValue();
1632 runningTotal = runningTotal + value;
1633 minimum = Math.min(minimum, runningTotal);
1634 maximum = Math.max(maximum, runningTotal);
1635 }
1636 }
1637 }
1638 if (!allItemsNull) {
1639 return new Range(minimum, maximum);
1640 }
1641 else {
1642 return null;
1643 }
1644
1645 }
1646
1647 }