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