001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-2009, 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; 037 * Rafal Skalny (patch 1925366); 038 * Jerome David (patch 2131001); 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 * ------------- JFREECHART 1.0.x --------------------------------------------- 104 * 15-Mar-2007 : Added calculateStackTotal() method (DG); 105 * 27-Mar-2008 : Fixed bug in findCumulativeRangeBounds() method (DG); 106 * 28-Mar-2008 : Fixed sample count in sampleFunction2D() method, renamed 107 * iterateXYRangeBounds() --> iterateRangeBounds(XYDataset), and 108 * fixed a bug in findRangeBounds(XYDataset, false) (DG); 109 * 28-Mar-2008 : Applied a variation of patch 1925366 (from Rafal Skalny) for 110 * slightly more efficient iterateRangeBounds() methods (DG); 111 * 08-Apr-2008 : Fixed typo in iterateRangeBounds() (DG); 112 * 08-Oct-2008 : Applied patch 2131001 by Jerome David, with some modifications 113 * and additions and some new unit tests (DG); 114 * 12-Feb-2009 : Added sampleFunction2DToSeries() method (DG); 115 * 27-Mar-2009 : Added new methods to find domain and range bounds taking into 116 * account hidden series (DG); 117 * 01-Apr-2009 : Handle a StatisticalCategoryDataset in 118 * iterateToFindRangeBounds() (DG); 119 * 120 */ 121 122 package org.jfree.data.general; 123 124 import java.util.ArrayList; 125 import java.util.Iterator; 126 import java.util.List; 127 128 import org.jfree.data.DomainInfo; 129 import org.jfree.data.KeyToGroupMap; 130 import org.jfree.data.KeyedValues; 131 import org.jfree.data.Range; 132 import org.jfree.data.RangeInfo; 133 import org.jfree.data.category.CategoryDataset; 134 import org.jfree.data.category.CategoryRangeInfo; 135 import org.jfree.data.category.DefaultCategoryDataset; 136 import org.jfree.data.category.IntervalCategoryDataset; 137 import org.jfree.data.function.Function2D; 138 import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset; 139 import org.jfree.data.statistics.BoxAndWhiskerXYDataset; 140 import org.jfree.data.statistics.StatisticalCategoryDataset; 141 import org.jfree.data.xy.IntervalXYDataset; 142 import org.jfree.data.xy.OHLCDataset; 143 import org.jfree.data.xy.TableXYDataset; 144 import org.jfree.data.xy.XYDataset; 145 import org.jfree.data.xy.XYDomainInfo; 146 import org.jfree.data.xy.XYRangeInfo; 147 import org.jfree.data.xy.XYSeries; 148 import org.jfree.data.xy.XYSeriesCollection; 149 import org.jfree.util.ArrayUtilities; 150 151 /** 152 * A collection of useful static methods relating to datasets. 153 */ 154 public final class DatasetUtilities { 155 156 /** 157 * Private constructor for non-instanceability. 158 */ 159 private DatasetUtilities() { 160 // now try to instantiate this ;-) 161 } 162 163 /** 164 * Calculates the total of all the values in a {@link PieDataset}. If 165 * the dataset contains negative or <code>null</code> values, they are 166 * ignored. 167 * 168 * @param dataset the dataset (<code>null</code> not permitted). 169 * 170 * @return The total. 171 */ 172 public static double calculatePieDatasetTotal(PieDataset dataset) { 173 if (dataset == null) { 174 throw new IllegalArgumentException("Null 'dataset' argument."); 175 } 176 List keys = dataset.getKeys(); 177 double totalValue = 0; 178 Iterator iterator = keys.iterator(); 179 while (iterator.hasNext()) { 180 Comparable current = (Comparable) iterator.next(); 181 if (current != null) { 182 Number value = dataset.getValue(current); 183 double v = 0.0; 184 if (value != null) { 185 v = value.doubleValue(); 186 } 187 if (v > 0) { 188 totalValue = totalValue + v; 189 } 190 } 191 } 192 return totalValue; 193 } 194 195 /** 196 * Creates a pie dataset from a table dataset by taking all the values 197 * for a single row. 198 * 199 * @param dataset the dataset (<code>null</code> not permitted). 200 * @param rowKey the row key. 201 * 202 * @return A pie dataset. 203 */ 204 public static PieDataset createPieDatasetForRow(CategoryDataset dataset, 205 Comparable rowKey) { 206 int row = dataset.getRowIndex(rowKey); 207 return createPieDatasetForRow(dataset, row); 208 } 209 210 /** 211 * Creates a pie dataset from a table dataset by taking all the values 212 * for a single row. 213 * 214 * @param dataset the dataset (<code>null</code> not permitted). 215 * @param row the row (zero-based index). 216 * 217 * @return A pie dataset. 218 */ 219 public static PieDataset createPieDatasetForRow(CategoryDataset dataset, 220 int row) { 221 DefaultPieDataset result = new DefaultPieDataset(); 222 int columnCount = dataset.getColumnCount(); 223 for (int current = 0; current < columnCount; current++) { 224 Comparable columnKey = dataset.getColumnKey(current); 225 result.setValue(columnKey, dataset.getValue(row, current)); 226 } 227 return result; 228 } 229 230 /** 231 * Creates a pie dataset from a table dataset by taking all the values 232 * for a single column. 233 * 234 * @param dataset the dataset (<code>null</code> not permitted). 235 * @param columnKey the column key. 236 * 237 * @return A pie dataset. 238 */ 239 public static PieDataset createPieDatasetForColumn(CategoryDataset dataset, 240 Comparable columnKey) { 241 int column = dataset.getColumnIndex(columnKey); 242 return createPieDatasetForColumn(dataset, column); 243 } 244 245 /** 246 * Creates a pie dataset from a {@link CategoryDataset} by taking all the 247 * values for a single column. 248 * 249 * @param dataset the dataset (<code>null</code> not permitted). 250 * @param column the column (zero-based index). 251 * 252 * @return A pie dataset. 253 */ 254 public static PieDataset createPieDatasetForColumn(CategoryDataset dataset, 255 int column) { 256 DefaultPieDataset result = new DefaultPieDataset(); 257 int rowCount = dataset.getRowCount(); 258 for (int i = 0; i < rowCount; i++) { 259 Comparable rowKey = dataset.getRowKey(i); 260 result.setValue(rowKey, dataset.getValue(i, column)); 261 } 262 return result; 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 with the 269 * key "Other". 270 * 271 * @param source the source dataset (<code>null</code> not permitted). 272 * @param key a new key for the aggregated items (<code>null</code> not 273 * permitted). 274 * @param minimumPercent the percent threshold. 275 * 276 * @return The pie dataset with (possibly) aggregated items. 277 */ 278 public static PieDataset createConsolidatedPieDataset(PieDataset source, 279 Comparable key, double minimumPercent) { 280 return DatasetUtilities.createConsolidatedPieDataset(source, key, 281 minimumPercent, 2); 282 } 283 284 /** 285 * Creates a new pie dataset based on the supplied dataset, but modified 286 * by aggregating all the low value items (those whose value is lower 287 * than the <code>percentThreshold</code>) into a single item. The 288 * aggregated items are assigned the specified key. Aggregation only 289 * occurs if there are at least <code>minItems</code> items to aggregate. 290 * 291 * @param source the source dataset (<code>null</code> not permitted). 292 * @param key the key to represent the aggregated items. 293 * @param minimumPercent the percent threshold (ten percent is 0.10). 294 * @param minItems only aggregate low values if there are at least this 295 * many. 296 * 297 * @return The pie dataset with (possibly) aggregated items. 298 */ 299 public static PieDataset createConsolidatedPieDataset(PieDataset source, 300 Comparable key, double minimumPercent, int minItems) { 301 302 DefaultPieDataset result = new DefaultPieDataset(); 303 double total = DatasetUtilities.calculatePieDatasetTotal(source); 304 305 // Iterate and find all keys below threshold percentThreshold 306 List keys = source.getKeys(); 307 ArrayList otherKeys = new ArrayList(); 308 Iterator iterator = keys.iterator(); 309 while (iterator.hasNext()) { 310 Comparable currentKey = (Comparable) iterator.next(); 311 Number dataValue = source.getValue(currentKey); 312 if (dataValue != null) { 313 double value = dataValue.doubleValue(); 314 if (value / total < minimumPercent) { 315 otherKeys.add(currentKey); 316 } 317 } 318 } 319 320 // Create new dataset with keys above threshold percentThreshold 321 iterator = keys.iterator(); 322 double otherValue = 0; 323 while (iterator.hasNext()) { 324 Comparable currentKey = (Comparable) iterator.next(); 325 Number dataValue = source.getValue(currentKey); 326 if (dataValue != null) { 327 if (otherKeys.contains(currentKey) 328 && otherKeys.size() >= minItems) { 329 // Do not add key to dataset 330 otherValue += dataValue.doubleValue(); 331 } 332 else { 333 // Add key to dataset 334 result.setValue(currentKey, dataValue); 335 } 336 } 337 } 338 // Add other category if applicable 339 if (otherKeys.size() >= minItems) { 340 result.setValue(key, otherValue); 341 } 342 return result; 343 } 344 345 /** 346 * Creates a {@link CategoryDataset} that contains a copy of the data in an 347 * array (instances of <code>Double</code> are created to represent the 348 * data items). 349 * <p> 350 * Row and column keys are created by appending 0, 1, 2, ... to the 351 * supplied prefixes. 352 * 353 * @param rowKeyPrefix the row key prefix. 354 * @param columnKeyPrefix the column key prefix. 355 * @param data the data. 356 * 357 * @return The dataset. 358 */ 359 public static CategoryDataset createCategoryDataset(String rowKeyPrefix, 360 String columnKeyPrefix, double[][] data) { 361 362 DefaultCategoryDataset result = new DefaultCategoryDataset(); 363 for (int r = 0; r < data.length; r++) { 364 String rowKey = rowKeyPrefix + (r + 1); 365 for (int c = 0; c < data[r].length; c++) { 366 String columnKey = columnKeyPrefix + (c + 1); 367 result.addValue(new Double(data[r][c]), rowKey, columnKey); 368 } 369 } 370 return result; 371 372 } 373 374 /** 375 * Creates a {@link CategoryDataset} that contains a copy of the data in 376 * an array. 377 * <p> 378 * Row and column keys are created by appending 0, 1, 2, ... to the 379 * supplied prefixes. 380 * 381 * @param rowKeyPrefix the row key prefix. 382 * @param columnKeyPrefix the column key prefix. 383 * @param data the data. 384 * 385 * @return The dataset. 386 */ 387 public static CategoryDataset createCategoryDataset(String rowKeyPrefix, 388 String columnKeyPrefix, Number[][] data) { 389 390 DefaultCategoryDataset result = new DefaultCategoryDataset(); 391 for (int r = 0; r < data.length; r++) { 392 String rowKey = rowKeyPrefix + (r + 1); 393 for (int c = 0; c < data[r].length; c++) { 394 String columnKey = columnKeyPrefix + (c + 1); 395 result.addValue(data[r][c], rowKey, columnKey); 396 } 397 } 398 return result; 399 400 } 401 402 /** 403 * Creates a {@link CategoryDataset} that contains a copy of the data in 404 * an array (instances of <code>Double</code> are created to represent the 405 * data items). 406 * <p> 407 * Row and column keys are taken from the supplied arrays. 408 * 409 * @param rowKeys the row keys (<code>null</code> not permitted). 410 * @param columnKeys the column keys (<code>null</code> not permitted). 411 * @param data the data. 412 * 413 * @return The dataset. 414 */ 415 public static CategoryDataset createCategoryDataset(Comparable[] rowKeys, 416 Comparable[] columnKeys, double[][] data) { 417 418 // check arguments... 419 if (rowKeys == null) { 420 throw new IllegalArgumentException("Null 'rowKeys' argument."); 421 } 422 if (columnKeys == null) { 423 throw new IllegalArgumentException("Null 'columnKeys' argument."); 424 } 425 if (ArrayUtilities.hasDuplicateItems(rowKeys)) { 426 throw new IllegalArgumentException("Duplicate items in 'rowKeys'."); 427 } 428 if (ArrayUtilities.hasDuplicateItems(columnKeys)) { 429 throw new IllegalArgumentException( 430 "Duplicate items in 'columnKeys'."); 431 } 432 if (rowKeys.length != data.length) { 433 throw new IllegalArgumentException( 434 "The number of row keys does not match the number of rows in " 435 + "the data array."); 436 } 437 int columnCount = 0; 438 for (int r = 0; r < data.length; r++) { 439 columnCount = Math.max(columnCount, data[r].length); 440 } 441 if (columnKeys.length != columnCount) { 442 throw new IllegalArgumentException( 443 "The number of column keys does not match the number of " 444 + "columns in the data array."); 445 } 446 447 // now do the work... 448 DefaultCategoryDataset result = new DefaultCategoryDataset(); 449 for (int r = 0; r < data.length; r++) { 450 Comparable rowKey = rowKeys[r]; 451 for (int c = 0; c < data[r].length; c++) { 452 Comparable columnKey = columnKeys[c]; 453 result.addValue(new Double(data[r][c]), rowKey, columnKey); 454 } 455 } 456 return result; 457 458 } 459 460 /** 461 * Creates a {@link CategoryDataset} by copying the data from the supplied 462 * {@link KeyedValues} instance. 463 * 464 * @param rowKey the row key (<code>null</code> not permitted). 465 * @param rowData the row data (<code>null</code> not permitted). 466 * 467 * @return A dataset. 468 */ 469 public static CategoryDataset createCategoryDataset(Comparable rowKey, 470 KeyedValues rowData) { 471 472 if (rowKey == null) { 473 throw new IllegalArgumentException("Null 'rowKey' argument."); 474 } 475 if (rowData == null) { 476 throw new IllegalArgumentException("Null 'rowData' argument."); 477 } 478 DefaultCategoryDataset result = new DefaultCategoryDataset(); 479 for (int i = 0; i < rowData.getItemCount(); i++) { 480 result.addValue(rowData.getValue(i), rowKey, rowData.getKey(i)); 481 } 482 return result; 483 484 } 485 486 /** 487 * Creates an {@link XYDataset} by sampling the specified function over a 488 * fixed range. 489 * 490 * @param f the function (<code>null</code> not permitted). 491 * @param start the start value for the range. 492 * @param end the end value for the range. 493 * @param samples the number of sample points (must be > 1). 494 * @param seriesKey the key to give the resulting series 495 * (<code>null</code> not permitted). 496 * 497 * @return A dataset. 498 */ 499 public static XYDataset sampleFunction2D(Function2D f, double start, 500 double end, int samples, Comparable seriesKey) { 501 502 // defer argument checking 503 XYSeries series = sampleFunction2DToSeries(f, start, end, samples, 504 seriesKey); 505 XYSeriesCollection collection = new XYSeriesCollection(series); 506 return collection; 507 } 508 509 /** 510 * Creates an {@link XYSeries} by sampling the specified function over a 511 * fixed range. 512 * 513 * @param f the function (<code>null</code> not permitted). 514 * @param start the start value for the range. 515 * @param end the end value for the range. 516 * @param samples the number of sample points (must be > 1). 517 * @param seriesKey the key to give the resulting series 518 * (<code>null</code> not permitted). 519 * 520 * @return A series. 521 * 522 * @since 1.0.13 523 */ 524 public static XYSeries sampleFunction2DToSeries(Function2D f, 525 double start, double end, int samples, Comparable seriesKey) { 526 527 if (f == null) { 528 throw new IllegalArgumentException("Null 'f' argument."); 529 } 530 if (seriesKey == null) { 531 throw new IllegalArgumentException("Null 'seriesKey' argument."); 532 } 533 if (start >= end) { 534 throw new IllegalArgumentException("Requires 'start' < 'end'."); 535 } 536 if (samples < 2) { 537 throw new IllegalArgumentException("Requires 'samples' > 1"); 538 } 539 540 XYSeries series = new XYSeries(seriesKey); 541 double step = (end - start) / (samples - 1); 542 for (int i = 0; i < samples; i++) { 543 double x = start + (step * i); 544 series.add(x, f.getValue(x)); 545 } 546 return series; 547 } 548 549 /** 550 * Returns <code>true</code> if the dataset is empty (or <code>null</code>), 551 * and <code>false</code> otherwise. 552 * 553 * @param dataset the dataset (<code>null</code> permitted). 554 * 555 * @return A boolean. 556 */ 557 public static boolean isEmptyOrNull(PieDataset dataset) { 558 559 if (dataset == null) { 560 return true; 561 } 562 563 int itemCount = dataset.getItemCount(); 564 if (itemCount == 0) { 565 return true; 566 } 567 568 for (int item = 0; item < itemCount; item++) { 569 Number y = dataset.getValue(item); 570 if (y != null) { 571 double yy = y.doubleValue(); 572 if (yy > 0.0) { 573 return false; 574 } 575 } 576 } 577 578 return true; 579 580 } 581 582 /** 583 * Returns <code>true</code> if the dataset is empty (or <code>null</code>), 584 * and <code>false</code> otherwise. 585 * 586 * @param dataset the dataset (<code>null</code> permitted). 587 * 588 * @return A boolean. 589 */ 590 public static boolean isEmptyOrNull(CategoryDataset dataset) { 591 592 if (dataset == null) { 593 return true; 594 } 595 596 int rowCount = dataset.getRowCount(); 597 int columnCount = dataset.getColumnCount(); 598 if (rowCount == 0 || columnCount == 0) { 599 return true; 600 } 601 602 for (int r = 0; r < rowCount; r++) { 603 for (int c = 0; c < columnCount; c++) { 604 if (dataset.getValue(r, c) != null) { 605 return false; 606 } 607 608 } 609 } 610 611 return true; 612 613 } 614 615 /** 616 * Returns <code>true</code> if the dataset is empty (or <code>null</code>), 617 * and <code>false</code> otherwise. 618 * 619 * @param dataset the dataset (<code>null</code> permitted). 620 * 621 * @return A boolean. 622 */ 623 public static boolean isEmptyOrNull(XYDataset dataset) { 624 if (dataset != null) { 625 for (int s = 0; s < dataset.getSeriesCount(); s++) { 626 if (dataset.getItemCount(s) > 0) { 627 return false; 628 } 629 } 630 } 631 return true; 632 } 633 634 /** 635 * Returns the range of values in the domain (x-values) of a dataset. 636 * 637 * @param dataset the dataset (<code>null</code> not permitted). 638 * 639 * @return The range of values (possibly <code>null</code>). 640 */ 641 public static Range findDomainBounds(XYDataset dataset) { 642 return findDomainBounds(dataset, true); 643 } 644 645 /** 646 * Returns the range of values in the domain (x-values) of a dataset. 647 * 648 * @param dataset the dataset (<code>null</code> not permitted). 649 * @param includeInterval determines whether or not the x-interval is taken 650 * into account (only applies if the dataset is an 651 * {@link IntervalXYDataset}). 652 * 653 * @return The range of values (possibly <code>null</code>). 654 */ 655 public static Range findDomainBounds(XYDataset dataset, 656 boolean includeInterval) { 657 658 if (dataset == null) { 659 throw new IllegalArgumentException("Null 'dataset' argument."); 660 } 661 662 Range result = null; 663 // if the dataset implements DomainInfo, life is easier 664 if (dataset instanceof DomainInfo) { 665 DomainInfo info = (DomainInfo) dataset; 666 result = info.getDomainBounds(includeInterval); 667 } 668 else { 669 result = iterateDomainBounds(dataset, includeInterval); 670 } 671 return result; 672 673 } 674 675 /** 676 * Returns the bounds of the x-values in the specified <code>dataset</code> 677 * taking into account only the visible series and including any x-interval 678 * if requested. 679 * 680 * @param dataset the dataset (<code>null</code> not permitted). 681 * @param visibleSeriesKeys the visible series keys (<code>null</code> 682 * not permitted). 683 * @param includeInterval include the x-interval (if any)? 684 * 685 * @return The bounds (or <code>null</code> if the dataset contains no 686 * values. 687 * 688 * @since 1.0.13 689 */ 690 public static Range findDomainBounds(XYDataset dataset, 691 List visibleSeriesKeys, boolean includeInterval) { 692 if (dataset == null) { 693 throw new IllegalArgumentException("Null 'dataset' argument."); 694 } 695 Range result = null; 696 if (dataset instanceof XYDomainInfo) { 697 XYDomainInfo info = (XYDomainInfo) dataset; 698 result = info.getDomainBounds(visibleSeriesKeys, includeInterval); 699 } 700 else { 701 result = iterateToFindDomainBounds(dataset, visibleSeriesKeys, 702 includeInterval); 703 } 704 return result; 705 } 706 707 /** 708 * Iterates over the items in an {@link XYDataset} to find 709 * the range of x-values. If the dataset is an instance of 710 * {@link IntervalXYDataset}, the starting and ending x-values 711 * will be used for the bounds calculation. 712 * 713 * @param dataset the dataset (<code>null</code> not permitted). 714 * 715 * @return The range (possibly <code>null</code>). 716 */ 717 public static Range iterateDomainBounds(XYDataset dataset) { 718 return iterateDomainBounds(dataset, true); 719 } 720 721 /** 722 * Iterates over the items in an {@link XYDataset} to find 723 * the range of x-values. 724 * 725 * @param dataset the dataset (<code>null</code> not permitted). 726 * @param includeInterval a flag that determines, for an 727 * {@link IntervalXYDataset}, whether the x-interval or just the 728 * x-value is used to determine the overall range. 729 * 730 * @return The range (possibly <code>null</code>). 731 */ 732 public static Range iterateDomainBounds(XYDataset dataset, 733 boolean includeInterval) { 734 if (dataset == null) { 735 throw new IllegalArgumentException("Null 'dataset' argument."); 736 } 737 double minimum = Double.POSITIVE_INFINITY; 738 double maximum = Double.NEGATIVE_INFINITY; 739 int seriesCount = dataset.getSeriesCount(); 740 double lvalue; 741 double uvalue; 742 if (includeInterval && dataset instanceof IntervalXYDataset) { 743 IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset; 744 for (int series = 0; series < seriesCount; series++) { 745 int itemCount = dataset.getItemCount(series); 746 for (int item = 0; item < itemCount; item++) { 747 lvalue = intervalXYData.getStartXValue(series, item); 748 uvalue = intervalXYData.getEndXValue(series, item); 749 if (!Double.isNaN(lvalue)) { 750 minimum = Math.min(minimum, lvalue); 751 } 752 if (!Double.isNaN(uvalue)) { 753 maximum = Math.max(maximum, uvalue); 754 } 755 } 756 } 757 } 758 else { 759 for (int series = 0; series < seriesCount; series++) { 760 int itemCount = dataset.getItemCount(series); 761 for (int item = 0; item < itemCount; item++) { 762 lvalue = dataset.getXValue(series, item); 763 uvalue = lvalue; 764 if (!Double.isNaN(lvalue)) { 765 minimum = Math.min(minimum, lvalue); 766 maximum = Math.max(maximum, uvalue); 767 } 768 } 769 } 770 } 771 if (minimum > maximum) { 772 return null; 773 } 774 else { 775 return new Range(minimum, maximum); 776 } 777 } 778 779 /** 780 * Returns the range of values in the range for the dataset. 781 * 782 * @param dataset the dataset (<code>null</code> not permitted). 783 * 784 * @return The range (possibly <code>null</code>). 785 */ 786 public static Range findRangeBounds(CategoryDataset dataset) { 787 return findRangeBounds(dataset, true); 788 } 789 790 /** 791 * Returns the range of values in the range for the dataset. 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 public static Range findRangeBounds(CategoryDataset dataset, 800 boolean includeInterval) { 801 if (dataset == null) { 802 throw new IllegalArgumentException("Null 'dataset' argument."); 803 } 804 Range result = null; 805 if (dataset instanceof RangeInfo) { 806 RangeInfo info = (RangeInfo) dataset; 807 result = info.getRangeBounds(includeInterval); 808 } 809 else { 810 result = iterateRangeBounds(dataset, includeInterval); 811 } 812 return result; 813 } 814 815 /** 816 * Finds the bounds of the y-values in the specified dataset, including 817 * only those series that are listed in visibleSeriesKeys. 818 * 819 * @param dataset the dataset (<code>null</code> not permitted). 820 * @param visibleSeriesKeys the keys for the visible series 821 * (<code>null</code> not permitted). 822 * @param includeInterval include the y-interval (if the dataset has a 823 * y-interval). 824 * 825 * @return The data bounds. 826 * 827 * @since 1.0.13 828 */ 829 public static Range findRangeBounds(CategoryDataset dataset, 830 List visibleSeriesKeys, boolean includeInterval) { 831 if (dataset == null) { 832 throw new IllegalArgumentException("Null 'dataset' argument."); 833 } 834 Range result = null; 835 if (dataset instanceof CategoryRangeInfo) { 836 CategoryRangeInfo info = (CategoryRangeInfo) dataset; 837 result = info.getRangeBounds(visibleSeriesKeys, includeInterval); 838 } 839 else { 840 result = iterateToFindRangeBounds(dataset, visibleSeriesKeys, 841 includeInterval); 842 } 843 return result; 844 } 845 846 /** 847 * Returns the range of values in the range for the dataset. This method 848 * is the partner for the {@link #findDomainBounds(XYDataset)} method. 849 * 850 * @param dataset the dataset (<code>null</code> not permitted). 851 * 852 * @return The range (possibly <code>null</code>). 853 */ 854 public static Range findRangeBounds(XYDataset dataset) { 855 return findRangeBounds(dataset, true); 856 } 857 858 /** 859 * Returns the range of values in the range for the dataset. This method 860 * is the partner for the {@link #findDomainBounds(XYDataset, boolean)} 861 * method. 862 * 863 * @param dataset the dataset (<code>null</code> not permitted). 864 * @param includeInterval a flag that determines whether or not the 865 * y-interval is taken into account. 866 * 867 * @return The range (possibly <code>null</code>). 868 */ 869 public static Range findRangeBounds(XYDataset dataset, 870 boolean includeInterval) { 871 if (dataset == null) { 872 throw new IllegalArgumentException("Null 'dataset' argument."); 873 } 874 Range result = null; 875 if (dataset instanceof RangeInfo) { 876 RangeInfo info = (RangeInfo) dataset; 877 result = info.getRangeBounds(includeInterval); 878 } 879 else { 880 result = iterateRangeBounds(dataset, includeInterval); 881 } 882 return result; 883 } 884 885 /** 886 * Finds the bounds of the y-values in the specified dataset, including 887 * only those series that are listed in visibleSeriesKeys, and those items 888 * whose x-values fall within the specified range. 889 * 890 * @param dataset the dataset (<code>null</code> not permitted). 891 * @param visibleSeriesKeys the keys for the visible series 892 * (<code>null</code> not permitted). 893 * @param xRange the x-range (<code>null</code> not permitted). 894 * @param includeInterval include the y-interval (if the dataset has a 895 * y-interval). 896 * 897 * @return The data bounds. 898 * 899 * @since 1.0.13 900 */ 901 public static Range findRangeBounds(XYDataset dataset, 902 List visibleSeriesKeys, Range xRange, boolean includeInterval) { 903 if (dataset == null) { 904 throw new IllegalArgumentException("Null 'dataset' argument."); 905 } 906 Range result = null; 907 if (dataset instanceof XYRangeInfo) { 908 XYRangeInfo info = (XYRangeInfo) dataset; 909 result = info.getRangeBounds(visibleSeriesKeys, xRange, 910 includeInterval); 911 } 912 else { 913 result = iterateToFindRangeBounds(dataset, visibleSeriesKeys, 914 xRange, includeInterval); 915 } 916 return result; 917 } 918 919 /** 920 * Iterates over the data item of the category dataset to find 921 * the range bounds. 922 * 923 * @param dataset the dataset (<code>null</code> not permitted). 924 * @param includeInterval a flag that determines whether or not the 925 * y-interval is taken into account. 926 * 927 * @return The range (possibly <code>null</code>). 928 * 929 * @deprecated As of 1.0.10, use 930 * {@link #iterateRangeBounds(CategoryDataset, boolean)}. 931 */ 932 public static Range iterateCategoryRangeBounds(CategoryDataset dataset, 933 boolean includeInterval) { 934 return iterateRangeBounds(dataset, includeInterval); 935 } 936 937 /** 938 * Iterates over the data item of the category dataset to find 939 * the range bounds. 940 * 941 * @param dataset the dataset (<code>null</code> not permitted). 942 * 943 * @return The range (possibly <code>null</code>). 944 * 945 * @since 1.0.10 946 */ 947 public static Range iterateRangeBounds(CategoryDataset dataset) { 948 return iterateRangeBounds(dataset, true); 949 } 950 951 /** 952 * Iterates over the data item of the category dataset to find 953 * the range bounds. 954 * 955 * @param dataset the dataset (<code>null</code> not permitted). 956 * @param includeInterval a flag that determines whether or not the 957 * y-interval is taken into account. 958 * 959 * @return The range (possibly <code>null</code>). 960 * 961 * @since 1.0.10 962 */ 963 public static Range iterateRangeBounds(CategoryDataset dataset, 964 boolean includeInterval) { 965 double minimum = Double.POSITIVE_INFINITY; 966 double maximum = Double.NEGATIVE_INFINITY; 967 int rowCount = dataset.getRowCount(); 968 int columnCount = dataset.getColumnCount(); 969 if (includeInterval && dataset instanceof IntervalCategoryDataset) { 970 // handle the special case where the dataset has y-intervals that 971 // we want to measure 972 IntervalCategoryDataset icd = (IntervalCategoryDataset) dataset; 973 Number lvalue, uvalue; 974 for (int row = 0; row < rowCount; row++) { 975 for (int column = 0; column < columnCount; column++) { 976 lvalue = icd.getStartValue(row, column); 977 uvalue = icd.getEndValue(row, column); 978 if (lvalue != null && !Double.isNaN(lvalue.doubleValue())) { 979 minimum = Math.min(minimum, lvalue.doubleValue()); 980 } 981 if (uvalue != null && !Double.isNaN(uvalue.doubleValue())) { 982 maximum = Math.max(maximum, uvalue.doubleValue()); 983 } 984 } 985 } 986 } 987 else { 988 // handle the standard case (plain CategoryDataset) 989 for (int row = 0; row < rowCount; row++) { 990 for (int column = 0; column < columnCount; column++) { 991 Number value = dataset.getValue(row, column); 992 if (value != null) { 993 double v = value.doubleValue(); 994 if (!Double.isNaN(v)) { 995 minimum = Math.min(minimum, v); 996 maximum = Math.max(maximum, v); 997 } 998 } 999 } 1000 } 1001 } 1002 if (minimum == Double.POSITIVE_INFINITY) { 1003 return null; 1004 } 1005 else { 1006 return new Range(minimum, maximum); 1007 } 1008 } 1009 1010 /** 1011 * Iterates over the data item of the category dataset to find 1012 * the range bounds. 1013 * 1014 * @param dataset the dataset (<code>null</code> not permitted). 1015 * @param includeInterval a flag that determines whether or not the 1016 * y-interval is taken into account. 1017 * @param visibleSeriesKeys the visible series keys. 1018 * 1019 * @return The range (possibly <code>null</code>). 1020 * 1021 * @since 1.0.13 1022 */ 1023 public static Range iterateToFindRangeBounds(CategoryDataset dataset, 1024 List visibleSeriesKeys, boolean includeInterval) { 1025 1026 if (dataset == null) { 1027 throw new IllegalArgumentException("Null 'dataset' argument."); 1028 } 1029 if (visibleSeriesKeys == null) { 1030 throw new IllegalArgumentException( 1031 "Null 'visibleSeriesKeys' argument."); 1032 } 1033 1034 double minimum = Double.POSITIVE_INFINITY; 1035 double maximum = Double.NEGATIVE_INFINITY; 1036 int columnCount = dataset.getColumnCount(); 1037 if (includeInterval 1038 && dataset instanceof BoxAndWhiskerCategoryDataset) { 1039 // handle special case of BoxAndWhiskerDataset 1040 BoxAndWhiskerCategoryDataset bx 1041 = (BoxAndWhiskerCategoryDataset) dataset; 1042 Iterator iterator = visibleSeriesKeys.iterator(); 1043 while (iterator.hasNext()) { 1044 Comparable seriesKey = (Comparable) iterator.next(); 1045 int series = dataset.getRowIndex(seriesKey); 1046 int itemCount = dataset.getColumnCount(); 1047 for (int item = 0; item < itemCount; item++) { 1048 Number lvalue = bx.getMinRegularValue(series, item); 1049 if (lvalue == null) { 1050 lvalue = bx.getValue(series, item); 1051 } 1052 Number uvalue = bx.getMaxRegularValue(series, item); 1053 if (uvalue == null) { 1054 uvalue = bx.getValue(series, item); 1055 } 1056 if (lvalue != null) { 1057 minimum = Math.min(minimum, lvalue.doubleValue()); 1058 } 1059 if (uvalue != null) { 1060 maximum = Math.max(maximum, uvalue.doubleValue()); 1061 } 1062 } 1063 } 1064 } 1065 else if (includeInterval 1066 && dataset instanceof IntervalCategoryDataset) { 1067 // handle the special case where the dataset has y-intervals that 1068 // we want to measure 1069 IntervalCategoryDataset icd = (IntervalCategoryDataset) dataset; 1070 Number lvalue, uvalue; 1071 Iterator iterator = visibleSeriesKeys.iterator(); 1072 while (iterator.hasNext()) { 1073 Comparable seriesKey = (Comparable) iterator.next(); 1074 int series = dataset.getRowIndex(seriesKey); 1075 for (int column = 0; column < columnCount; column++) { 1076 lvalue = icd.getStartValue(series, column); 1077 uvalue = icd.getEndValue(series, column); 1078 if (lvalue != null && !Double.isNaN(lvalue.doubleValue())) { 1079 minimum = Math.min(minimum, lvalue.doubleValue()); 1080 } 1081 if (uvalue != null && !Double.isNaN(uvalue.doubleValue())) { 1082 maximum = Math.max(maximum, uvalue.doubleValue()); 1083 } 1084 } 1085 } 1086 } 1087 else if (includeInterval 1088 && dataset instanceof StatisticalCategoryDataset) { 1089 // handle the special case where the dataset has y-intervals that 1090 // we want to measure 1091 StatisticalCategoryDataset scd 1092 = (StatisticalCategoryDataset) dataset; 1093 Iterator iterator = visibleSeriesKeys.iterator(); 1094 while (iterator.hasNext()) { 1095 Comparable seriesKey = (Comparable) iterator.next(); 1096 int series = dataset.getRowIndex(seriesKey); 1097 for (int column = 0; column < columnCount; column++) { 1098 Number meanN = scd.getMeanValue(series, column); 1099 if (meanN != null) { 1100 double std = 0.0; 1101 Number stdN = scd.getStdDevValue(series, column); 1102 if (stdN != null) { 1103 std = stdN.doubleValue(); 1104 if (Double.isNaN(std)) { 1105 std = 0.0; 1106 } 1107 } 1108 double mean = meanN.doubleValue(); 1109 if (!Double.isNaN(mean)) { 1110 minimum = Math.min(minimum, mean - std); 1111 maximum = Math.max(maximum, mean + std); 1112 } 1113 } 1114 } 1115 } 1116 } 1117 else { 1118 // handle the standard case (plain CategoryDataset) 1119 Iterator iterator = visibleSeriesKeys.iterator(); 1120 while (iterator.hasNext()) { 1121 Comparable seriesKey = (Comparable) iterator.next(); 1122 int series = dataset.getRowIndex(seriesKey); 1123 for (int column = 0; column < columnCount; column++) { 1124 Number value = dataset.getValue(series, column); 1125 if (value != null) { 1126 double v = value.doubleValue(); 1127 if (!Double.isNaN(v)) { 1128 minimum = Math.min(minimum, v); 1129 maximum = Math.max(maximum, v); 1130 } 1131 } 1132 } 1133 } 1134 } 1135 if (minimum == Double.POSITIVE_INFINITY) { 1136 return null; 1137 } 1138 else { 1139 return new Range(minimum, maximum); 1140 } 1141 } 1142 1143 /** 1144 * Iterates over the data item of the xy dataset to find 1145 * the range bounds. 1146 * 1147 * @param dataset the dataset (<code>null</code> not permitted). 1148 * 1149 * @return The range (possibly <code>null</code>). 1150 * 1151 * @deprecated As of 1.0.10, use {@link #iterateRangeBounds(XYDataset)}. 1152 */ 1153 public static Range iterateXYRangeBounds(XYDataset dataset) { 1154 return iterateRangeBounds(dataset); 1155 } 1156 1157 /** 1158 * Iterates over the data item of the xy dataset to find 1159 * the range bounds. 1160 * 1161 * @param dataset the dataset (<code>null</code> not permitted). 1162 * 1163 * @return The range (possibly <code>null</code>). 1164 * 1165 * @since 1.0.10 1166 */ 1167 public static Range iterateRangeBounds(XYDataset dataset) { 1168 return iterateRangeBounds(dataset, true); 1169 } 1170 1171 /** 1172 * Iterates over the data items of the xy dataset to find 1173 * the range bounds. 1174 * 1175 * @param dataset the dataset (<code>null</code> not permitted). 1176 * @param includeInterval a flag that determines, for an 1177 * {@link IntervalXYDataset}, whether the y-interval or just the 1178 * y-value is used to determine the overall range. 1179 * 1180 * @return The range (possibly <code>null</code>). 1181 * 1182 * @since 1.0.10 1183 */ 1184 public static Range iterateRangeBounds(XYDataset dataset, 1185 boolean includeInterval) { 1186 double minimum = Double.POSITIVE_INFINITY; 1187 double maximum = Double.NEGATIVE_INFINITY; 1188 int seriesCount = dataset.getSeriesCount(); 1189 1190 // handle three cases by dataset type 1191 if (includeInterval && dataset instanceof IntervalXYDataset) { 1192 // handle special case of IntervalXYDataset 1193 IntervalXYDataset ixyd = (IntervalXYDataset) dataset; 1194 for (int series = 0; series < seriesCount; series++) { 1195 int itemCount = dataset.getItemCount(series); 1196 for (int item = 0; item < itemCount; item++) { 1197 double lvalue = ixyd.getStartYValue(series, item); 1198 double uvalue = ixyd.getEndYValue(series, item); 1199 if (!Double.isNaN(lvalue)) { 1200 minimum = Math.min(minimum, lvalue); 1201 } 1202 if (!Double.isNaN(uvalue)) { 1203 maximum = Math.max(maximum, uvalue); 1204 } 1205 } 1206 } 1207 } 1208 else if (includeInterval && dataset instanceof OHLCDataset) { 1209 // handle special case of OHLCDataset 1210 OHLCDataset ohlc = (OHLCDataset) dataset; 1211 for (int series = 0; series < seriesCount; series++) { 1212 int itemCount = dataset.getItemCount(series); 1213 for (int item = 0; item < itemCount; item++) { 1214 double lvalue = ohlc.getLowValue(series, item); 1215 double uvalue = ohlc.getHighValue(series, item); 1216 if (!Double.isNaN(lvalue)) { 1217 minimum = Math.min(minimum, lvalue); 1218 } 1219 if (!Double.isNaN(uvalue)) { 1220 maximum = Math.max(maximum, uvalue); 1221 } 1222 } 1223 } 1224 } 1225 else { 1226 // standard case - plain XYDataset 1227 for (int series = 0; series < seriesCount; series++) { 1228 int itemCount = dataset.getItemCount(series); 1229 for (int item = 0; item < itemCount; item++) { 1230 double value = dataset.getYValue(series, item); 1231 if (!Double.isNaN(value)) { 1232 minimum = Math.min(minimum, value); 1233 maximum = Math.max(maximum, value); 1234 } 1235 } 1236 } 1237 } 1238 if (minimum == Double.POSITIVE_INFINITY) { 1239 return null; 1240 } 1241 else { 1242 return new Range(minimum, maximum); 1243 } 1244 } 1245 1246 /** 1247 * Returns the range of x-values in the specified dataset for the 1248 * data items belonging to the visible series. 1249 * 1250 * @param dataset the dataset (<code>null</code> not permitted). 1251 * @param visibleSeriesKeys the visible series keys (<code>null</code> not 1252 * permitted). 1253 * @param includeInterval a flag that determines whether or not the 1254 * y-interval for the dataset is included (this only applies if the 1255 * dataset is an instance of IntervalXYDataset). 1256 * 1257 * @return The x-range (possibly <code>null</code>). 1258 * 1259 * @since 1.0.13 1260 */ 1261 public static Range iterateToFindDomainBounds(XYDataset dataset, 1262 List visibleSeriesKeys, boolean includeInterval) { 1263 1264 if (dataset == null) { 1265 throw new IllegalArgumentException("Null 'dataset' argument."); 1266 } 1267 if (visibleSeriesKeys == null) { 1268 throw new IllegalArgumentException( 1269 "Null 'visibleSeriesKeys' argument."); 1270 } 1271 1272 double minimum = Double.POSITIVE_INFINITY; 1273 double maximum = Double.NEGATIVE_INFINITY; 1274 1275 if (includeInterval && dataset instanceof IntervalXYDataset) { 1276 // handle special case of IntervalXYDataset 1277 IntervalXYDataset ixyd = (IntervalXYDataset) dataset; 1278 Iterator iterator = visibleSeriesKeys.iterator(); 1279 while (iterator.hasNext()) { 1280 Comparable seriesKey = (Comparable) iterator.next(); 1281 int series = dataset.indexOf(seriesKey); 1282 int itemCount = dataset.getItemCount(series); 1283 for (int item = 0; item < itemCount; item++) { 1284 double lvalue = ixyd.getStartXValue(series, item); 1285 double uvalue = ixyd.getEndXValue(series, item); 1286 if (!Double.isNaN(lvalue)) { 1287 minimum = Math.min(minimum, lvalue); 1288 } 1289 if (!Double.isNaN(uvalue)) { 1290 maximum = Math.max(maximum, uvalue); 1291 } 1292 } 1293 } 1294 } 1295 else { 1296 // standard case - plain XYDataset 1297 Iterator iterator = visibleSeriesKeys.iterator(); 1298 while (iterator.hasNext()) { 1299 Comparable seriesKey = (Comparable) iterator.next(); 1300 int series = dataset.indexOf(seriesKey); 1301 int itemCount = dataset.getItemCount(series); 1302 for (int item = 0; item < itemCount; item++) { 1303 double x = dataset.getXValue(series, item); 1304 if (!Double.isNaN(x)) { 1305 minimum = Math.min(minimum, x); 1306 maximum = Math.max(maximum, x); 1307 } 1308 } 1309 } 1310 } 1311 1312 if (minimum == Double.POSITIVE_INFINITY) { 1313 return null; 1314 } 1315 else { 1316 return new Range(minimum, maximum); 1317 } 1318 } 1319 1320 /** 1321 * Returns the range of y-values in the specified dataset for the 1322 * data items belonging to the visible series and with x-values in the 1323 * given range. 1324 * 1325 * @param dataset the dataset (<code>null</code> not permitted). 1326 * @param visibleSeriesKeys the visible series keys (<code>null</code> not 1327 * permitted). 1328 * @param xRange the x-range (<code>null</code> not permitted). 1329 * @param includeInterval a flag that determines whether or not the 1330 * y-interval for the dataset is included (this only applies if the 1331 * dataset is an instance of IntervalXYDataset). 1332 * 1333 * @return The y-range (possibly <code>null</code>). 1334 * 1335 * @since 1.0.13 1336 */ 1337 public static Range iterateToFindRangeBounds(XYDataset dataset, 1338 List visibleSeriesKeys, Range xRange, boolean includeInterval) { 1339 1340 if (dataset == null) { 1341 throw new IllegalArgumentException("Null 'dataset' argument."); 1342 } 1343 if (visibleSeriesKeys == null) { 1344 throw new IllegalArgumentException( 1345 "Null 'visibleSeriesKeys' argument."); 1346 } 1347 if (xRange == null) { 1348 throw new IllegalArgumentException("Null 'xRange' argument"); 1349 } 1350 1351 double minimum = Double.POSITIVE_INFINITY; 1352 double maximum = Double.NEGATIVE_INFINITY; 1353 1354 // handle three cases by dataset type 1355 if (includeInterval && dataset instanceof OHLCDataset) { 1356 // handle special case of OHLCDataset 1357 OHLCDataset ohlc = (OHLCDataset) dataset; 1358 Iterator iterator = visibleSeriesKeys.iterator(); 1359 while (iterator.hasNext()) { 1360 Comparable seriesKey = (Comparable) iterator.next(); 1361 int series = dataset.indexOf(seriesKey); 1362 int itemCount = dataset.getItemCount(series); 1363 for (int item = 0; item < itemCount; item++) { 1364 double x = ohlc.getXValue(series, item); 1365 if (xRange.contains(x)) { 1366 double lvalue = ohlc.getLowValue(series, item); 1367 double uvalue = ohlc.getHighValue(series, item); 1368 if (!Double.isNaN(lvalue)) { 1369 minimum = Math.min(minimum, lvalue); 1370 } 1371 if (!Double.isNaN(uvalue)) { 1372 maximum = Math.max(maximum, uvalue); 1373 } 1374 } 1375 } 1376 } 1377 } 1378 else if (includeInterval && dataset instanceof BoxAndWhiskerXYDataset) { 1379 // handle special case of BoxAndWhiskerXYDataset 1380 BoxAndWhiskerXYDataset bx = (BoxAndWhiskerXYDataset) dataset; 1381 Iterator iterator = visibleSeriesKeys.iterator(); 1382 while (iterator.hasNext()) { 1383 Comparable seriesKey = (Comparable) iterator.next(); 1384 int series = dataset.indexOf(seriesKey); 1385 int itemCount = dataset.getItemCount(series); 1386 for (int item = 0; item < itemCount; item++) { 1387 double x = bx.getXValue(series, item); 1388 if (xRange.contains(x)) { 1389 Number lvalue = bx.getMinRegularValue(series, item); 1390 Number uvalue = bx.getMaxRegularValue(series, item); 1391 if (lvalue != null) { 1392 minimum = Math.min(minimum, lvalue.doubleValue()); 1393 } 1394 if (uvalue != null) { 1395 maximum = Math.max(maximum, uvalue.doubleValue()); 1396 } 1397 } 1398 } 1399 } 1400 } 1401 else if (includeInterval && dataset instanceof IntervalXYDataset) { 1402 // handle special case of IntervalXYDataset 1403 IntervalXYDataset ixyd = (IntervalXYDataset) dataset; 1404 Iterator iterator = visibleSeriesKeys.iterator(); 1405 while (iterator.hasNext()) { 1406 Comparable seriesKey = (Comparable) iterator.next(); 1407 int series = dataset.indexOf(seriesKey); 1408 int itemCount = dataset.getItemCount(series); 1409 for (int item = 0; item < itemCount; item++) { 1410 double x = ixyd.getXValue(series, item); 1411 if (xRange.contains(x)) { 1412 double lvalue = ixyd.getStartYValue(series, item); 1413 double uvalue = ixyd.getEndYValue(series, item); 1414 if (!Double.isNaN(lvalue)) { 1415 minimum = Math.min(minimum, lvalue); 1416 } 1417 if (!Double.isNaN(uvalue)) { 1418 maximum = Math.max(maximum, uvalue); 1419 } 1420 } 1421 } 1422 } 1423 } 1424 else { 1425 // standard case - plain XYDataset 1426 Iterator iterator = visibleSeriesKeys.iterator(); 1427 while (iterator.hasNext()) { 1428 Comparable seriesKey = (Comparable) iterator.next(); 1429 int series = dataset.indexOf(seriesKey); 1430 int itemCount = dataset.getItemCount(series); 1431 for (int item = 0; item < itemCount; item++) { 1432 double x = dataset.getXValue(series, item); 1433 double y = dataset.getYValue(series, item); 1434 if (xRange.contains(x)) { 1435 if (!Double.isNaN(y)) { 1436 minimum = Math.min(minimum, y); 1437 maximum = Math.max(maximum, y); 1438 } 1439 } 1440 } 1441 } 1442 } 1443 if (minimum == Double.POSITIVE_INFINITY) { 1444 return null; 1445 } 1446 else { 1447 return new Range(minimum, maximum); 1448 } 1449 } 1450 1451 /** 1452 * Finds the minimum domain (or X) value for the specified dataset. This 1453 * is easy if the dataset implements the {@link DomainInfo} interface (a 1454 * good idea if there is an efficient way to determine the minimum value). 1455 * Otherwise, it involves iterating over the entire data-set. 1456 * <p> 1457 * Returns <code>null</code> if all the data values in the dataset are 1458 * <code>null</code>. 1459 * 1460 * @param dataset the dataset (<code>null</code> not permitted). 1461 * 1462 * @return The minimum value (possibly <code>null</code>). 1463 */ 1464 public static Number findMinimumDomainValue(XYDataset dataset) { 1465 if (dataset == null) { 1466 throw new IllegalArgumentException("Null 'dataset' argument."); 1467 } 1468 Number result = null; 1469 // if the dataset implements DomainInfo, life is easy 1470 if (dataset instanceof DomainInfo) { 1471 DomainInfo info = (DomainInfo) dataset; 1472 return new Double(info.getDomainLowerBound(true)); 1473 } 1474 else { 1475 double minimum = Double.POSITIVE_INFINITY; 1476 int seriesCount = dataset.getSeriesCount(); 1477 for (int series = 0; series < seriesCount; series++) { 1478 int itemCount = dataset.getItemCount(series); 1479 for (int item = 0; item < itemCount; item++) { 1480 1481 double value; 1482 if (dataset instanceof IntervalXYDataset) { 1483 IntervalXYDataset intervalXYData 1484 = (IntervalXYDataset) dataset; 1485 value = intervalXYData.getStartXValue(series, item); 1486 } 1487 else { 1488 value = dataset.getXValue(series, item); 1489 } 1490 if (!Double.isNaN(value)) { 1491 minimum = Math.min(minimum, value); 1492 } 1493 1494 } 1495 } 1496 if (minimum == Double.POSITIVE_INFINITY) { 1497 result = null; 1498 } 1499 else { 1500 result = new Double(minimum); 1501 } 1502 } 1503 1504 return result; 1505 } 1506 1507 /** 1508 * Returns the maximum domain value for the specified dataset. This is 1509 * easy if the dataset implements the {@link DomainInfo} interface (a good 1510 * idea if there is an efficient way to determine the maximum value). 1511 * Otherwise, it involves iterating over the entire data-set. Returns 1512 * <code>null</code> if all the data values in the dataset are 1513 * <code>null</code>. 1514 * 1515 * @param dataset the dataset (<code>null</code> not permitted). 1516 * 1517 * @return The maximum value (possibly <code>null</code>). 1518 */ 1519 public static Number findMaximumDomainValue(XYDataset dataset) { 1520 if (dataset == null) { 1521 throw new IllegalArgumentException("Null 'dataset' argument."); 1522 } 1523 Number result = null; 1524 // if the dataset implements DomainInfo, life is easy 1525 if (dataset instanceof DomainInfo) { 1526 DomainInfo info = (DomainInfo) dataset; 1527 return new Double(info.getDomainUpperBound(true)); 1528 } 1529 1530 // hasn't implemented DomainInfo, so iterate... 1531 else { 1532 double maximum = Double.NEGATIVE_INFINITY; 1533 int seriesCount = dataset.getSeriesCount(); 1534 for (int series = 0; series < seriesCount; series++) { 1535 int itemCount = dataset.getItemCount(series); 1536 for (int item = 0; item < itemCount; item++) { 1537 1538 double value; 1539 if (dataset instanceof IntervalXYDataset) { 1540 IntervalXYDataset intervalXYData 1541 = (IntervalXYDataset) dataset; 1542 value = intervalXYData.getEndXValue(series, item); 1543 } 1544 else { 1545 value = dataset.getXValue(series, item); 1546 } 1547 if (!Double.isNaN(value)) { 1548 maximum = Math.max(maximum, value); 1549 } 1550 } 1551 } 1552 if (maximum == Double.NEGATIVE_INFINITY) { 1553 result = null; 1554 } 1555 else { 1556 result = new Double(maximum); 1557 } 1558 1559 } 1560 1561 return result; 1562 } 1563 1564 /** 1565 * Returns the minimum range value for the specified dataset. This is 1566 * easy if the dataset implements the {@link RangeInfo} interface (a good 1567 * idea if there is an efficient way to determine the minimum value). 1568 * Otherwise, it involves iterating over the entire data-set. Returns 1569 * <code>null</code> if all the data values in the dataset are 1570 * <code>null</code>. 1571 * 1572 * @param dataset the dataset (<code>null</code> not permitted). 1573 * 1574 * @return The minimum value (possibly <code>null</code>). 1575 */ 1576 public static Number findMinimumRangeValue(CategoryDataset dataset) { 1577 1578 if (dataset == null) { 1579 throw new IllegalArgumentException("Null 'dataset' argument."); 1580 } 1581 1582 if (dataset instanceof RangeInfo) { 1583 RangeInfo info = (RangeInfo) dataset; 1584 return new Double(info.getRangeLowerBound(true)); 1585 } 1586 1587 // hasn't implemented RangeInfo, so we'll have to iterate... 1588 else { 1589 double minimum = Double.POSITIVE_INFINITY; 1590 int seriesCount = dataset.getRowCount(); 1591 int itemCount = dataset.getColumnCount(); 1592 for (int series = 0; series < seriesCount; series++) { 1593 for (int item = 0; item < itemCount; item++) { 1594 Number value; 1595 if (dataset instanceof IntervalCategoryDataset) { 1596 IntervalCategoryDataset icd 1597 = (IntervalCategoryDataset) dataset; 1598 value = icd.getStartValue(series, item); 1599 } 1600 else { 1601 value = dataset.getValue(series, item); 1602 } 1603 if (value != null) { 1604 minimum = Math.min(minimum, value.doubleValue()); 1605 } 1606 } 1607 } 1608 if (minimum == Double.POSITIVE_INFINITY) { 1609 return null; 1610 } 1611 else { 1612 return new Double(minimum); 1613 } 1614 1615 } 1616 1617 } 1618 1619 /** 1620 * Returns the minimum range value for the specified dataset. This is 1621 * easy if the dataset implements the {@link RangeInfo} interface (a good 1622 * idea if there is an efficient way to determine the minimum value). 1623 * Otherwise, it involves iterating over the entire data-set. Returns 1624 * <code>null</code> if all the data values in the dataset are 1625 * <code>null</code>. 1626 * 1627 * @param dataset the dataset (<code>null</code> not permitted). 1628 * 1629 * @return The minimum value (possibly <code>null</code>). 1630 */ 1631 public static Number findMinimumRangeValue(XYDataset dataset) { 1632 1633 if (dataset == null) { 1634 throw new IllegalArgumentException("Null 'dataset' argument."); 1635 } 1636 1637 // work out the minimum value... 1638 if (dataset instanceof RangeInfo) { 1639 RangeInfo info = (RangeInfo) dataset; 1640 return new Double(info.getRangeLowerBound(true)); 1641 } 1642 1643 // hasn't implemented RangeInfo, so we'll have to iterate... 1644 else { 1645 double minimum = Double.POSITIVE_INFINITY; 1646 int seriesCount = dataset.getSeriesCount(); 1647 for (int series = 0; series < seriesCount; series++) { 1648 int itemCount = dataset.getItemCount(series); 1649 for (int item = 0; item < itemCount; item++) { 1650 1651 double value; 1652 if (dataset instanceof IntervalXYDataset) { 1653 IntervalXYDataset intervalXYData 1654 = (IntervalXYDataset) dataset; 1655 value = intervalXYData.getStartYValue(series, item); 1656 } 1657 else if (dataset instanceof OHLCDataset) { 1658 OHLCDataset highLowData = (OHLCDataset) dataset; 1659 value = highLowData.getLowValue(series, item); 1660 } 1661 else { 1662 value = dataset.getYValue(series, item); 1663 } 1664 if (!Double.isNaN(value)) { 1665 minimum = Math.min(minimum, value); 1666 } 1667 1668 } 1669 } 1670 if (minimum == Double.POSITIVE_INFINITY) { 1671 return null; 1672 } 1673 else { 1674 return new Double(minimum); 1675 } 1676 1677 } 1678 1679 } 1680 1681 /** 1682 * Returns the maximum range value for the specified dataset. This is easy 1683 * if the dataset implements the {@link RangeInfo} interface (a good idea 1684 * if there is an efficient way to determine the maximum value). 1685 * Otherwise, it involves iterating over the entire data-set. Returns 1686 * <code>null</code> if all the data values are <code>null</code>. 1687 * 1688 * @param dataset the dataset (<code>null</code> not permitted). 1689 * 1690 * @return The maximum value (possibly <code>null</code>). 1691 */ 1692 public static Number findMaximumRangeValue(CategoryDataset dataset) { 1693 1694 if (dataset == null) { 1695 throw new IllegalArgumentException("Null 'dataset' argument."); 1696 } 1697 1698 // work out the minimum value... 1699 if (dataset instanceof RangeInfo) { 1700 RangeInfo info = (RangeInfo) dataset; 1701 return new Double(info.getRangeUpperBound(true)); 1702 } 1703 1704 // hasn't implemented RangeInfo, so we'll have to iterate... 1705 else { 1706 1707 double maximum = Double.NEGATIVE_INFINITY; 1708 int seriesCount = dataset.getRowCount(); 1709 int itemCount = dataset.getColumnCount(); 1710 for (int series = 0; series < seriesCount; series++) { 1711 for (int item = 0; item < itemCount; item++) { 1712 Number value; 1713 if (dataset instanceof IntervalCategoryDataset) { 1714 IntervalCategoryDataset icd 1715 = (IntervalCategoryDataset) dataset; 1716 value = icd.getEndValue(series, item); 1717 } 1718 else { 1719 value = dataset.getValue(series, item); 1720 } 1721 if (value != null) { 1722 maximum = Math.max(maximum, value.doubleValue()); 1723 } 1724 } 1725 } 1726 if (maximum == Double.NEGATIVE_INFINITY) { 1727 return null; 1728 } 1729 else { 1730 return new Double(maximum); 1731 } 1732 1733 } 1734 1735 } 1736 1737 /** 1738 * Returns the maximum range value for the specified dataset. This is 1739 * easy if the dataset implements the {@link RangeInfo} interface (a good 1740 * idea if there is an efficient way to determine the maximum value). 1741 * Otherwise, it involves iterating over the entire data-set. Returns 1742 * <code>null</code> if all the data values are <code>null</code>. 1743 * 1744 * @param dataset the dataset (<code>null</code> not permitted). 1745 * 1746 * @return The maximum value (possibly <code>null</code>). 1747 */ 1748 public static Number findMaximumRangeValue(XYDataset dataset) { 1749 1750 if (dataset == null) { 1751 throw new IllegalArgumentException("Null 'dataset' argument."); 1752 } 1753 1754 // work out the minimum value... 1755 if (dataset instanceof RangeInfo) { 1756 RangeInfo info = (RangeInfo) dataset; 1757 return new Double(info.getRangeUpperBound(true)); 1758 } 1759 1760 // hasn't implemented RangeInfo, so we'll have to iterate... 1761 else { 1762 1763 double maximum = Double.NEGATIVE_INFINITY; 1764 int seriesCount = dataset.getSeriesCount(); 1765 for (int series = 0; series < seriesCount; series++) { 1766 int itemCount = dataset.getItemCount(series); 1767 for (int item = 0; item < itemCount; item++) { 1768 double value; 1769 if (dataset instanceof IntervalXYDataset) { 1770 IntervalXYDataset intervalXYData 1771 = (IntervalXYDataset) dataset; 1772 value = intervalXYData.getEndYValue(series, item); 1773 } 1774 else if (dataset instanceof OHLCDataset) { 1775 OHLCDataset highLowData = (OHLCDataset) dataset; 1776 value = highLowData.getHighValue(series, item); 1777 } 1778 else { 1779 value = dataset.getYValue(series, item); 1780 } 1781 if (!Double.isNaN(value)) { 1782 maximum = Math.max(maximum, value); 1783 } 1784 } 1785 } 1786 if (maximum == Double.NEGATIVE_INFINITY) { 1787 return null; 1788 } 1789 else { 1790 return new Double(maximum); 1791 } 1792 1793 } 1794 1795 } 1796 1797 /** 1798 * Returns the minimum and maximum values for the dataset's range 1799 * (y-values), assuming that the series in one category are stacked. 1800 * 1801 * @param dataset the dataset (<code>null</code> not permitted). 1802 * 1803 * @return The range (<code>null</code> if the dataset contains no values). 1804 */ 1805 public static Range findStackedRangeBounds(CategoryDataset dataset) { 1806 return findStackedRangeBounds(dataset, 0.0); 1807 } 1808 1809 /** 1810 * Returns the minimum and maximum values for the dataset's range 1811 * (y-values), assuming that the series in one category are stacked. 1812 * 1813 * @param dataset the dataset (<code>null</code> not permitted). 1814 * @param base the base value for the bars. 1815 * 1816 * @return The range (<code>null</code> if the dataset contains no values). 1817 */ 1818 public static Range findStackedRangeBounds(CategoryDataset dataset, 1819 double base) { 1820 if (dataset == null) { 1821 throw new IllegalArgumentException("Null 'dataset' argument."); 1822 } 1823 Range result = null; 1824 double minimum = Double.POSITIVE_INFINITY; 1825 double maximum = Double.NEGATIVE_INFINITY; 1826 int categoryCount = dataset.getColumnCount(); 1827 for (int item = 0; item < categoryCount; item++) { 1828 double positive = base; 1829 double negative = base; 1830 int seriesCount = dataset.getRowCount(); 1831 for (int series = 0; series < seriesCount; series++) { 1832 Number number = dataset.getValue(series, item); 1833 if (number != null) { 1834 double value = number.doubleValue(); 1835 if (value > 0.0) { 1836 positive = positive + value; 1837 } 1838 if (value < 0.0) { 1839 negative = negative + value; 1840 // '+', remember value is negative 1841 } 1842 } 1843 } 1844 minimum = Math.min(minimum, negative); 1845 maximum = Math.max(maximum, positive); 1846 } 1847 if (minimum <= maximum) { 1848 result = new Range(minimum, maximum); 1849 } 1850 return result; 1851 1852 } 1853 1854 /** 1855 * Returns the minimum and maximum values for the dataset's range 1856 * (y-values), assuming that the series in one category are stacked. 1857 * 1858 * @param dataset the dataset. 1859 * @param map a structure that maps series to groups. 1860 * 1861 * @return The value range (<code>null</code> if the dataset contains no 1862 * values). 1863 */ 1864 public static Range findStackedRangeBounds(CategoryDataset dataset, 1865 KeyToGroupMap map) { 1866 if (dataset == null) { 1867 throw new IllegalArgumentException("Null 'dataset' argument."); 1868 } 1869 boolean hasValidData = false; 1870 Range result = null; 1871 1872 // create an array holding the group indices for each series... 1873 int[] groupIndex = new int[dataset.getRowCount()]; 1874 for (int i = 0; i < dataset.getRowCount(); i++) { 1875 groupIndex[i] = map.getGroupIndex(map.getGroup( 1876 dataset.getRowKey(i))); 1877 } 1878 1879 // minimum and maximum for each group... 1880 int groupCount = map.getGroupCount(); 1881 double[] minimum = new double[groupCount]; 1882 double[] maximum = new double[groupCount]; 1883 1884 int categoryCount = dataset.getColumnCount(); 1885 for (int item = 0; item < categoryCount; item++) { 1886 double[] positive = new double[groupCount]; 1887 double[] negative = new double[groupCount]; 1888 int seriesCount = dataset.getRowCount(); 1889 for (int series = 0; series < seriesCount; series++) { 1890 Number number = dataset.getValue(series, item); 1891 if (number != null) { 1892 hasValidData = true; 1893 double value = number.doubleValue(); 1894 if (value > 0.0) { 1895 positive[groupIndex[series]] 1896 = positive[groupIndex[series]] + value; 1897 } 1898 if (value < 0.0) { 1899 negative[groupIndex[series]] 1900 = negative[groupIndex[series]] + value; 1901 // '+', remember value is negative 1902 } 1903 } 1904 } 1905 for (int g = 0; g < groupCount; g++) { 1906 minimum[g] = Math.min(minimum[g], negative[g]); 1907 maximum[g] = Math.max(maximum[g], positive[g]); 1908 } 1909 } 1910 if (hasValidData) { 1911 for (int j = 0; j < groupCount; j++) { 1912 result = Range.combine(result, new Range(minimum[j], 1913 maximum[j])); 1914 } 1915 } 1916 return result; 1917 } 1918 1919 /** 1920 * Returns the minimum value in the dataset range, assuming that values in 1921 * each category are "stacked". 1922 * 1923 * @param dataset the dataset (<code>null</code> not permitted). 1924 * 1925 * @return The minimum value. 1926 * 1927 * @see #findMaximumStackedRangeValue(CategoryDataset) 1928 */ 1929 public static Number findMinimumStackedRangeValue(CategoryDataset dataset) { 1930 if (dataset == null) { 1931 throw new IllegalArgumentException("Null 'dataset' argument."); 1932 } 1933 Number result = null; 1934 boolean hasValidData = false; 1935 double minimum = 0.0; 1936 int categoryCount = dataset.getColumnCount(); 1937 for (int item = 0; item < categoryCount; item++) { 1938 double total = 0.0; 1939 int seriesCount = dataset.getRowCount(); 1940 for (int series = 0; series < seriesCount; series++) { 1941 Number number = dataset.getValue(series, item); 1942 if (number != null) { 1943 hasValidData = true; 1944 double value = number.doubleValue(); 1945 if (value < 0.0) { 1946 total = total + value; 1947 // '+', remember value is negative 1948 } 1949 } 1950 } 1951 minimum = Math.min(minimum, total); 1952 } 1953 if (hasValidData) { 1954 result = new Double(minimum); 1955 } 1956 return result; 1957 } 1958 1959 /** 1960 * Returns the maximum value in the dataset range, assuming that values in 1961 * each category are "stacked". 1962 * 1963 * @param dataset the dataset (<code>null</code> not permitted). 1964 * 1965 * @return The maximum value (possibly <code>null</code>). 1966 * 1967 * @see #findMinimumStackedRangeValue(CategoryDataset) 1968 */ 1969 public static Number findMaximumStackedRangeValue(CategoryDataset dataset) { 1970 if (dataset == null) { 1971 throw new IllegalArgumentException("Null 'dataset' argument."); 1972 } 1973 Number result = null; 1974 boolean hasValidData = false; 1975 double maximum = 0.0; 1976 int categoryCount = dataset.getColumnCount(); 1977 for (int item = 0; item < categoryCount; item++) { 1978 double total = 0.0; 1979 int seriesCount = dataset.getRowCount(); 1980 for (int series = 0; series < seriesCount; series++) { 1981 Number number = dataset.getValue(series, item); 1982 if (number != null) { 1983 hasValidData = true; 1984 double value = number.doubleValue(); 1985 if (value > 0.0) { 1986 total = total + value; 1987 } 1988 } 1989 } 1990 maximum = Math.max(maximum, total); 1991 } 1992 if (hasValidData) { 1993 result = new Double(maximum); 1994 } 1995 return result; 1996 } 1997 1998 /** 1999 * Returns the minimum and maximum values for the dataset's range, 2000 * assuming that the series are stacked. 2001 * 2002 * @param dataset the dataset (<code>null</code> not permitted). 2003 * 2004 * @return The range ([0.0, 0.0] if the dataset contains no values). 2005 */ 2006 public static Range findStackedRangeBounds(TableXYDataset dataset) { 2007 return findStackedRangeBounds(dataset, 0.0); 2008 } 2009 2010 /** 2011 * Returns the minimum and maximum values for the dataset's range, 2012 * assuming that the series are stacked, using the specified base value. 2013 * 2014 * @param dataset the dataset (<code>null</code> not permitted). 2015 * @param base the base value. 2016 * 2017 * @return The range (<code>null</code> if the dataset contains no values). 2018 */ 2019 public static Range findStackedRangeBounds(TableXYDataset dataset, 2020 double base) { 2021 if (dataset == null) { 2022 throw new IllegalArgumentException("Null 'dataset' argument."); 2023 } 2024 double minimum = base; 2025 double maximum = base; 2026 for (int itemNo = 0; itemNo < dataset.getItemCount(); itemNo++) { 2027 double positive = base; 2028 double negative = base; 2029 int seriesCount = dataset.getSeriesCount(); 2030 for (int seriesNo = 0; seriesNo < seriesCount; seriesNo++) { 2031 double y = dataset.getYValue(seriesNo, itemNo); 2032 if (!Double.isNaN(y)) { 2033 if (y > 0.0) { 2034 positive += y; 2035 } 2036 else { 2037 negative += y; 2038 } 2039 } 2040 } 2041 if (positive > maximum) { 2042 maximum = positive; 2043 } 2044 if (negative < minimum) { 2045 minimum = negative; 2046 } 2047 } 2048 if (minimum <= maximum) { 2049 return new Range(minimum, maximum); 2050 } 2051 else { 2052 return null; 2053 } 2054 } 2055 2056 /** 2057 * Calculates the total for the y-values in all series for a given item 2058 * index. 2059 * 2060 * @param dataset the dataset. 2061 * @param item the item index. 2062 * 2063 * @return The total. 2064 * 2065 * @since 1.0.5 2066 */ 2067 public static double calculateStackTotal(TableXYDataset dataset, int item) { 2068 double total = 0.0; 2069 int seriesCount = dataset.getSeriesCount(); 2070 for (int s = 0; s < seriesCount; s++) { 2071 double value = dataset.getYValue(s, item); 2072 if (!Double.isNaN(value)) { 2073 total = total + value; 2074 } 2075 } 2076 return total; 2077 } 2078 2079 /** 2080 * Calculates the range of values for a dataset where each item is the 2081 * running total of the items for the current series. 2082 * 2083 * @param dataset the dataset (<code>null</code> not permitted). 2084 * 2085 * @return The range. 2086 * 2087 * @see #findRangeBounds(CategoryDataset) 2088 */ 2089 public static Range findCumulativeRangeBounds(CategoryDataset dataset) { 2090 if (dataset == null) { 2091 throw new IllegalArgumentException("Null 'dataset' argument."); 2092 } 2093 boolean allItemsNull = true; // we'll set this to false if there is at 2094 // least one non-null data item... 2095 double minimum = 0.0; 2096 double maximum = 0.0; 2097 for (int row = 0; row < dataset.getRowCount(); row++) { 2098 double runningTotal = 0.0; 2099 for (int column = 0; column <= dataset.getColumnCount() - 1; 2100 column++) { 2101 Number n = dataset.getValue(row, column); 2102 if (n != null) { 2103 allItemsNull = false; 2104 double value = n.doubleValue(); 2105 if (!Double.isNaN(value)) { 2106 runningTotal = runningTotal + value; 2107 minimum = Math.min(minimum, runningTotal); 2108 maximum = Math.max(maximum, runningTotal); 2109 } 2110 } 2111 } 2112 } 2113 if (!allItemsNull) { 2114 return new Range(minimum, maximum); 2115 } 2116 else { 2117 return null; 2118 } 2119 } 2120 2121 }