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