Source for org.jfree.data.general.DatasetUtilities

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ---------------------
  28:  * DatasetUtilities.java
  29:  * ---------------------
  30:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Andrzej Porebski (bug fix);
  34:  *                   Jonathan Nash (bug fix);
  35:  *                   Richard Atkinson;
  36:  *                   Andreas Schroeder (beatification)
  37:  *
  38:  * $Id: DatasetUtilities.java,v 1.18.2.4 2006/08/02 15:27:14 mungady Exp $
  39:  *
  40:  * Changes (from 18-Sep-2001)
  41:  * --------------------------
  42:  * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
  43:  * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
  44:  * 15-Nov-2001 : Moved to package com.jrefinery.data.* in the JCommon class 
  45:  *               library (DG);
  46:  *               Changed to handle null values from datasets (DG);
  47:  *               Bug fix (thanks to Andrzej Porebski) - initial value now set 
  48:  *               to positive or negative infinity when iterating (DG);
  49:  * 22-Nov-2001 : Datasets with containing no data now return null for min and 
  50:  *               max calculations (DG);
  51:  * 13-Dec-2001 : Extended to handle HighLowDataset and IntervalXYDataset (DG);
  52:  * 15-Feb-2002 : Added getMinimumStackedRangeValue() and 
  53:  *               getMaximumStackedRangeValue() (DG);
  54:  * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
  55:  * 18-Mar-2002 : Fixed bug in min/max domain calculation for datasets that 
  56:  *               implement the CategoryDataset interface AND the XYDataset 
  57:  *               interface at the same time.  Thanks to Jonathan Nash for the 
  58:  *               fix (DG);
  59:  * 23-Apr-2002 : Added getDomainExtent() and getRangeExtent() methods (DG);
  60:  * 13-Jun-2002 : Modified range measurements to handle 
  61:  *               IntervalCategoryDataset (DG);
  62:  * 12-Jul-2002 : Method name change in DomainInfo interface (DG);
  63:  * 30-Jul-2002 : Added pie dataset summation method (DG);
  64:  * 01-Oct-2002 : Added a method for constructing an XYDataset from a Function2D
  65:  *               instance (DG);
  66:  * 24-Oct-2002 : Amendments required following changes to the CategoryDataset 
  67:  *               interface (DG);
  68:  * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
  69:  * 04-Mar-2003 : Added isEmpty(XYDataset) method (DG);
  70:  * 05-Mar-2003 : Added a method for creating a CategoryDataset from a 
  71:  *               KeyedValues instance (DG);
  72:  * 15-May-2003 : Renamed isEmpty --> isEmptyOrNull (DG);
  73:  * 25-Jun-2003 : Added limitPieDataset methods (RA);
  74:  * 26-Jun-2003 : Modified getDomainExtent() method to accept null datasets (DG);
  75:  * 27-Jul-2003 : Added getStackedRangeExtent(TableXYDataset data) (RA);
  76:  * 18-Aug-2003 : getStackedRangeExtent(TableXYDataset data) now handles null 
  77:  *               values (RA);
  78:  * 02-Sep-2003 : Added method to check for null or empty PieDataset (DG);
  79:  * 18-Sep-2003 : Fix for bug 803660 (getMaximumRangeValue for 
  80:  *               CategoryDataset) (DG);
  81:  * 20-Oct-2003 : Added getCumulativeRangeExtent() method (DG);
  82:  * 09-Jan-2003 : Added argument checking code to the createCategoryDataset() 
  83:  *               method (DG);
  84:  * 23-Mar-2004 : Fixed bug in getMaximumStackedRangeValue() method (DG);
  85:  * 31-Mar-2004 : Exposed the extent iteration algorithms to use one of them and 
  86:  *               applied noninstantiation pattern (AS);
  87:  * 11-May-2004 : Renamed getPieDatasetTotal --> calculatePieDatasetTotal (DG);
  88:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue();
  89:  * 24-Aug-2004 : Added argument checks to createCategoryDataset() method (DG);
  90:  * 04-Oct-2004 : Renamed ArrayUtils --> ArrayUtilities (DG);
  91:  * 06-Oct-2004 : Renamed findDomainExtent() --> findDomainBounds(),
  92:  *               findRangeExtent() --> findRangeBounds() (DG);
  93:  * 07-Jan-2005 : Renamed findStackedRangeExtent() --> findStackedRangeBounds(),
  94:  *               findCumulativeRangeExtent() --> findCumulativeRangeBounds(),
  95:  *               iterateXYRangeExtent() --> iterateXYRangeBounds(), 
  96:  *               removed deprecated methods (DG);
  97:  * 03-Feb-2005 : The findStackedRangeBounds() methods now return null for 
  98:  *               empty datasets (DG);
  99:  * 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:  * 
 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.OHLCDataset;
 122: import org.jfree.data.xy.IntervalXYDataset;
 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: 
 594:         boolean result = true;
 595: 
 596:         if (dataset != null) {
 597:             for (int s = 0; s < dataset.getSeriesCount(); s++) {
 598:                 if (dataset.getItemCount(s) > 0) {
 599:                     result = false;
 600:                     continue;
 601:                 }
 602:             }
 603:         }
 604: 
 605:         return result;
 606: 
 607:     }
 608: 
 609:     /**
 610:      * Returns the range of values in the domain (x-values) of a dataset.
 611:      *
 612:      * @param dataset  the dataset (<code>null</code> not permitted).
 613:      *
 614:      * @return The range of values (possibly <code>null</code>).
 615:      */
 616:     public static Range findDomainBounds(XYDataset dataset) {
 617:         return findDomainBounds(dataset, true);
 618:     }
 619: 
 620:     /**
 621:      * Returns the range of values in the domain (x-values) of a dataset.
 622:      *
 623:      * @param dataset  the dataset (<code>null</code> not permitted).
 624:      * @param includeInterval  determines whether or not the x-interval is taken
 625:      *                         into account (only applies if the dataset is an
 626:      *                         {@link IntervalXYDataset}).
 627:      *
 628:      * @return The range of values (possibly <code>null</code>).
 629:      */
 630:     public static Range findDomainBounds(XYDataset dataset, 
 631:                                          boolean includeInterval) {
 632: 
 633:         if (dataset == null) {
 634:             throw new IllegalArgumentException("Null 'dataset' argument.");
 635:         }
 636: 
 637:         Range result = null;
 638:         // if the dataset implements DomainInfo, life is easier
 639:         if (dataset instanceof DomainInfo) {
 640:             DomainInfo info = (DomainInfo) dataset;
 641:             result = info.getDomainBounds(includeInterval);
 642:         }
 643:         else {
 644:             result = iterateDomainBounds(dataset, includeInterval);
 645:         }
 646:         return result;
 647:         
 648:     }
 649: 
 650:     /**
 651:      * Iterates over the items in an {@link XYDataset} to find
 652:      * the range of x-values. 
 653:      *  
 654:      * @param dataset  the dataset (<code>null</code> not permitted).
 655:      * 
 656:      * @return The range (possibly <code>null</code>).
 657:      */
 658:     public static Range iterateDomainBounds(XYDataset dataset) {
 659:         return iterateDomainBounds(dataset, true);
 660:     }
 661: 
 662:     /**
 663:      * Iterates over the items in an {@link XYDataset} to find
 664:      * the range of x-values. 
 665:      *  
 666:      * @param dataset  the dataset (<code>null</code> not permitted).
 667:      * @param includeInterval  a flag that determines, for an IntervalXYDataset,
 668:      *                         whether the x-interval or just the x-value is 
 669:      *                         used to determine the overall range.
 670:      *   
 671:      * @return The range (possibly <code>null</code>).
 672:      */
 673:     public static Range iterateDomainBounds(XYDataset dataset, 
 674:                                             boolean includeInterval) {
 675:         if (dataset == null) {
 676:             throw new IllegalArgumentException("Null 'dataset' argument.");   
 677:         }
 678:         double minimum = Double.POSITIVE_INFINITY;
 679:         double maximum = Double.NEGATIVE_INFINITY;
 680:         int seriesCount = dataset.getSeriesCount();
 681:         double lvalue;
 682:         double uvalue;
 683:         if (includeInterval && dataset instanceof IntervalXYDataset) {
 684:             IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
 685:             for (int series = 0; series < seriesCount; series++) {
 686:                 int itemCount = dataset.getItemCount(series);
 687:                 for (int item = 0; item < itemCount; item++) {
 688:                     lvalue = intervalXYData.getStartXValue(series, item);
 689:                     uvalue = intervalXYData.getEndXValue(series, item);
 690:                     minimum = Math.min(minimum, lvalue);
 691:                     maximum = Math.max(maximum, uvalue);
 692:                 }
 693:             }
 694:         }
 695:         else {
 696:             for (int series = 0; series < seriesCount; series++) {
 697:                 int itemCount = dataset.getItemCount(series);
 698:                 for (int item = 0; item < itemCount; item++) {
 699:                     lvalue = dataset.getXValue(series, item);
 700:                     uvalue = lvalue;
 701:                     minimum = Math.min(minimum, lvalue);
 702:                     maximum = Math.max(maximum, uvalue);
 703:                 }
 704:             }
 705:         }
 706:         if (minimum > maximum) {
 707:             return null;
 708:         }
 709:         else {
 710:             return new Range(minimum, maximum);
 711:         }
 712:     }
 713:     
 714:     /**
 715:      * Returns the range of values in the range for the dataset.  This method
 716:      * is the partner for the getDomainExtent method.
 717:      *
 718:      * @param dataset  the dataset (<code>null</code> not permitted).
 719:      *
 720:      * @return The range (possibly <code>null</code>).
 721:      */
 722:     public static Range findRangeBounds(CategoryDataset dataset) {
 723:         return findRangeBounds(dataset, true);
 724:     }
 725:     
 726:     /**
 727:      * Returns the range of values in the range for the dataset.  This method
 728:      * is the partner for the getDomainExtent method.
 729:      *
 730:      * @param dataset  the dataset (<code>null</code> not permitted).
 731:      * @param includeInterval  a flag that determines whether or not the
 732:      *                         y-interval is taken into account.
 733:      * 
 734:      * @return The range (possibly <code>null</code>).
 735:      */
 736:     public static Range findRangeBounds(CategoryDataset dataset, 
 737:                                         boolean includeInterval) {
 738:         if (dataset == null) {
 739:             throw new IllegalArgumentException("Null 'dataset' argument.");
 740:         }
 741:         Range result = null;
 742:         if (dataset instanceof RangeInfo) {
 743:             RangeInfo info = (RangeInfo) dataset;
 744:             result = info.getRangeBounds(includeInterval);
 745:         }
 746:         else {
 747:             result = iterateCategoryRangeBounds(dataset, includeInterval);
 748:         }
 749:         return result;
 750:     }
 751:     
 752:     /**
 753:      * Returns the range of values in the range for the dataset.  This method
 754:      * is the partner for the {@link #findDomainBounds(XYDataset)} method.
 755:      *
 756:      * @param dataset  the dataset (<code>null</code> not permitted).
 757:      *
 758:      * @return The range (possibly <code>null</code>).
 759:      */
 760:     public static Range findRangeBounds(XYDataset dataset) {
 761:         return findRangeBounds(dataset, true);
 762:     }
 763:     
 764:     /**
 765:      * Returns the range of values in the range for the dataset.  This method
 766:      * is the partner for the {@link #findDomainBounds(XYDataset)} method.
 767:      *
 768:      * @param dataset  the dataset (<code>null</code> not permitted).
 769:      * @param includeInterval  a flag that determines whether or not the
 770:      *                         y-interval is taken into account.
 771:      * 
 772:      *
 773:      * @return The range (possibly <code>null</code>).
 774:      */
 775:     public static Range findRangeBounds(XYDataset dataset, 
 776:                                         boolean includeInterval) {
 777:         if (dataset == null) {
 778:             throw new IllegalArgumentException("Null 'dataset' argument.");
 779:         }
 780:         Range result = null;
 781:         if (dataset instanceof RangeInfo) {
 782:             RangeInfo info = (RangeInfo) dataset;
 783:             result = info.getRangeBounds(includeInterval);
 784:         }
 785:         else {
 786:             result = iterateXYRangeBounds(dataset);
 787:         }
 788:         return result;
 789:     }
 790:     
 791:     /**
 792:      * Iterates over the data item of the category dataset to find
 793:      * the range bounds.
 794:      * 
 795:      * @param dataset  the dataset (<code>null</code> not permitted).
 796:      * @param includeInterval  a flag that determines whether or not the
 797:      *                         y-interval is taken into account.
 798:      * 
 799:      * @return The range (possibly <code>null</code>).
 800:      */
 801:     public static Range iterateCategoryRangeBounds(CategoryDataset dataset, 
 802:             boolean includeInterval) {
 803:         double minimum = Double.POSITIVE_INFINITY;
 804:         double maximum = Double.NEGATIVE_INFINITY;
 805:         boolean interval = includeInterval 
 806:                            && dataset instanceof IntervalCategoryDataset;
 807:         int rowCount = dataset.getRowCount();
 808:         int columnCount = dataset.getColumnCount();
 809:         for (int row = 0; row < rowCount; row++) {
 810:             for (int column = 0; column < columnCount; column++) {
 811:                 Number lvalue;
 812:                 Number uvalue;
 813:                 if (interval) {
 814:                     IntervalCategoryDataset icd 
 815:                         = (IntervalCategoryDataset) dataset;
 816:                     lvalue = icd.getStartValue(row, column);
 817:                     uvalue = icd.getEndValue(row, column);
 818:                 }
 819:                 else {
 820:                     lvalue = dataset.getValue(row, column);
 821:                     uvalue = lvalue;
 822:                 }
 823:                 if (lvalue != null) {
 824:                     minimum = Math.min(minimum, lvalue.doubleValue());
 825:                 }
 826:                 if (uvalue != null) {
 827:                     maximum = Math.max(maximum, uvalue.doubleValue());
 828:                 }
 829:             }
 830:         }
 831:         if (minimum == Double.POSITIVE_INFINITY) {
 832:             return null;
 833:         }
 834:         else {
 835:             return new Range(minimum, maximum);
 836:         }
 837:     }
 838:     
 839:     /**
 840:      * Iterates over the data item of the xy dataset to find
 841:      * the range bounds.
 842:      * 
 843:      * @param dataset  the dataset (<code>null</code> not permitted).
 844:      * 
 845:      * @return The range (possibly <code>null</code>).
 846:      */
 847:     public static Range iterateXYRangeBounds(XYDataset dataset) {
 848:         double minimum = Double.POSITIVE_INFINITY;
 849:         double maximum = Double.NEGATIVE_INFINITY;
 850:         int seriesCount = dataset.getSeriesCount();
 851:         for (int series = 0; series < seriesCount; series++) {
 852:             int itemCount = dataset.getItemCount(series);
 853:             for (int item = 0; item < itemCount; item++) {
 854:                 double lvalue;
 855:                 double uvalue;
 856:                 if (dataset instanceof IntervalXYDataset) {
 857:                     IntervalXYDataset intervalXYData 
 858:                         = (IntervalXYDataset) dataset;
 859:                     lvalue = intervalXYData.getStartYValue(series, item);
 860:                     uvalue = intervalXYData.getEndYValue(series, item);
 861:                 }
 862:                 else if (dataset instanceof OHLCDataset) {
 863:                     OHLCDataset highLowData = (OHLCDataset) dataset;
 864:                     lvalue = highLowData.getLowValue(series, item);
 865:                     uvalue = highLowData.getHighValue(series, item);
 866:                 }
 867:                 else {
 868:                     lvalue = dataset.getYValue(series, item);
 869:                     uvalue = lvalue;
 870:                 }
 871:                 if (!Double.isNaN(lvalue)) {
 872:                     minimum = Math.min(minimum, lvalue);
 873:                 }
 874:                 if (!Double.isNaN(uvalue)) {     
 875:                     maximum = Math.max(maximum, uvalue);
 876:                 }
 877:             }
 878:         }
 879:         if (minimum == Double.POSITIVE_INFINITY) {
 880:             return null;
 881:         }
 882:         else {
 883:             return new Range(minimum, maximum);
 884:         }
 885:     }
 886: 
 887:     /**
 888:      * Finds the minimum domain (or X) value for the specified dataset.  This 
 889:      * is easy if the dataset implements the {@link DomainInfo} interface (a 
 890:      * good idea if there is an efficient way to determine the minimum value).
 891:      * Otherwise, it involves iterating over the entire data-set.
 892:      * <p>
 893:      * Returns <code>null</code> if all the data values in the dataset are 
 894:      * <code>null</code>.
 895:      *
 896:      * @param dataset  the dataset (<code>null</code> not permitted).
 897:      *
 898:      * @return The minimum value (possibly <code>null</code>).
 899:      */
 900:     public static Number findMinimumDomainValue(XYDataset dataset) {
 901:         if (dataset == null) {
 902:             throw new IllegalArgumentException("Null 'dataset' argument.");
 903:         }
 904:         Number result = null;
 905:         // if the dataset implements DomainInfo, life is easy
 906:         if (dataset instanceof DomainInfo) {
 907:             DomainInfo info = (DomainInfo) dataset;
 908:             return new Double(info.getDomainLowerBound(true));
 909:         }
 910:         else {
 911:             double minimum = Double.POSITIVE_INFINITY;
 912:             int seriesCount = dataset.getSeriesCount();
 913:             for (int series = 0; series < seriesCount; series++) {
 914:                 int itemCount = dataset.getItemCount(series);
 915:                 for (int item = 0; item < itemCount; item++) {
 916: 
 917:                     double value;
 918:                     if (dataset instanceof IntervalXYDataset) {
 919:                         IntervalXYDataset intervalXYData 
 920:                             = (IntervalXYDataset) dataset;
 921:                         value = intervalXYData.getStartXValue(series, item);
 922:                     }
 923:                     else {
 924:                         value = dataset.getXValue(series, item);
 925:                     }
 926:                     if (!Double.isNaN(value)) {
 927:                         minimum = Math.min(minimum, value);
 928:                     }
 929: 
 930:                 }
 931:             }
 932:             if (minimum == Double.POSITIVE_INFINITY) {
 933:                 result = null;
 934:             }
 935:             else {
 936:                 result = new Double(minimum);
 937:             }
 938:         }
 939: 
 940:         return result;
 941:     }
 942:     
 943:     /**
 944:      * Returns the maximum domain value for the specified dataset.  This is 
 945:      * easy if the dataset implements the {@link DomainInfo} interface (a good 
 946:      * idea if there is an efficient way to determine the maximum value).  
 947:      * Otherwise, it involves iterating over the entire data-set.  Returns 
 948:      * <code>null</code> if all the data values in the dataset are 
 949:      * <code>null</code>.
 950:      *
 951:      * @param dataset  the dataset (<code>null</code> not permitted).
 952:      *
 953:      * @return The maximum value (possibly <code>null</code>).
 954:      */
 955:     public static Number findMaximumDomainValue(XYDataset dataset) {
 956:         if (dataset == null) {
 957:             throw new IllegalArgumentException("Null 'dataset' argument.");
 958:         }
 959:         Number result = null;
 960:         // if the dataset implements DomainInfo, life is easy
 961:         if (dataset instanceof DomainInfo) {
 962:             DomainInfo info = (DomainInfo) dataset;
 963:             return new Double(info.getDomainUpperBound(true));
 964:         }
 965: 
 966:         // hasn't implemented DomainInfo, so iterate...
 967:         else {
 968:             double maximum = Double.NEGATIVE_INFINITY;
 969:             int seriesCount = dataset.getSeriesCount();
 970:             for (int series = 0; series < seriesCount; series++) {
 971:                 int itemCount = dataset.getItemCount(series);
 972:                 for (int item = 0; item < itemCount; item++) {
 973: 
 974:                     double value;
 975:                     if (dataset instanceof IntervalXYDataset) {
 976:                         IntervalXYDataset intervalXYData 
 977:                             = (IntervalXYDataset) dataset;
 978:                         value = intervalXYData.getEndXValue(series, item);
 979:                     }
 980:                     else {
 981:                         value = dataset.getXValue(series, item);
 982:                     }
 983:                     if (!Double.isNaN(value)) {
 984:                         maximum = Math.max(maximum, value);
 985:                     }
 986:                 }
 987:             }
 988:             if (maximum == Double.NEGATIVE_INFINITY) {
 989:                 result = null;
 990:             }
 991:             else {
 992:                 result = new Double(maximum);
 993:             }
 994: 
 995:         }
 996:         
 997:         return result;
 998:     }
 999: 
