001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, 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 058 package org.jfree.data.statistics; 059 060 import java.util.List; 061 062 import org.jfree.data.KeyedObjects2D; 063 import org.jfree.data.Range; 064 import org.jfree.data.RangeInfo; 065 import org.jfree.data.general.AbstractDataset; 066 import org.jfree.data.general.DatasetChangeEvent; 067 import org.jfree.util.PublicCloneable; 068 069 /** 070 * A convenience class that provides a default implementation of the 071 * {@link StatisticalCategoryDataset} interface. 072 */ 073 public 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 public Number getMeanValue(int row, int column) { 155 Number result = null; 156 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 157 this.data.getObject(row, column); 158 if (masd != null) { 159 result = masd.getMean(); 160 } 161 return result; 162 } 163 164 /** 165 * Returns the value for an item (for this dataset, the mean value is 166 * returned). 167 * 168 * @param row the row index. 169 * @param column the column index. 170 * 171 * @return The value (possibly <code>null</code>). 172 */ 173 public Number getValue(int row, int column) { 174 return getMeanValue(row, column); 175 } 176 177 /** 178 * Returns the value for an item (for this dataset, the mean value is 179 * returned). 180 * 181 * @param rowKey the row key. 182 * @param columnKey the columnKey. 183 * 184 * @return The value (possibly <code>null</code>). 185 */ 186 public Number getValue(Comparable rowKey, Comparable columnKey) { 187 return getMeanValue(rowKey, columnKey); 188 } 189 190 /** 191 * Returns the mean value for an item. 192 * 193 * @param rowKey the row key. 194 * @param columnKey the columnKey. 195 * 196 * @return The mean value (possibly <code>null</code>). 197 */ 198 public Number getMeanValue(Comparable rowKey, Comparable columnKey) { 199 Number result = null; 200 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 201 this.data.getObject(rowKey, columnKey); 202 if (masd != null) { 203 result = masd.getMean(); 204 } 205 return result; 206 } 207 208 /** 209 * Returns the standard deviation value for an item. 210 * 211 * @param row the row index (zero-based). 212 * @param column the column index (zero-based). 213 * 214 * @return The standard deviation (possibly <code>null</code>). 215 */ 216 public Number getStdDevValue(int row, int column) { 217 Number result = null; 218 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 219 this.data.getObject(row, column); 220 if (masd != null) { 221 result = masd.getStandardDeviation(); 222 } 223 return result; 224 } 225 226 /** 227 * Returns the standard deviation value for an item. 228 * 229 * @param rowKey the row key. 230 * @param columnKey the columnKey. 231 * 232 * @return The standard deviation (possibly <code>null</code>). 233 */ 234 public Number getStdDevValue(Comparable rowKey, Comparable columnKey) { 235 Number result = null; 236 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 237 this.data.getObject(rowKey, columnKey); 238 if (masd != null) { 239 result = masd.getStandardDeviation(); 240 } 241 return result; 242 } 243 244 /** 245 * Returns the column index for a given key. 246 * 247 * @param key the column key (<code>null</code> not permitted). 248 * 249 * @return The column index. 250 */ 251 public int getColumnIndex(Comparable key) { 252 // defer null argument check 253 return this.data.getColumnIndex(key); 254 } 255 256 /** 257 * Returns a column key. 258 * 259 * @param column the column index (zero-based). 260 * 261 * @return The column key. 262 */ 263 public Comparable getColumnKey(int column) { 264 return this.data.getColumnKey(column); 265 } 266 267 /** 268 * Returns the column keys. 269 * 270 * @return The keys. 271 */ 272 public List getColumnKeys() { 273 return this.data.getColumnKeys(); 274 } 275 276 /** 277 * Returns the row index for a given key. 278 * 279 * @param key the row key (<code>null</code> not permitted). 280 * 281 * @return The row index. 282 */ 283 public int getRowIndex(Comparable key) { 284 // defer null argument check 285 return this.data.getRowIndex(key); 286 } 287 288 /** 289 * Returns a row key. 290 * 291 * @param row the row index (zero-based). 292 * 293 * @return The row key. 294 */ 295 public Comparable getRowKey(int row) { 296 return this.data.getRowKey(row); 297 } 298 299 /** 300 * Returns the row keys. 301 * 302 * @return The keys. 303 */ 304 public List getRowKeys() { 305 return this.data.getRowKeys(); 306 } 307 308 /** 309 * Returns the number of rows in the table. 310 * 311 * @return The row count. 312 * 313 * @see #getColumnCount() 314 */ 315 public int getRowCount() { 316 return this.data.getRowCount(); 317 } 318 319 /** 320 * Returns the number of columns in the table. 321 * 322 * @return The column count. 323 * 324 * @see #getRowCount() 325 */ 326 public int getColumnCount() { 327 return this.data.getColumnCount(); 328 } 329 330 /** 331 * Adds a mean and standard deviation to the table. 332 * 333 * @param mean the mean. 334 * @param standardDeviation the standard deviation. 335 * @param rowKey the row key. 336 * @param columnKey the column key. 337 */ 338 public void add(double mean, double standardDeviation, 339 Comparable rowKey, Comparable columnKey) { 340 add(new Double(mean), new Double(standardDeviation), rowKey, columnKey); 341 } 342 343 /** 344 * Adds a mean and standard deviation to the table. 345 * 346 * @param mean the mean. 347 * @param standardDeviation the standard deviation. 348 * @param rowKey the row key. 349 * @param columnKey the column key. 350 */ 351 public void add(Number mean, Number standardDeviation, 352 Comparable rowKey, Comparable columnKey) { 353 MeanAndStandardDeviation item = new MeanAndStandardDeviation( 354 mean, standardDeviation); 355 this.data.addObject(item, rowKey, columnKey); 356 357 double m = Double.NaN; 358 double sd = Double.NaN; 359 if (mean != null) { 360 m = mean.doubleValue(); 361 } 362 if (standardDeviation != null) { 363 sd = standardDeviation.doubleValue(); 364 } 365 366 // update cached range values 367 int r = this.data.getColumnIndex(columnKey); 368 int c = this.data.getRowIndex(rowKey); 369 if ((r == this.maximumRangeValueRow && c 370 == this.maximumRangeValueColumn) || (r 371 == this.maximumRangeValueIncStdDevRow && c 372 == this.maximumRangeValueIncStdDevColumn) || (r 373 == this.minimumRangeValueRow && c 374 == this.minimumRangeValueColumn) || (r 375 == this.minimumRangeValueIncStdDevRow && c 376 == this.minimumRangeValueIncStdDevColumn)) { 377 378 // iterate over all data items and update mins and maxes 379 updateBounds(); 380 } 381 else { 382 if (!Double.isNaN(m)) { 383 if (Double.isNaN(this.maximumRangeValue) 384 || m > this.maximumRangeValue) { 385 this.maximumRangeValue = m; 386 this.maximumRangeValueRow = r; 387 this.maximumRangeValueColumn = c; 388 } 389 } 390 391 if (!Double.isNaN(m + sd)) { 392 if (Double.isNaN(this.maximumRangeValueIncStdDev) 393 || (m + sd) > this.maximumRangeValueIncStdDev) { 394 this.maximumRangeValueIncStdDev = m + sd; 395 this.maximumRangeValueIncStdDevRow = r; 396 this.maximumRangeValueIncStdDevColumn = c; 397 } 398 } 399 400 if (!Double.isNaN(m)) { 401 if (Double.isNaN(this.minimumRangeValue) 402 || m < this.minimumRangeValue) { 403 this.minimumRangeValue = m; 404 this.minimumRangeValueRow = r; 405 this.minimumRangeValueColumn = c; 406 } 407 } 408 409 if (!Double.isNaN(m - sd)) { 410 if (Double.isNaN(this.minimumRangeValueIncStdDev) 411 || (m - sd) < this.minimumRangeValueIncStdDev) { 412 this.minimumRangeValueIncStdDev = m - sd; 413 this.minimumRangeValueIncStdDevRow = r; 414 this.minimumRangeValueIncStdDevColumn = c; 415 } 416 } 417 } 418 fireDatasetChanged(); 419 } 420 421 /** 422 * Removes an item from the dataset and sends a {@link DatasetChangeEvent} 423 * to all registered listeners. 424 * 425 * @param rowKey the row key (<code>null</code> not permitted). 426 * @param columnKey the column key (<code>null</code> not permitted). 427 * 428 * @see #add(double, double, Comparable, Comparable) 429 * 430 * @since 1.0.7 431 */ 432 public void remove(Comparable rowKey, Comparable columnKey) { 433 // defer null argument checks 434 int r = getRowIndex(rowKey); 435 int c = getColumnIndex(columnKey); 436 this.data.removeObject(rowKey, columnKey); 437 438 // if this cell held a maximum and/or minimum value, we'll need to 439 // update the cached bounds... 440 if ((r == this.maximumRangeValueRow && c 441 == this.maximumRangeValueColumn) || (r 442 == this.maximumRangeValueIncStdDevRow && c 443 == this.maximumRangeValueIncStdDevColumn) || (r 444 == this.minimumRangeValueRow && c 445 == this.minimumRangeValueColumn) || (r 446 == this.minimumRangeValueIncStdDevRow && c 447 == this.minimumRangeValueIncStdDevColumn)) { 448 449 // iterate over all data items and update mins and maxes 450 updateBounds(); 451 } 452 453 fireDatasetChanged(); 454 } 455 456 457 /** 458 * Removes a row from the dataset and sends a {@link DatasetChangeEvent} 459 * to all registered listeners. 460 * 461 * @param rowIndex the row index. 462 * 463 * @see #removeColumn(int) 464 * 465 * @since 1.0.7 466 */ 467 public void removeRow(int rowIndex) { 468 this.data.removeRow(rowIndex); 469 updateBounds(); 470 fireDatasetChanged(); 471 } 472 473 /** 474 * Removes a row from the dataset and sends a {@link DatasetChangeEvent} 475 * to all registered listeners. 476 * 477 * @param rowKey the row key (<code>null</code> not permitted). 478 * 479 * @see #removeColumn(Comparable) 480 * 481 * @since 1.0.7 482 */ 483 public void removeRow(Comparable rowKey) { 484 this.data.removeRow(rowKey); 485 updateBounds(); 486 fireDatasetChanged(); 487 } 488 489 /** 490 * Removes a column from the dataset and sends a {@link DatasetChangeEvent} 491 * to all registered listeners. 492 * 493 * @param columnIndex the column index. 494 * 495 * @see #removeRow(int) 496 * 497 * @since 1.0.7 498 */ 499 public void removeColumn(int columnIndex) { 500 this.data.removeColumn(columnIndex); 501 updateBounds(); 502 fireDatasetChanged(); 503 } 504 505 /** 506 * Removes a column from the dataset and sends a {@link DatasetChangeEvent} 507 * to all registered listeners. 508 * 509 * @param columnKey the column key (<code>null</code> not permitted). 510 * 511 * @see #removeRow(Comparable) 512 * 513 * @since 1.0.7 514 */ 515 public void removeColumn(Comparable columnKey) { 516 this.data.removeColumn(columnKey); 517 updateBounds(); 518 fireDatasetChanged(); 519 } 520 521 /** 522 * Clears all data from the dataset and sends a {@link DatasetChangeEvent} 523 * to all registered listeners. 524 * 525 * @since 1.0.7 526 */ 527 public void clear() { 528 this.data.clear(); 529 updateBounds(); 530 fireDatasetChanged(); 531 } 532 533 /** 534 * Iterate over all the data items and update the cached bound values. 535 */ 536 private void updateBounds() { 537 this.maximumRangeValue = Double.NaN; 538 this.maximumRangeValueRow = -1; 539 this.maximumRangeValueColumn = -1; 540 this.minimumRangeValue = Double.NaN; 541 this.minimumRangeValueRow = -1; 542 this.minimumRangeValueColumn = -1; 543 this.maximumRangeValueIncStdDev = Double.NaN; 544 this.maximumRangeValueIncStdDevRow = -1; 545 this.maximumRangeValueIncStdDevColumn = -1; 546 this.minimumRangeValueIncStdDev = Double.NaN; 547 this.minimumRangeValueIncStdDevRow = -1; 548 this.minimumRangeValueIncStdDevColumn = -1; 549 550 int rowCount = this.data.getRowCount(); 551 int columnCount = this.data.getColumnCount(); 552 for (int r = 0; r < rowCount; r++) { 553 for (int c = 0; c < columnCount; c++) { 554 MeanAndStandardDeviation masd = (MeanAndStandardDeviation) 555 this.data.getObject(r, c); 556 if (masd == null) { 557 continue; 558 } 559 double m = masd.getMeanValue(); 560 double sd = masd.getStandardDeviationValue(); 561 562 if (!Double.isNaN(m)) { 563 564 // update the max value 565 if (Double.isNaN(this.maximumRangeValue)) { 566 this.maximumRangeValue = m; 567 this.maximumRangeValueRow = r; 568 this.maximumRangeValueColumn = c; 569 } 570 else { 571 if (m > this.maximumRangeValue) { 572 this.maximumRangeValue = m; 573 this.maximumRangeValueRow = r; 574 this.maximumRangeValueColumn = c; 575 } 576 } 577 578 // update the min value 579 if (Double.isNaN(this.minimumRangeValue)) { 580 this.minimumRangeValue = m; 581 this.minimumRangeValueRow = r; 582 this.minimumRangeValueColumn = c; 583 } 584 else { 585 if (m < this.minimumRangeValue) { 586 this.minimumRangeValue = m; 587 this.minimumRangeValueRow = r; 588 this.minimumRangeValueColumn = c; 589 } 590 } 591 592 if (!Double.isNaN(sd)) { 593 // update the max value 594 if (Double.isNaN(this.maximumRangeValueIncStdDev)) { 595 this.maximumRangeValueIncStdDev = m + sd; 596 this.maximumRangeValueIncStdDevRow = r; 597 this.maximumRangeValueIncStdDevColumn = c; 598 } 599 else { 600 if (m + sd > this.maximumRangeValueIncStdDev) { 601 this.maximumRangeValueIncStdDev = m + sd; 602 this.maximumRangeValueIncStdDevRow = r; 603 this.maximumRangeValueIncStdDevColumn = c; 604 } 605 } 606 607 // update the min value 608 if (Double.isNaN(this.minimumRangeValueIncStdDev)) { 609 this.minimumRangeValueIncStdDev = m - sd; 610 this.minimumRangeValueIncStdDevRow = r; 611 this.minimumRangeValueIncStdDevColumn = c; 612 } 613 else { 614 if (m - sd < this.minimumRangeValueIncStdDev) { 615 this.minimumRangeValueIncStdDev = m - sd; 616 this.minimumRangeValueIncStdDevRow = r; 617 this.minimumRangeValueIncStdDevColumn = c; 618 } 619 } 620 } 621 } 622 } 623 } 624 } 625 626 /** 627 * Returns the minimum y-value in the dataset. 628 * 629 * @param includeInterval a flag that determines whether or not the 630 * y-interval is taken into account. 631 * 632 * @return The minimum value. 633 * 634 * @see #getRangeUpperBound(boolean) 635 */ 636 public double getRangeLowerBound(boolean includeInterval) { 637 if (includeInterval && !Double.isNaN(this.minimumRangeValueIncStdDev)) { 638 return this.minimumRangeValueIncStdDev; 639 } 640 else { 641 return this.minimumRangeValue; 642 } 643 } 644 645 /** 646 * Returns the maximum 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 maximum value. 652 * 653 * @see #getRangeLowerBound(boolean) 654 */ 655 public double getRangeUpperBound(boolean includeInterval) { 656 if (includeInterval && !Double.isNaN(this.maximumRangeValueIncStdDev)) { 657 return this.maximumRangeValueIncStdDev; 658 } 659 else { 660 return this.maximumRangeValue; 661 } 662 } 663 664 /** 665 * Returns the bounds of the values in this dataset's y-values. 666 * 667 * @param includeInterval a flag that determines whether or not the 668 * y-interval is taken into account. 669 * 670 * @return The range. 671 */ 672 public Range getRangeBounds(boolean includeInterval) { 673 double lower = getRangeLowerBound(includeInterval); 674 double upper = getRangeUpperBound(includeInterval); 675 if (Double.isNaN(lower) && Double.isNaN(upper)) { 676 return null; 677 } 678 return new Range(lower, upper); 679 } 680 681 /** 682 * Tests this instance for equality with an arbitrary object. 683 * 684 * @param obj the object (<code>null</code> permitted). 685 * 686 * @return A boolean. 687 */ 688 public boolean equals(Object obj) { 689 if (obj == this) { 690 return true; 691 } 692 if (!(obj instanceof DefaultStatisticalCategoryDataset)) { 693 return false; 694 } 695 DefaultStatisticalCategoryDataset that 696 = (DefaultStatisticalCategoryDataset) obj; 697 if (!this.data.equals(that.data)) { 698 return false; 699 } 700 return true; 701 } 702 703 /** 704 * Returns a clone of this dataset. 705 * 706 * @return A clone of this dataset. 707 * 708 * @throws CloneNotSupportedException if cloning cannot be completed. 709 */ 710 public Object clone() throws CloneNotSupportedException { 711 DefaultStatisticalCategoryDataset clone 712 = (DefaultStatisticalCategoryDataset) super.clone(); 713 clone.data = (KeyedObjects2D) this.data.clone(); 714 return clone; 715 } 716 }