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