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