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