1000:     /**
1001:      * Returns the minimum range value for the specified dataset.  This is 
1002:      * easy if the dataset implements the {@link RangeInfo} interface (a good
1003:      * idea if there is an efficient way to determine the minimum value).  
1004:      * Otherwise, it involves iterating over the entire data-set.  Returns 
1005:      * <code>null</code> if all the data values in the dataset are 
1006:      * <code>null</code>.
1007:      *
1008:      * @param dataset  the dataset (<code>null</code> not permitted).
1009:      *
1010:      * @return The minimum value (possibly <code>null</code>).
1011:      */
1012:     public static Number findMinimumRangeValue(CategoryDataset dataset) {
1013: 
1014:         // check parameters...
1015:         if (dataset == null) {
1016:             throw new IllegalArgumentException("Null 'dataset' argument.");
1017:         }
1018: 
1019:         // work out the minimum value...
1020:         if (dataset instanceof RangeInfo) {
1021:             RangeInfo info = (RangeInfo) dataset;
1022:             return new Double(info.getRangeLowerBound(true));
1023:         }
1024: 
1025:         // hasn't implemented RangeInfo, so we'll have to iterate...
1026:         else {
1027:             double minimum = Double.POSITIVE_INFINITY;
1028:             int seriesCount = dataset.getRowCount();
1029:             int itemCount = dataset.getColumnCount();
1030:             for (int series = 0; series < seriesCount; series++) {
1031:                 for (int item = 0; item < itemCount; item++) {
1032:                     Number value;
1033:                     if (dataset instanceof IntervalCategoryDataset) {
1034:                         IntervalCategoryDataset icd 
1035:                             = (IntervalCategoryDataset) dataset;
1036:                         value = icd.getStartValue(series, item);
1037:                     }
1038:                     else {
1039:                         value = dataset.getValue(series, item);
1040:                     }
1041:                     if (value != null) {
1042:                         minimum = Math.min(minimum, value.doubleValue());
1043:                     }
1044:                 }
1045:             }
1046:             if (minimum == Double.POSITIVE_INFINITY) {
1047:                 return null;
1048:             }
1049:             else {
1050:                 return new Double(minimum);
1051:             }
1052: 
1053:         }
1054: 
1055:     }
1056: 
1057:     /**
1058:      * Returns the minimum range value for the specified dataset.  This is 
1059:      * easy if the dataset implements the {@link RangeInfo} interface (a good
1060:      * idea if there is an efficient way to determine the minimum value).  
1061:      * Otherwise, it involves iterating over the entire data-set.  Returns 
1062:      * <code>null</code> if all the data values in the dataset are 
1063:      * <code>null</code>.
1064:      *
1065:      * @param dataset  the dataset (<code>null</code> not permitted).
1066:      *
1067:      * @return The minimum value (possibly <code>null</code>).
1068:      */
1069:     public static Number findMinimumRangeValue(XYDataset dataset) {
1070: 
1071:         if (dataset == null) {
1072:             throw new IllegalArgumentException("Null 'dataset' argument.");
1073:         }
1074: 
1075:         // work out the minimum value...
1076:         if (dataset instanceof RangeInfo) {
1077:             RangeInfo info = (RangeInfo) dataset;
1078:             return new Double(info.getRangeLowerBound(true));
1079:         }
1080: 
1081:         // hasn't implemented RangeInfo, so we'll have to iterate...
1082:         else {
1083:             double minimum = Double.POSITIVE_INFINITY;
1084:             int seriesCount = dataset.getSeriesCount();
1085:             for (int series = 0; series < seriesCount; series++) {
1086:                 int itemCount = dataset.getItemCount(series);
1087:                 for (int item = 0; item < itemCount; item++) {
1088: 
1089:                     double value;
1090:                     if (dataset instanceof IntervalXYDataset) {
1091:                         IntervalXYDataset intervalXYData 
1092:                             = (IntervalXYDataset) dataset;
1093:                         value = intervalXYData.getStartYValue(series, item);
1094:                     }
1095:                     else if (dataset instanceof OHLCDataset) {
1096:                         OHLCDataset highLowData = (OHLCDataset) dataset;
1097:                         value = highLowData.getLowValue(series, item);
1098:                     }
1099:                     else {
1100:                         value = dataset.getYValue(series, item);
1101:                     }
1102:                     if (!Double.isNaN(value)) {
1103:                         minimum = Math.min(minimum, value);
1104:                     }
1105: 
1106:                 }
1107:             }
1108:             if (minimum == Double.POSITIVE_INFINITY) {
1109:                 return null;
1110:             }
1111:             else {
1112:                 return new Double(minimum);
1113:             }
1114: 
1115:         }
1116: 
1117:     }
1118: 
1119:     /**
1120:      * Returns the maximum range value for the specified dataset.  This is easy
1121:      * if the dataset implements the {@link RangeInfo} interface (a good idea 
1122:      * if there is an efficient way to determine the maximum value).  
1123:      * Otherwise, it involves iterating over the entire data-set.  Returns 
1124:      * <code>null</code> if all the data values are <code>null</code>.
1125:      *
1126:      * @param dataset  the dataset (<code>null</code> not permitted).
1127:      *
1128:      * @return The maximum value (possibly <code>null</code>).
1129:      */
1130:     public static Number findMaximumRangeValue(CategoryDataset dataset) {
1131: 
1132:         if (dataset == null) {
1133:             throw new IllegalArgumentException("Null 'dataset' argument.");
1134:         }
1135: 
1136:         // work out the minimum value...
1137:         if (dataset instanceof RangeInfo) {
1138:             RangeInfo info = (RangeInfo) dataset;
1139:             return new Double(info.getRangeUpperBound(true));
1140:         }
1141: 
1142:         // hasn't implemented RangeInfo, so we'll have to iterate...
1143:         else {
1144: 
1145:             double maximum = Double.NEGATIVE_INFINITY;
1146:             int seriesCount = dataset.getRowCount();
1147:             int itemCount = dataset.getColumnCount();
1148:             for (int series = 0; series < seriesCount; series++) {
1149:                 for (int item = 0; item < itemCount; item++) {
1150:                     Number value;
1151:                     if (dataset instanceof IntervalCategoryDataset) {
1152:                         IntervalCategoryDataset icd 
1153:                             = (IntervalCategoryDataset) dataset;
1154:                         value = icd.getEndValue(series, item);
1155:                     }
1156:                     else {
1157:                         value = dataset.getValue(series, item);
1158:                     }
1159:                     if (value != null) {
1160:                         maximum = Math.max(maximum, value.doubleValue());
1161:                     }
1162:                 }
1163:             }
1164:             if (maximum == Double.NEGATIVE_INFINITY) {
1165:                 return null;
1166:             }
1167:             else {
1168:                 return new Double(maximum);
1169:             }
1170: 
1171:         }
1172: 
1173:     }
1174: 
1175:     /**
1176:      * Returns the maximum range value for the specified dataset.  This is 
1177:      * easy if the dataset implements the {@link RangeInfo} interface (a good 
1178:      * idea if there is an efficient way to determine the maximum value).  
1179:      * Otherwise, it involves iterating over the entire data-set.  Returns 
1180:      * <code>null</code> if all the data values are <code>null</code>.
1181:      *
1182:      * @param dataset  the dataset (<code>null</code> not permitted).
1183:      *
1184:      * @return The maximum value (possibly <code>null</code>).
1185:      */
1186:     public static Number findMaximumRangeValue(XYDataset dataset) {
1187: 
1188:         if (dataset == null) {
1189:             throw new IllegalArgumentException("Null 'dataset' argument.");
1190:         }
1191: 
1192:         // work out the minimum value...
1193:         if (dataset instanceof RangeInfo) {
1194:             RangeInfo info = (RangeInfo) dataset;
1195:             return new Double(info.getRangeUpperBound(true));
1196:         }
1197: 
1198:         // hasn't implemented RangeInfo, so we'll have to iterate...
1199:         else  {
1200: 
1201:             double maximum = Double.NEGATIVE_INFINITY;
1202:             int seriesCount = dataset.getSeriesCount();
1203:             for (int series = 0; series < seriesCount; series++) {
1204:                 int itemCount = dataset.getItemCount(series);
1205:                 for (int item = 0; item < itemCount; item++) {
1206:                     double value;
1207:                     if (dataset instanceof IntervalXYDataset) {
1208:                         IntervalXYDataset intervalXYData 
1209:                             = (IntervalXYDataset) dataset;
1210:                         value = intervalXYData.getEndYValue(series, item);
1211:                     }
1212:                     else if (dataset instanceof OHLCDataset) {
1213:                         OHLCDataset highLowData = (OHLCDataset) dataset;
1214:                         value = highLowData.getHighValue(series, item);
1215:                     }
1216:                     else {
1217:                         value = dataset.getYValue(series, item);
1218:                     }
1219:                     if (!Double.isNaN(value)) {
1220:                         maximum = Math.max(maximum, value);
1221:                     }
1222:                 }
1223:             }
1224:             if (maximum == Double.NEGATIVE_INFINITY) {
1225:                 return null;
1226:             }
1227:             else {
1228:                 return new Double(maximum);
1229:             }
1230: 
1231:         }
1232: 
1233:     }
1234: 
1235:     /**
1236:      * Returns the minimum and maximum values for the dataset's range 
1237:      * (y-values), assuming that the series in one category are stacked.
1238:      *
1239:      * @param dataset  the dataset (<code>null</code> not permitted).
1240:      *
1241:      * @return The range (<code>null</code> if the dataset contains no values).
1242:      */
1243:     public static Range findStackedRangeBounds(CategoryDataset dataset) {
1244:         return findStackedRangeBounds(dataset, 0.0);
1245:     }
1246: 
1247:     /**
1248:      * Returns the minimum and maximum values for the dataset's range 
1249:      * (y-values), assuming that the series in one category are stacked.
1250:      *
1251:      * @param dataset  the dataset (<code>null</code> not permitted).
1252:      * @param base  the base value for the bars.
1253:      *
1254:      * @return The range (<code>null</code> if the dataset contains no values).
1255:      */
1256:     public static Range findStackedRangeBounds(CategoryDataset dataset, 
1257:             double base) {
1258:         if (dataset == null) {
1259:             throw new IllegalArgumentException("Null 'dataset' argument.");
1260:         }
1261:         Range result = null;
1262:         double minimum = Double.POSITIVE_INFINITY;
1263:         double maximum = Double.NEGATIVE_INFINITY;
1264:         int categoryCount = dataset.getColumnCount();
1265:         for (int item = 0; item < categoryCount; item++) {
1266:             double positive = base;
1267:             double negative = base;
1268:             int seriesCount = dataset.getRowCount();
1269:             for (int series = 0; series < seriesCount; series++) {
1270:                 Number number = dataset.getValue(series, item);
1271:                 if (number != null) {
1272:                     double value = number.doubleValue();
1273:                     if (value > 0.0) {
1274:                         positive = positive + value;
1275:                     }
1276:                     if (value < 0.0) {
1277:                         negative = negative + value;  
1278:                         // '+', remember value is negative
1279:                     }
1280:                 }
1281:             }
1282:             minimum = Math.min(minimum, negative);
1283:             maximum = Math.max(maximum, positive);
1284:         }
1285:         if (minimum <= maximum) {
1286:             result = new Range(minimum, maximum);
1287:         }
1288:         return result;
1289: 
1290:     }
1291: 
1292:     /**
1293:      * Returns the minimum and maximum values for the dataset's range 
1294:      * (y-values), assuming that the series in one category are stacked.
1295:      *
1296:      * @param dataset  the dataset.
1297:      * @param map  a structure that maps series to groups.
1298:      *
1299:      * @return The value range (<code>null</code> if the dataset contains no 
1300:      *         values).
1301:      */
1302:     public static Range findStackedRangeBounds(CategoryDataset dataset,
1303:                                                KeyToGroupMap map) {
1304:     
1305:         Range result = null;
1306:         if (dataset != null) {
1307:             
1308:             // create an array holding the group indices...
1309:             int[] groupIndex = new int[dataset.getRowCount()];
1310:             for (int i = 0; i < dataset.getRowCount(); i++) {
1311:                 groupIndex[i] = map.getGroupIndex(
1312:                     map.getGroup(dataset.getRowKey(i))
1313:                 );   
1314:             }
1315:             
1316:             // minimum and maximum for each group...
1317:             int groupCount = map.getGroupCount();
1318:             double[] minimum = new double[groupCount];
1319:             double[] maximum = new double[groupCount];
1320:             
1321:             int categoryCount = dataset.getColumnCount();
1322:             for (int item = 0; item < categoryCount; item++) {
1323:                 double[] positive = new double[groupCount];
1324:                 double[] negative = new double[groupCount];
1325:                 int seriesCount = dataset.getRowCount();
1326:                 for (int series = 0; series < seriesCount; series++) {
1327:                     Number number = dataset.getValue(series, item);
1328:                     if (number != null) {
1329:                         double value = number.doubleValue();
1330:                         if (value > 0.0) {
1331:                             positive[groupIndex[series]] 
1332:                                  = positive[groupIndex[series]] + value;
1333:                         }
1334:                         if (value < 0.0) {
1335:                             negative[groupIndex[series]] 
1336:                                  = negative[groupIndex[series]] + value;
1337:                                  // '+', remember value is negative
1338:                         }
1339:                     }
1340:                 }
1341:                 for (int g = 0; g < groupCount; g++) {
1342:                     minimum[g] = Math.min(minimum[g], negative[g]);
1343:                     maximum[g] = Math.max(maximum[g], positive[g]);
1344:                 }
1345:             }
1346:             for (int j = 0; j < groupCount; j++) {
1347:                 result = Range.combine(
1348:                     result, new Range(minimum[j], maximum[j])
1349:                 );
1350:             }
1351:         }
1352:         return result;
1353: 
1354:     }
1355: 
1356:     /**
1357:      * Returns the minimum value in the dataset range, assuming that values in
1358:      * each category are "stacked".
1359:      *
1360:      * @param dataset  the dataset.
1361:      *
1362:      * @return The minimum value.
1363:      */
1364:     public static Number findMinimumStackedRangeValue(CategoryDataset dataset) {
1365: 
1366:         Number result = null;
1367:         if (dataset != null) {
1368:             double minimum = 0.0;
1369:             int categoryCount = dataset.getRowCount();
1370:             for (int item = 0; item < categoryCount; item++) {
1371:                 double total = 0.0;
1372: 
1373:                 int seriesCount = dataset.getColumnCount();
1374:                 for (int series = 0; series < seriesCount; series++) {
1375:                     Number number = dataset.getValue(series, item);
1376:                     if (number != null) {
1377:                         double value = number.doubleValue();
1378:                         if (value < 0.0) {
1379:                             total = total + value;  
1380:                             // '+', remember value is negative
1381:                         }
1382:                     }
1383:                 }
1384:                 minimum = Math.min(minimum, total);
1385: 
1386:             }
1387:             result = new Double(minimum);
1388:         }
1389:         return result;
1390: 
1391:     }
1392: 
1393:     /**
1394:      * Returns the maximum value in the dataset range, assuming that values in
1395:      * each category are "stacked".
1396:      *
1397:      * @param dataset  the dataset (<code>null</code> permitted).
1398:      *
1399:      * @return The maximum value (possibly <code>null</code>).
1400:      */
1401:     public static Number findMaximumStackedRangeValue(CategoryDataset dataset) {
1402: 
1403:         Number result = null;
1404: 
1405:         if (dataset != null) {
1406:             double maximum = 0.0;
1407:             int categoryCount = dataset.getColumnCount();
1408:             for (int item = 0; item < categoryCount; item++) {
1409:                 double total = 0.0;
1410:                 int seriesCount = dataset.getRowCount();
1411:                 for (int series = 0; series < seriesCount; series++) {
1412:                     Number number = dataset.getValue(series, item);
1413:                     if (number != null) {
1414:                         double value = number.doubleValue();
1415:                         if (value > 0.0) {
1416:                             total = total + value;
1417:                         }
1418:                     }
1419:                 }
1420:                 maximum = Math.max(maximum, total);
1421:             }
1422:             result = new Double(maximum);
1423:         }
1424: 
1425:         return result;
1426: 
1427:     }
1428: 
1429:     /**
1430:      * Returns the minimum and maximum values for the dataset's range,
1431:      * assuming that the series are stacked.
1432:      *
1433:      * @param dataset  the dataset (<code>null</code> not permitted).
1434:      * 
1435:      * @return The range ([0.0, 0.0] if the dataset contains no values).
1436:      */
1437:     public static Range findStackedRangeBounds(TableXYDataset dataset) {
1438:         return findStackedRangeBounds(dataset, 0.0);
1439:     }
1440:     
1441:     /**
1442:      * Returns the minimum and maximum values for the dataset's range,
1443:      * assuming that the series are stacked, using the specified base value.
1444:      *
1445:      * @param dataset  the dataset (<code>null</code> not permitted).
1446:      * @param base  the base value.
1447:      * 
1448:      * @return The range (<code>null</code> if the dataset contains no values).
1449:      */
1450:     public static Range findStackedRangeBounds(TableXYDataset dataset, 
1451:                                                double base) {
1452:         if (dataset == null) {
1453:             throw new IllegalArgumentException("Null 'dataset' argument.");
1454:         }
1455:         double minimum = base;
1456:         double maximum = base;
1457:         for (int itemNo = 0; itemNo < dataset.getItemCount(); itemNo++) {
1458:             double positive = base;
1459:             double negative = base;
1460:             int seriesCount = dataset.getSeriesCount();
1461:             for (int seriesNo = 0; seriesNo < seriesCount; seriesNo++) {
1462:                 double y = dataset.getYValue(seriesNo, itemNo);
1463:                 if (!Double.isNaN(y)) {
1464:                     if (y > 0.0) {
1465:                         positive += y;
1466:                     }
1467:                     else {
1468:                         negative += y;
1469:                     }
1470:                 }
1471:             }
1472:             if (positive > maximum) {
1473:                 maximum = positive;
1474:             } 
1475:             if (negative < minimum) {
1476:                 minimum = negative;
1477:             } 
1478:         }
1479:         if (minimum <= maximum) {
1480:             return new Range(minimum, maximum);
1481:         }
1482:         else {
1483:             return null;   
1484:         }
1485:     }
1486: 
1487:     /**
1488:      * Calculates the range of values for a dataset where each item is the 
1489:      * running total of the items for the current series.
1490:      * 
1491:      * @param dataset  the dataset (<code>null</code> not permitted).
1492:      * 
1493:      * @return The range.
1494:      */
1495:     public static Range findCumulativeRangeBounds(CategoryDataset dataset) {
1496:         
1497:         if (dataset == null) {
1498:             throw new IllegalArgumentException("Null 'dataset' argument.");
1499:         }
1500:         
1501:         boolean allItemsNull = true; // we'll set this to false if there is at 
1502:                                      // least one non-null data item... 
1503:         double minimum = 0.0;
1504:         double maximum = 0.0;
1505:         for (int row = 0; row < dataset.getRowCount(); row++) {
1506:             double runningTotal = 0.0;
1507:             for (int column = 0; column < dataset.getColumnCount() - 1; 
1508:                  column++) {
1509:                 Number n = dataset.getValue(row, column);
1510:                 if (n != null) {
1511:                     allItemsNull = false;
1512:                     double value = n.doubleValue();
1513:                     runningTotal = runningTotal + value;
1514:                     minimum = Math.min(minimum, runningTotal);
1515:                     maximum = Math.max(maximum, runningTotal);
1516:                 }
1517:             }    
1518:         }
1519:         if (!allItemsNull) {
1520:             return new Range(minimum, maximum);
1521:         }
1522:         else {
1523:             return null;
1524:         }
1525:         
1526:     }
1527: 
1528: }