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