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