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 * DefaultStatisticalCategoryDataset.java 029 * -------------------------------------- 030 * (C) Copyright 2002-2011, by Pascal Collet and Contributors. 031 * 032 * Original Author: Pascal Collet; 033 * Contributor(s): David Gilbert; 034 * 035 * Changes 036 * ------- 037 * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG); 038 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 039 * 05-Feb-2003 : Revised implementation to use KeyedObjects2D (DG); 040 * 28-Aug-2003 : Moved from org.jfree.data --> org.jfree.data.statistics (DG); 041 * 06-Oct-2003 : Removed incorrect Javadoc text (DG); 042 * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG); 043 * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 044 * release (DG); 045 * 01-Feb-2005 : Changed minimumRangeValue and maximumRangeValue from Double 046 * to double (DG); 047 * 05-Feb-2005 : Implemented equals() method (DG); 048 * ------------- JFREECHART 1.0.x --------------------------------------------- 049 * 08-Aug-2006 : Reworked implementation of RangeInfo methods (DG); 050 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG); 051 * 28-Sep-2007 : Fixed cloning bug (DG); 052 * 02-Oct-2007 : Fixed bug updating cached range values (DG); 053 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG); 054 * 20-Oct-2011 : Fixed getRangeBounds() bug 3072674 (DG); 055 * 19-Jan-2019 : Added missing hashCode (TH); 056 * 057 */ 058 059package org.jfree.data.statistics; 060 061import java.util.List; 062import java.util.Objects; 063import org.jfree.chart.internal.CloneUtils; 064import org.jfree.chart.api.PublicCloneable; 065 066import org.jfree.data.KeyedObjects2D; 067import org.jfree.data.Range; 068import org.jfree.data.RangeInfo; 069import org.jfree.data.general.AbstractDataset; 070import org.jfree.data.general.DatasetChangeEvent; 071 072/** 073 * A convenience class that provides a default implementation of the 074 * {@link StatisticalCategoryDataset} interface. 075 */ 076public class DefaultStatisticalCategoryDataset<R extends Comparable<R>, 077 C extends Comparable<C>> extends AbstractDataset 078 implements StatisticalCategoryDataset<R, C>, RangeInfo, PublicCloneable { 079 080 /** Storage for the data. */ 081 private KeyedObjects2D<R, C> data; 082 083 /** The minimum range value. */ 084 private double minimumRangeValue; 085 086 /** The row index for the minimum range value. */ 087 private int minimumRangeValueRow; 088 089 /** The column index for the minimum range value. */ 090 private int minimumRangeValueColumn; 091 092 /** The minimum range value including the standard deviation. */ 093 private double minimumRangeValueIncStdDev; 094 095 /** 096 * The row index for the minimum range value (including the standard 097 * deviation). 098 */ 099 private int minimumRangeValueIncStdDevRow; 100 101 /** 102 * The column index for the minimum range value (including the standard 103 * deviation). 104 */ 105 private int minimumRangeValueIncStdDevColumn; 106 107 /** The maximum range value. */ 108 private double maximumRangeValue; 109 110 /** The row index for the maximum range value. */ 111 private int maximumRangeValueRow; 112 113 /** The column index for the maximum range value. */ 114 private int maximumRangeValueColumn; 115 116 /** The maximum range value including the standard deviation. */ 117 private double maximumRangeValueIncStdDev; 118 119 /** 120 * The row index for the maximum range value (including the standard 121 * deviation). 122 */ 123 private int maximumRangeValueIncStdDevRow; 124 125 /** 126 * The column index for the maximum range value (including the standard 127 * deviation). 128 */ 129 private int maximumRangeValueIncStdDevColumn; 130 131 /** 132 * Creates a new dataset. 133 */ 134 public DefaultStatisticalCategoryDataset() { 135 this.data = new KeyedObjects2D<>(); 136 this.minimumRangeValue = Double.NaN; 137 this.minimumRangeValueRow = -1; 138 this.minimumRangeValueColumn = -1; 139 this.maximumRangeValue = Double.NaN; 140 this.maximumRangeValueRow = -1; 141 this.maximumRangeValueColumn = -1; 142 this.minimumRangeValueIncStdDev = Double.NaN; 143 this.minimumRangeValueIncStdDevRow = -1; 144 this.minimumRangeValueIncStdDevColumn = -1; 145 this.maximumRangeValueIncStdDev = Double.NaN; 146 this.maximumRangeValueIncStdDevRow = -1; 147 this.maximumRangeValueIncStdDevColumn = -1; 148 } 149 150 /** 151 * Returns the mean value for an item. 152 * 153 * @param row the row index (zero-based). 154 * @param column the column index (zero-based). 155 * 156 * @return The mean value (possibly {@code null}). 157 */ 158 @Override 159 public Number getMeanValue(int row, int column) { 160 Number result = null; 161 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 162 this.data.getObject(row, column); 163 if (masd != null) { 164 result = masd.getMean(); 165 } 166 return result; 167 } 168 169 /** 170 * Returns the value for an item (for this dataset, the mean value is 171 * returned). 172 * 173 * @param row the row index. 174 * @param column the column index. 175 * 176 * @return The value (possibly {@code null}). 177 */ 178 @Override 179 public Number getValue(int row, int column) { 180 return getMeanValue(row, column); 181 } 182 183 /** 184 * Returns the value for an item (for this dataset, the mean value is 185 * returned). 186 * 187 * @param rowKey the row key. 188 * @param columnKey the columnKey. 189 * 190 * @return The value (possibly {@code null}). 191 */ 192 @Override 193 public Number getValue(R rowKey, C columnKey) { 194 return getMeanValue(rowKey, columnKey); 195 } 196 197 /** 198 * Returns the mean value for an item. 199 * 200 * @param rowKey the row key. 201 * @param columnKey the columnKey. 202 * 203 * @return The mean value (possibly {@code null}). 204 */ 205 @Override 206 public Number getMeanValue(R rowKey, C columnKey) { 207 Number result = null; 208 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 209 this.data.getObject(rowKey, columnKey); 210 if (masd != null) { 211 result = masd.getMean(); 212 } 213 return result; 214 } 215 216 /** 217 * Returns the standard deviation value for an item. 218 * 219 * @param row the row index (zero-based). 220 * @param column the column index (zero-based). 221 * 222 * @return The standard deviation (possibly {@code null}). 223 */ 224 @Override 225 public Number getStdDevValue(int row, int column) { 226 Number result = null; 227 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 228 this.data.getObject(row, column); 229 if (masd != null) { 230 result = masd.getStandardDeviation(); 231 } 232 return result; 233 } 234 235 /** 236 * Returns the standard deviation value for an item. 237 * 238 * @param rowKey the row key. 239 * @param columnKey the columnKey. 240 * 241 * @return The standard deviation (possibly {@code null}). 242 */ 243 @Override 244 public Number getStdDevValue(R rowKey, C columnKey) { 245 Number result = null; 246 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 247 this.data.getObject(rowKey, columnKey); 248 if (masd != null) { 249 result = masd.getStandardDeviation(); 250 } 251 return result; 252 } 253 254 /** 255 * Returns the column index for a given key. 256 * 257 * @param key the column key ({@code null} not permitted). 258 * 259 * @return The column index. 260 */ 261 @Override 262 public int getColumnIndex(C key) { 263 // defer null argument check 264 return this.data.getColumnIndex(key); 265 } 266 267 /** 268 * Returns a column key. 269 * 270 * @param column the column index (zero-based). 271 * 272 * @return The column key. 273 */ 274 @Override 275 public C getColumnKey(int column) { 276 return this.data.getColumnKey(column); 277 } 278 279 /** 280 * Returns the column keys. 281 * 282 * @return The keys. 283 */ 284 @Override 285 public List<C> getColumnKeys() { 286 return this.data.getColumnKeys(); 287 } 288 289 /** 290 * Returns the row index for a given key. 291 * 292 * @param key the row key ({@code null} not permitted). 293 * 294 * @return The row index. 295 */ 296 @Override 297 public int getRowIndex(R key) { 298 // defer null argument check 299 return this.data.getRowIndex(key); 300 } 301 302 /** 303 * Returns a row key. 304 * 305 * @param row the row index (zero-based). 306 * 307 * @return The row key. 308 */ 309 @Override 310 public R getRowKey(int row) { 311 return this.data.getRowKey(row); 312 } 313 314 /** 315 * Returns the row keys. 316 * 317 * @return The keys. 318 */ 319 @Override 320 public List<R> getRowKeys() { 321 return this.data.getRowKeys(); 322 } 323 324 /** 325 * Returns the number of rows in the table. 326 * 327 * @return The row count. 328 * 329 * @see #getColumnCount() 330 */ 331 @Override 332 public int getRowCount() { 333 return this.data.getRowCount(); 334 } 335 336 /** 337 * Returns the number of columns in the table. 338 * 339 * @return The column count. 340 * 341 * @see #getRowCount() 342 */ 343 @Override 344 public int getColumnCount() { 345 return this.data.getColumnCount(); 346 } 347 348 /** 349 * Adds a mean and standard deviation to the table. 350 * 351 * @param mean the mean. 352 * @param standardDeviation the standard deviation. 353 * @param rowKey the row key. 354 * @param columnKey the column key. 355 */ 356 public void add(double mean, double standardDeviation, R rowKey, 357 C columnKey) { 358 add(Double.valueOf(mean), Double.valueOf(standardDeviation), rowKey, 359 columnKey); 360 } 361 362 /** 363 * Adds a mean and standard deviation to the table. 364 * 365 * @param mean the mean. 366 * @param standardDeviation the standard deviation. 367 * @param rowKey the row key. 368 * @param columnKey the column key. 369 */ 370 public void add(Number mean, Number standardDeviation, 371 R rowKey, C columnKey) { 372 MeanAndStandardDeviation item = new MeanAndStandardDeviation( 373 mean, standardDeviation); 374 this.data.addObject(item, rowKey, columnKey); 375 376 double m = Double.NaN; 377 double sd = Double.NaN; 378 if (mean != null) { 379 m = mean.doubleValue(); 380 } 381 if (standardDeviation != null) { 382 sd = standardDeviation.doubleValue(); 383 } 384 385 // update cached range values 386 int r = this.data.getColumnIndex(columnKey); 387 int c = this.data.getRowIndex(rowKey); 388 if ((r == this.maximumRangeValueRow && c 389 == this.maximumRangeValueColumn) || (r 390 == this.maximumRangeValueIncStdDevRow && c 391 == this.maximumRangeValueIncStdDevColumn) || (r 392 == this.minimumRangeValueRow && c 393 == this.minimumRangeValueColumn) || (r 394 == this.minimumRangeValueIncStdDevRow && c 395 == this.minimumRangeValueIncStdDevColumn)) { 396 397 // iterate over all data items and update mins and maxes 398 updateBounds(); 399 } 400 else { 401 if (!Double.isNaN(m)) { 402 if (Double.isNaN(this.maximumRangeValue) 403 || m > this.maximumRangeValue) { 404 this.maximumRangeValue = m; 405 this.maximumRangeValueRow = r; 406 this.maximumRangeValueColumn = c; 407 } 408 } 409 410 if (!Double.isNaN(m + sd)) { 411 if (Double.isNaN(this.maximumRangeValueIncStdDev) 412 || (m + sd) > this.maximumRangeValueIncStdDev) { 413 this.maximumRangeValueIncStdDev = m + sd; 414 this.maximumRangeValueIncStdDevRow = r; 415 this.maximumRangeValueIncStdDevColumn = c; 416 } 417 } 418 419 if (!Double.isNaN(m)) { 420 if (Double.isNaN(this.minimumRangeValue) 421 || m < this.minimumRangeValue) { 422 this.minimumRangeValue = m; 423 this.minimumRangeValueRow = r; 424 this.minimumRangeValueColumn = c; 425 } 426 } 427 428 if (!Double.isNaN(m - sd)) { 429 if (Double.isNaN(this.minimumRangeValueIncStdDev) 430 || (m - sd) < this.minimumRangeValueIncStdDev) { 431 this.minimumRangeValueIncStdDev = m - sd; 432 this.minimumRangeValueIncStdDevRow = r; 433 this.minimumRangeValueIncStdDevColumn = c; 434 } 435 } 436 } 437 fireDatasetChanged(); 438 } 439 440 /** 441 * Removes an item from the dataset and sends a {@link DatasetChangeEvent} 442 * to all registered listeners. 443 * 444 * @param rowKey the row key ({@code null} not permitted). 445 * @param columnKey the column key ({@code null} not permitted). 446 * 447 * @see #add(double, double, Comparable, Comparable) 448 * 449 * @since 1.0.7 450 */ 451 public void remove(R rowKey, C columnKey) { 452 // defer null argument checks 453 int r = getRowIndex(rowKey); 454 int c = getColumnIndex(columnKey); 455 this.data.removeObject(rowKey, columnKey); 456 457 // if this cell held a maximum and/or minimum value, we'll need to 458 // update the cached bounds... 459 if ((r == this.maximumRangeValueRow && c 460 == this.maximumRangeValueColumn) || (r 461 == this.maximumRangeValueIncStdDevRow && c 462 == this.maximumRangeValueIncStdDevColumn) || (r 463 == this.minimumRangeValueRow && c 464 == this.minimumRangeValueColumn) || (r 465 == this.minimumRangeValueIncStdDevRow && c 466 == this.minimumRangeValueIncStdDevColumn)) { 467 468 // iterate over all data items and update mins and maxes 469 updateBounds(); 470 } 471 472 fireDatasetChanged(); 473 } 474 475 476 /** 477 * Removes a row from the dataset and sends a {@link DatasetChangeEvent} 478 * to all registered listeners. 479 * 480 * @param rowIndex the row index. 481 * 482 * @see #removeColumn(int) 483 * 484 * @since 1.0.7 485 */ 486 public void removeRow(int rowIndex) { 487 this.data.removeRow(rowIndex); 488 updateBounds(); 489 fireDatasetChanged(); 490 } 491 492 /** 493 * Removes a row from the dataset and sends a {@link DatasetChangeEvent} 494 * to all registered listeners. 495 * 496 * @param rowKey the row key ({@code null} not permitted). 497 * 498 * @see #removeColumn(Comparable) 499 * 500 * @since 1.0.7 501 */ 502 public void removeRow(R rowKey) { 503 this.data.removeRow(rowKey); 504 updateBounds(); 505 fireDatasetChanged(); 506 } 507 508 /** 509 * Removes a column from the dataset and sends a {@link DatasetChangeEvent} 510 * to all registered listeners. 511 * 512 * @param columnIndex the column index. 513 * 514 * @see #removeRow(int) 515 * 516 * @since 1.0.7 517 */ 518 public void removeColumn(int columnIndex) { 519 this.data.removeColumn(columnIndex); 520 updateBounds(); 521 fireDatasetChanged(); 522 } 523 524 /** 525 * Removes a column from the dataset and sends a {@link DatasetChangeEvent} 526 * to all registered listeners. 527 * 528 * @param columnKey the column key ({@code null} not permitted). 529 * 530 * @see #removeRow(Comparable) 531 * 532 * @since 1.0.7 533 */ 534 public void removeColumn(C columnKey) { 535 this.data.removeColumn(columnKey); 536 updateBounds(); 537 fireDatasetChanged(); 538 } 539 540 /** 541 * Clears all data from the dataset and sends a {@link DatasetChangeEvent} 542 * to all registered listeners. 543 * 544 * @since 1.0.7 545 */ 546 public void clear() { 547 this.data.clear(); 548 updateBounds(); 549 fireDatasetChanged(); 550 } 551 552 /** 553 * Iterate over all the data items and update the cached bound values. 554 */ 555 private void updateBounds() { 556 this.maximumRangeValue = Double.NaN; 557 this.maximumRangeValueRow = -1; 558 this.maximumRangeValueColumn = -1; 559 this.minimumRangeValue = Double.NaN; 560 this.minimumRangeValueRow = -1; 561 this.minimumRangeValueColumn = -1; 562 this.maximumRangeValueIncStdDev = Double.NaN; 563 this.maximumRangeValueIncStdDevRow = -1; 564 this.maximumRangeValueIncStdDevColumn = -1; 565 this.minimumRangeValueIncStdDev = Double.NaN; 566 this.minimumRangeValueIncStdDevRow = -1; 567 this.minimumRangeValueIncStdDevColumn = -1; 568 569 int rowCount = this.data.getRowCount(); 570 int columnCount = this.data.getColumnCount(); 571 for (int r = 0; r < rowCount; r++) { 572 for (int c = 0; c < columnCount; c++) { 573 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 574 this.data.getObject(r, c); 575 if (masd == null) { 576 continue; 577 } 578 double m = masd.getMeanValue(); 579 double sd = masd.getStandardDeviationValue(); 580 581 if (!Double.isNaN(m)) { 582 583 // update the max value 584 if (Double.isNaN(this.maximumRangeValue)) { 585 this.maximumRangeValue = m; 586 this.maximumRangeValueRow = r; 587 this.maximumRangeValueColumn = c; 588 } 589 else { 590 if (m > this.maximumRangeValue) { 591 this.maximumRangeValue = m; 592 this.maximumRangeValueRow = r; 593 this.maximumRangeValueColumn = c; 594 } 595 } 596 597 // update the min value 598 if (Double.isNaN(this.minimumRangeValue)) { 599 this.minimumRangeValue = m; 600 this.minimumRangeValueRow = r; 601 this.minimumRangeValueColumn = c; 602 } 603 else { 604 if (m < this.minimumRangeValue) { 605 this.minimumRangeValue = m; 606 this.minimumRangeValueRow = r; 607 this.minimumRangeValueColumn = c; 608 } 609 } 610 611 if (!Double.isNaN(sd)) { 612 // update the max value 613 if (Double.isNaN(this.maximumRangeValueIncStdDev)) { 614 this.maximumRangeValueIncStdDev = m + sd; 615 this.maximumRangeValueIncStdDevRow = r; 616 this.maximumRangeValueIncStdDevColumn = c; 617 } 618 else { 619 if (m + sd > this.maximumRangeValueIncStdDev) { 620 this.maximumRangeValueIncStdDev = m + sd; 621 this.maximumRangeValueIncStdDevRow = r; 622 this.maximumRangeValueIncStdDevColumn = c; 623 } 624 } 625 626 // update the min value 627 if (Double.isNaN(this.minimumRangeValueIncStdDev)) { 628 this.minimumRangeValueIncStdDev = m - sd; 629 this.minimumRangeValueIncStdDevRow = r; 630 this.minimumRangeValueIncStdDevColumn = c; 631 } 632 else { 633 if (m - sd < this.minimumRangeValueIncStdDev) { 634 this.minimumRangeValueIncStdDev = m - sd; 635 this.minimumRangeValueIncStdDevRow = r; 636 this.minimumRangeValueIncStdDevColumn = c; 637 } 638 } 639 } 640 } 641 } 642 } 643 } 644 645 /** 646 * Returns the minimum y-value in the dataset. 647 * 648 * @param includeInterval a flag that determines whether or not the 649 * y-interval is taken into account. 650 * 651 * @return The minimum value. 652 * 653 * @see #getRangeUpperBound(boolean) 654 */ 655 @Override 656 public double getRangeLowerBound(boolean includeInterval) { 657 if (includeInterval && !Double.isNaN(this.minimumRangeValueIncStdDev)) { 658 return this.minimumRangeValueIncStdDev; 659 } 660 else { 661 return this.minimumRangeValue; 662 } 663 } 664 665 /** 666 * Returns the maximum y-value in the dataset. 667 * 668 * @param includeInterval a flag that determines whether or not the 669 * y-interval is taken into account. 670 * 671 * @return The maximum value. 672 * 673 * @see #getRangeLowerBound(boolean) 674 */ 675 @Override 676 public double getRangeUpperBound(boolean includeInterval) { 677 if (includeInterval && !Double.isNaN(this.maximumRangeValueIncStdDev)) { 678 return this.maximumRangeValueIncStdDev; 679 } 680 else { 681 return this.maximumRangeValue; 682 } 683 } 684 685 /** 686 * Returns the bounds of the values in this dataset's y-values. 687 * 688 * @param includeInterval a flag that determines whether or not the 689 * y-interval is taken into account. 690 * 691 * @return The range. 692 */ 693 @Override 694 public Range getRangeBounds(boolean includeInterval) { 695 double lower = getRangeLowerBound(includeInterval); 696 double upper = getRangeUpperBound(includeInterval); 697 if (Double.isNaN(lower) && Double.isNaN(upper)) { 698 return null; 699 } 700 return new Range(lower, upper); 701 } 702 703 /** 704 * Tests this instance for equality with an arbitrary object. 705 * 706 * @param obj the object ({@code null} permitted). 707 * 708 * @return A boolean. 709 */ 710 @Override 711 public boolean equals(Object obj) { 712 if (obj == this) { 713 return true; 714 } 715 if (!(obj instanceof DefaultStatisticalCategoryDataset)) { 716 return false; 717 } 718 DefaultStatisticalCategoryDataset that 719 = (DefaultStatisticalCategoryDataset) obj; 720 if (!this.data.equals(that.data)) { 721 return false; 722 } 723 return true; 724 } 725 726 @Override 727 public int hashCode(){ 728 int hash = 7; 729 hash = 47 * hash + Objects.hashCode(this.data); 730 return hash; 731 } 732 733 /** 734 * Returns a clone of this dataset. 735 * 736 * @return A clone of this dataset. 737 * 738 * @throws CloneNotSupportedException if cloning cannot be completed. 739 */ 740 @Override 741 public Object clone() throws CloneNotSupportedException { 742 DefaultStatisticalCategoryDataset<R, C> clone 743 = (DefaultStatisticalCategoryDataset<R, C>) super.clone(); 744 clone.data = CloneUtils.clone(this.data); 745 return clone; 746 } 747}