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