001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ----------------------------------- 028 * DefaultIntervalCategoryDataset.java 029 * ----------------------------------- 030 * (C) Copyright 2002-2005, by Jeremy Bowman and Contributors. 031 * 032 * Original Author: Jeremy Bowman; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * $Id: DefaultIntervalCategoryDataset.java,v 1.9.2.2 2005/10/25 21:29:58 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 29-Apr-2002 : Version 1, contributed by Jeremy Bowman (DG); 040 * 24-Oct-2002 : Amendments for changes made to the dataset interface (DG); 041 * 042 */ 043 044 package org.jfree.data.category; 045 046 import java.util.ArrayList; 047 import java.util.Arrays; 048 import java.util.Collections; 049 import java.util.List; 050 import java.util.ResourceBundle; 051 052 import org.jfree.data.DataUtilities; 053 import org.jfree.data.general.AbstractSeriesDataset; 054 055 /** 056 * A convenience class that provides a default implementation of the 057 * {@link IntervalCategoryDataset} interface. 058 * <p> 059 * The standard constructor accepts data in a two dimensional array where the 060 * first dimension is the series, and the second dimension is the category. 061 * 062 * @author Jeremy Bowman 063 */ 064 public class DefaultIntervalCategoryDataset extends AbstractSeriesDataset 065 implements IntervalCategoryDataset { 066 067 /** The series keys. */ 068 private Comparable[] seriesKeys; 069 070 /** The category keys. */ 071 private Comparable[] categoryKeys; 072 073 /** Storage for the start value data. */ 074 private Number[][] startData; 075 076 /** Storage for the end value data. */ 077 private Number[][] endData; 078 079 /** 080 * Creates a new dataset. 081 * 082 * @param starts the starting values for the intervals. 083 * @param ends the ending values for the intervals. 084 */ 085 public DefaultIntervalCategoryDataset(double[][] starts, double[][] ends) { 086 this( 087 DataUtilities.createNumberArray2D(starts), 088 DataUtilities.createNumberArray2D(ends) 089 ); 090 } 091 092 /** 093 * Constructs a dataset and populates it with data from the array. 094 * <p> 095 * The arrays are indexed as data[series][category]. Series and category 096 * names are automatically generated - you can change them using the 097 * {@link #setSeriesKeys(Comparable[])} and 098 * {@link #setCategoryKeys(Comparable[])} methods. 099 * 100 * @param starts the start values data. 101 * @param ends the end values data. 102 */ 103 public DefaultIntervalCategoryDataset(Number[][] starts, Number[][] ends) { 104 this(null, null, starts, ends); 105 } 106 107 /** 108 * Constructs a DefaultIntervalCategoryDataset, populates it with data 109 * from the arrays, and uses the supplied names for the series. 110 * <p> 111 * Category names are generated automatically ("Category 1", "Category 2", 112 * etc). 113 * 114 * @param seriesNames the series names. 115 * @param starts the start values data, indexed as data[series][category]. 116 * @param ends the end values data, indexed as data[series][category]. 117 */ 118 public DefaultIntervalCategoryDataset(String[] seriesNames, 119 Number[][] starts, 120 Number[][] ends) { 121 122 this(seriesNames, null, starts, ends); 123 124 } 125 126 /** 127 * Constructs a DefaultIntervalCategoryDataset, populates it with data 128 * from the arrays, and uses the supplied names for the series and the 129 * supplied objects for the categories. 130 * 131 * @param seriesKeys the series keys. 132 * @param categoryKeys the categories. 133 * @param starts the start values data, indexed as data[series][category]. 134 * @param ends the end values data, indexed as data[series][category]. 135 */ 136 public DefaultIntervalCategoryDataset(Comparable[] seriesKeys, 137 Comparable[] categoryKeys, 138 Number[][] starts, 139 Number[][] ends) { 140 141 this.startData = starts; 142 this.endData = ends; 143 144 if (starts != null && ends != null) { 145 146 String baseName = "org.jfree.data.resources.DataPackageResources"; 147 ResourceBundle resources = ResourceBundle.getBundle(baseName); 148 149 int seriesCount = starts.length; 150 if (seriesCount != ends.length) { 151 String errMsg = "DefaultIntervalCategoryDataset: the number " 152 + "of series in the start value dataset does " 153 + "not match the number of series in the end " 154 + "value dataset."; 155 throw new IllegalArgumentException(errMsg); 156 } 157 if (seriesCount > 0) { 158 159 // set up the series names... 160 if (seriesKeys != null) { 161 162 if (seriesKeys.length != seriesCount) { 163 throw new IllegalArgumentException( 164 "The number of series keys does " 165 + "not match the number of series in the data." 166 ); 167 } 168 169 this.seriesKeys = seriesKeys; 170 } 171 else { 172 String prefix 173 = resources.getString("series.default-prefix") + " "; 174 this.seriesKeys = generateKeys(seriesCount, prefix); 175 } 176 177 // set up the category names... 178 int categoryCount = starts[0].length; 179 if (categoryCount != ends[0].length) { 180 String errMsg = "DefaultIntervalCategoryDataset: the " 181 + "number of categories in the start value " 182 + "dataset does not match the number of " 183 + "categories in the end value dataset."; 184 throw new IllegalArgumentException(errMsg); 185 } 186 if (categoryKeys != null) { 187 if (categoryKeys.length != categoryCount) { 188 throw new IllegalArgumentException( 189 "The number of category keys does " 190 + "not match the number of categories in the data." 191 ); 192 } 193 this.categoryKeys = categoryKeys; 194 } 195 else { 196 String prefix = resources.getString( 197 "categories.default-prefix" 198 ) + " "; 199 this.categoryKeys = generateKeys(categoryCount, prefix); 200 } 201 202 } 203 else { 204 this.seriesKeys = null; 205 this.categoryKeys = null; 206 } 207 } 208 209 } 210 211 /** 212 * Returns the number of series in the dataset (possibly zero). 213 * 214 * @return The number of series in the dataset. 215 */ 216 public int getSeriesCount() { 217 int result = 0; 218 if (this.startData != null) { 219 result = this.startData.length; 220 } 221 return result; 222 } 223 224 /** 225 * Returns the item count. 226 * 227 * @return The item count. 228 */ 229 public int getItemCount() { 230 return this.categoryKeys.length; 231 } 232 233 /** 234 * Returns a series index. 235 * 236 * @param series the series key. 237 * 238 * @return The series index. 239 */ 240 public int getSeriesIndex(Comparable series) { 241 List seriesKeys = getSeries(); 242 return seriesKeys.indexOf(series); 243 } 244 245 /** 246 * Returns the name of the specified series. 247 * 248 * @param series the index of the required series (zero-based). 249 * 250 * @return The name of the specified series. 251 */ 252 public Comparable getSeriesKey(int series) { 253 if ((series >= getSeriesCount()) || (series < 0)) { 254 throw new IllegalArgumentException("No such series : " + series); 255 } 256 return this.seriesKeys[series]; 257 } 258 259 /** 260 * Sets the names of the series in the dataset. 261 * 262 * @param seriesKeys the keys of the series in the dataset. 263 */ 264 public void setSeriesKeys(Comparable[] seriesKeys) { 265 266 // check argument... 267 if (seriesKeys == null) { 268 throw new IllegalArgumentException("Null 'seriesKeys' argument."); 269 } 270 271 if (seriesKeys.length != getSeriesCount()) { 272 throw new IllegalArgumentException( 273 "DefaultIntervalCategoryDataset.setSeriesKeys(): " 274 + "the number of series keys does not match the data." 275 ); 276 } 277 278 // make the change... 279 this.seriesKeys = seriesKeys; 280 fireDatasetChanged(); 281 282 } 283 284 /** 285 * Returns the number of categories in the dataset. 286 * <P> 287 * This method is part of the CategoryDataset interface. 288 * 289 * @return The number of categories in the dataset. 290 */ 291 public int getCategoryCount() { 292 int result = 0; 293 if (this.startData != null) { 294 if (getSeriesCount() > 0) { 295 result = this.startData[0].length; 296 } 297 } 298 return result; 299 } 300 301 /** 302 * Returns a list of the series in the dataset. 303 * <P> 304 * Supports the CategoryDataset interface. 305 * 306 * @return A list of the series in the dataset. 307 */ 308 public List getSeries() { 309 310 // the CategoryDataset interface expects a list of series, but 311 // we've stored them in an array... 312 if (this.seriesKeys == null) { 313 return new java.util.ArrayList(); 314 } 315 else { 316 return Collections.unmodifiableList(Arrays.asList(this.seriesKeys)); 317 } 318 319 } 320 321 /** 322 * Returns a list of the categories in the dataset. 323 * <P> 324 * Supports the CategoryDataset interface. 325 * 326 * @return A list of the categories in the dataset. 327 */ 328 public List getCategories() { 329 return getColumnKeys(); 330 } 331 332 /** 333 * Returns a list of the categories in the dataset. 334 * <P> 335 * Supports the CategoryDataset interface. 336 * 337 * @return A list of the categories in the dataset. 338 */ 339 public List getColumnKeys() { 340 341 // the CategoryDataset interface expects a list of categories, but 342 // we've stored them in an array... 343 if (this.categoryKeys == null) { 344 return new ArrayList(); 345 } 346 else { 347 return Collections.unmodifiableList( 348 Arrays.asList(this.categoryKeys) 349 ); 350 } 351 352 } 353 354 /** 355 * Sets the categories for the dataset. 356 * 357 * @param categoryKeys an array of objects representing the categories in 358 * the dataset. 359 */ 360 public void setCategoryKeys(Comparable[] categoryKeys) { 361 362 // check arguments... 363 if (categoryKeys == null) { 364 throw new IllegalArgumentException("Null 'categoryKeys' argument."); 365 } 366 367 if (categoryKeys.length != this.startData[0].length) { 368 throw new IllegalArgumentException( 369 "The number of categories does not match the data." 370 ); 371 } 372 373 for (int i = 0; i < categoryKeys.length; i++) { 374 if (categoryKeys[i] == null) { 375 throw new IllegalArgumentException( 376 "DefaultIntervalCategoryDataset.setCategoryKeys(): " 377 + "null category not permitted."); 378 } 379 } 380 381 // make the change... 382 this.categoryKeys = categoryKeys; 383 fireDatasetChanged(); 384 385 } 386 387 /** 388 * Returns the data value for one category in a series. 389 * <P> 390 * This method is part of the CategoryDataset interface. Not particularly 391 * meaningful for this class...returns the end value. 392 * @param series The required series (zero based index). 393 * @param category The required category. 394 * @return The data value for one category in a series (null possible). 395 */ 396 public Number getValue(Comparable series, Comparable category) { 397 int seriesIndex = getSeriesIndex(series); 398 int itemIndex = getColumnIndex(category); 399 return getValue(seriesIndex, itemIndex); 400 } 401 402 /** 403 * Returns the data value for one category in a series. 404 * <P> 405 * This method is part of the CategoryDataset interface. Not particularly 406 * meaningful for this class...returns the end value. 407 * 408 * @param series the required series (zero based index). 409 * @param category the required category. 410 * 411 * @return The data value for one category in a series (null possible). 412 */ 413 public Number getValue(int series, int category) { 414 return getEndValue(series, category); 415 } 416 417 /** 418 * Returns the start data value for one category in a series. 419 * 420 * @param series the required series. 421 * @param category the required category. 422 * 423 * @return The start data value for one category in a series 424 * (possibly <code>null</code>). 425 */ 426 public Number getStartValue(Comparable series, Comparable category) { 427 int seriesIndex = getSeriesIndex(series); 428 int itemIndex = getColumnIndex(category); 429 return getStartValue(seriesIndex, itemIndex); 430 } 431 432 /** 433 * Returns the start data value for one category in a series. 434 * 435 * @param series the required series (zero based index). 436 * @param category the required category. 437 * 438 * @return The start data value for one category in a series 439 * (possibly <code>null</code>). 440 */ 441 public Number getStartValue(int series, int category) { 442 443 // check arguments... 444 if ((series < 0) || (series >= getSeriesCount())) { 445 throw new IllegalArgumentException( 446 "DefaultIntervalCategoryDataset.getValue(): " 447 + "series index out of range."); 448 } 449 450 if ((category < 0) || (category >= getCategoryCount())) { 451 throw new IllegalArgumentException( 452 "DefaultIntervalCategoryDataset.getValue(): " 453 + "category index out of range."); 454 } 455 456 // fetch the value... 457 return this.startData[series][category]; 458 459 } 460 461 /** 462 * Returns the end data value for one category in a series. 463 * 464 * @param series the required series. 465 * @param category the required category. 466 * 467 * @return The end data value for one category in a series (null possible). 468 */ 469 public Number getEndValue(Comparable series, Comparable category) { 470 int seriesIndex = getSeriesIndex(series); 471 int itemIndex = getColumnIndex(category); 472 return getEndValue(seriesIndex, itemIndex); 473 } 474 475 /** 476 * Returns the end data value for one category in a series. 477 * 478 * @param series the required series (zero based index). 479 * @param category the required category. 480 * 481 * @return The end data value for one category in a series (null possible). 482 */ 483 public Number getEndValue(int series, int category) { 484 485 // check arguments... 486 if ((series < 0) || (series >= getSeriesCount())) { 487 throw new IllegalArgumentException( 488 "DefaultIntervalCategoryDataset.getValue(): " 489 + "series index out of range."); 490 } 491 492 if ((category < 0) || (category >= getCategoryCount())) { 493 throw new IllegalArgumentException( 494 "DefaultIntervalCategoryDataset.getValue(): " 495 + "category index out of range."); 496 } 497 498 // fetch the value... 499 return this.endData[series][category]; 500 501 } 502 503 /** 504 * Sets the start data value for one category in a series. 505 * 506 * @param series the series (zero-based index). 507 * @param category the category. 508 * 509 * @param value The value. 510 */ 511 public void setStartValue(int series, Comparable category, Number value) { 512 513 // does the series exist? 514 if ((series < 0) || (series > getSeriesCount())) { 515 throw new IllegalArgumentException( 516 "DefaultIntervalCategoryDataset.setValue: " 517 + "series outside valid range."); 518 } 519 520 // is the category valid? 521 int categoryIndex = getCategoryIndex(category); 522 if (categoryIndex < 0) { 523 throw new IllegalArgumentException( 524 "DefaultIntervalCategoryDataset.setValue: " 525 + "unrecognised category."); 526 } 527 528 // update the data... 529 this.startData[series][categoryIndex] = value; 530 fireDatasetChanged(); 531 532 } 533 534 /** 535 * Sets the end data value for one category in a series. 536 * 537 * @param series the series (zero-based index). 538 * @param category the category. 539 * 540 * @param value the value. 541 */ 542 public void setEndValue(int series, Comparable category, Number value) { 543 544 // does the series exist? 545 if ((series < 0) || (series > getSeriesCount())) { 546 throw new IllegalArgumentException( 547 "DefaultIntervalCategoryDataset.setValue: " 548 + "series outside valid range."); 549 } 550 551 // is the category valid? 552 int categoryIndex = getCategoryIndex(category); 553 if (categoryIndex < 0) { 554 throw new IllegalArgumentException( 555 "DefaultIntervalCategoryDataset.setValue: " 556 + "unrecognised category."); 557 } 558 559 // update the data... 560 this.endData[series][categoryIndex] = value; 561 fireDatasetChanged(); 562 563 } 564 565 /** 566 * Returns the index for the given category. 567 * 568 * @param category the category. 569 * 570 * @return The index. 571 */ 572 private int getCategoryIndex(Comparable category) { 573 int result = -1; 574 for (int i = 0; i < this.categoryKeys.length; i++) { 575 if (category.equals(this.categoryKeys[i])) { 576 result = i; 577 break; 578 } 579 } 580 return result; 581 } 582 583 /** 584 * Generates an array of keys, by appending a space plus an integer 585 * (starting with 1) to the supplied prefix string. 586 * 587 * @param count the number of keys required. 588 * @param prefix the name prefix. 589 * 590 * @return An array of <i>prefixN</i> with N = { 1 .. count}. 591 */ 592 private Comparable[] generateKeys(int count, String prefix) { 593 Comparable[] result = new Comparable[count]; 594 String name; 595 for (int i = 0; i < count; i++) { 596 name = prefix + (i + 1); 597 result[i] = name; 598 } 599 return result; 600 } 601 602 /** 603 * Returns a column key. 604 * 605 * @param column the column index. 606 * 607 * @return The column key. 608 */ 609 public Comparable getColumnKey(int column) { 610 return this.categoryKeys[column]; 611 } 612 613 /** 614 * Returns a column index. 615 * 616 * @param columnKey the column key. 617 * 618 * @return The column index. 619 */ 620 public int getColumnIndex(Comparable columnKey) { 621 List categories = getCategories(); 622 return categories.indexOf(columnKey); 623 } 624 625 /** 626 * Returns a row index. 627 * 628 * @param rowKey the row key. 629 * 630 * @return The row index. 631 */ 632 public int getRowIndex(Comparable rowKey) { 633 List seriesKeys = getSeries(); 634 return seriesKeys.indexOf(rowKey); 635 } 636 637 /** 638 * Returns a list of the series in the dataset. 639 * <P> 640 * Supports the CategoryDataset interface. 641 * 642 * @return A list of the series in the dataset. 643 */ 644 public List getRowKeys() { 645 // the CategoryDataset interface expects a list of series, but 646 // we've stored them in an array... 647 if (this.seriesKeys == null) { 648 return new java.util.ArrayList(); 649 } 650 else { 651 return Collections.unmodifiableList(Arrays.asList(this.seriesKeys)); 652 } 653 } 654 655 /** 656 * Returns the name of the specified series. 657 * 658 * @param row the index of the required row/series (zero-based). 659 * 660 * @return The name of the specified series. 661 */ 662 public Comparable getRowKey(int row) { 663 if ((row >= getRowCount()) || (row < 0)) { 664 throw new IllegalArgumentException( 665 "The 'row' argument is out of bounds."); 666 } 667 return this.seriesKeys[row]; 668 } 669 670 /** 671 * Returns the number of categories in the dataset. This method is part of 672 * the {@link CategoryDataset} interface. 673 * 674 * @return The number of categories in the dataset. 675 */ 676 public int getColumnCount() { 677 int result = 0; 678 if (this.startData != null) { 679 if (getSeriesCount() > 0) { 680 result = this.startData[0].length; 681 } 682 } 683 return result; 684 } 685 686 /** 687 * Returns the number of series in the dataset (possibly zero). 688 * 689 * @return The number of series in the dataset. 690 */ 691 public int getRowCount() { 692 int result = 0; 693 if (this.startData != null) { 694 result = this.startData.length; 695 } 696 return result; 697 } 698 699 }