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