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-2007, 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-2007, 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;
  37:  *
  38:  * Changes (from 18-Sep-2001)
  39:  * --------------------------
  40:  * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
  41:  * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
  42:  * 15-Nov-2001 : Moved to package com.jrefinery.data.* in the JCommon class 
  43:  *               library (DG);
  44:  *               Changed to handle null values from datasets (DG);
  45:  *               Bug fix (thanks to Andrzej Porebski) - initial value now set 
  46:  *               to positive or negative infinity when iterating (DG);
  47:  * 22-Nov-2001 : Datasets with containing no data now return null for min and 
  48:  *               max calculations (DG);
  49:  * 13-Dec-2001 : Extended to handle HighLowDataset and IntervalXYDataset (DG);
  50:  * 15-Feb-2002 : Added getMinimumStackedRangeValue() and 
  51:  *               getMaximumStackedRangeValue() (DG);
  52:  * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
  53:  * 18-Mar-2002 : Fixed bug in min/max domain calculation for datasets that 
  54:  *               implement the CategoryDataset interface AND the XYDataset 
  55:  *               interface at the same time.  Thanks to Jonathan Nash for the 
  56:  *               fix (DG);
  57:  * 23-Apr-2002 : Added getDomainExtent() and getRangeExtent() methods (DG);
  58:  * 13-Jun-2002 : Modified range measurements to handle 
  59:  *               IntervalCategoryDataset (DG);
  60:  * 12-Jul-2002 : Method name change in DomainInfo interface (DG);
  61:  * 30-Jul-2002 : Added pie dataset summation method (DG);
  62:  * 01-Oct-2002 : Added a method for constructing an XYDataset from a Function2D
  63:  *               instance (DG);
  64:  * 24-Oct-2002 : Amendments required following changes to the CategoryDataset 
  65:  *               interface (DG);
  66:  * 18-Nov-2002 : Changed CategoryDataset to TableDataset (DG);
  67:  * 04-Mar-2003 : Added isEmpty(XYDataset) method (DG);
  68:  * 05-Mar-2003 : Added a method for creating a CategoryDataset from a 
  69:  *               KeyedValues instance (DG);
  70:  * 15-May-2003 : Renamed isEmpty --> isEmptyOrNull (DG);
  71:  * 25-Jun-2003 : Added limitPieDataset methods (RA);
  72:  * 26-Jun-2003 : Modified getDomainExtent() method to accept null datasets (DG);
  73:  * 27-Jul-2003 : Added getStackedRangeExtent(TableXYDataset data) (RA);
  74:  * 18-Aug-2003 : getStackedRangeExtent(TableXYDataset data) now handles null 
  75:  *               values (RA);
  76:  * 02-Sep-2003 : Added method to check for null or empty PieDataset (DG);
  77:  * 18-Sep-2003 : Fix for bug 803660 (getMaximumRangeValue for 
  78:  *               CategoryDataset) (DG);
  79:  * 20-Oct-2003 : Added getCumulativeRangeExtent() method (DG);
  80:  * 09-Jan-2003 : Added argument checking code to the createCategoryDataset() 
  81:  *               method (DG);
  82:  * 23-Mar-2004 : Fixed bug in getMaximumStackedRangeValue() method (DG);
  83:  * 31-Mar-2004 : Exposed the extent iteration algorithms to use one of them and 
  84:  *               applied noninstantiation pattern (AS);
  85:  * 11-May-2004 : Renamed getPieDatasetTotal --> calculatePieDatasetTotal (DG);
  86:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue();
  87:  * 24-Aug-2004 : Added argument checks to createCategoryDataset() method (DG);
  88:  * 04-Oct-2004 : Renamed ArrayUtils --> ArrayUtilities (DG);
  89:  * 06-Oct-2004 : Renamed findDomainExtent() --> findDomainBounds(),
  90:  *               findRangeExtent() --> findRangeBounds() (DG);
  91:  * 07-Jan-2005 : Renamed findStackedRangeExtent() --> findStackedRangeBounds(),
  92:  *               findCumulativeRangeExtent() --> findCumulativeRangeBounds(),
  93:  *               iterateXYRangeExtent() --> iterateXYRangeBounds(), 
  94:  *               removed deprecated methods (DG);
  95:  * 03-Feb-2005 : The findStackedRangeBounds() methods now return null for 
  96:  *               empty datasets (DG);
  97:  * 03-Mar-2005 : Moved createNumberArray() and createNumberArray2D() methods
  98:  *               from DatasetUtilities --> DataUtilities (DG);
  99:  * 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: }