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