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