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