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