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