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