Source for org.jfree.data.category.DefaultIntervalCategoryDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -----------------------------------
  28:  * DefaultIntervalCategoryDataset.java
  29:  * -----------------------------------
  30:  * (C) Copyright 2002-2005, by Jeremy Bowman and Contributors.
  31:  *
  32:  * Original Author:  Jeremy Bowman;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: DefaultIntervalCategoryDataset.java,v 1.9.2.2 2005/10/25 21:29:58 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 29-Apr-2002 : Version 1, contributed by Jeremy Bowman (DG);
  40:  * 24-Oct-2002 : Amendments for changes made to the dataset interface (DG);
  41:  *
  42:  */
  43: 
  44: package org.jfree.data.category;
  45: 
  46: import java.util.ArrayList;
  47: import java.util.Arrays;
  48: import java.util.Collections;
  49: import java.util.List;
  50: import java.util.ResourceBundle;
  51: 
  52: import org.jfree.data.DataUtilities;
  53: import org.jfree.data.general.AbstractSeriesDataset;
  54: 
  55: /**
  56:  * A convenience class that provides a default implementation of the
  57:  * {@link IntervalCategoryDataset} interface.
  58:  * <p>
  59:  * The standard constructor accepts data in a two dimensional array where the
  60:  * first dimension is the series, and the second dimension is the category.
  61:  *
  62:  * @author Jeremy Bowman
  63:  */
  64: public class DefaultIntervalCategoryDataset extends AbstractSeriesDataset
  65:                                             implements IntervalCategoryDataset {
  66: 
  67:     /** The series keys. */
  68:     private Comparable[] seriesKeys;
  69: 
  70:     /** The category keys. */
  71:     private Comparable[] categoryKeys;
  72: 
  73:     /** Storage for the start value data. */
  74:     private Number[][] startData;
  75: 
  76:     /** Storage for the end value data. */
  77:     private Number[][] endData;
  78: 
  79:     /**
  80:      * Creates a new dataset.
  81:      *
  82:      * @param starts  the starting values for the intervals.
  83:      * @param ends  the ending values for the intervals.
  84:      */
  85:     public DefaultIntervalCategoryDataset(double[][] starts, double[][] ends) {
  86:         this(
  87:             DataUtilities.createNumberArray2D(starts),
  88:             DataUtilities.createNumberArray2D(ends)
  89:         );
  90:     }
  91: 
  92:     /**
  93:      * Constructs a dataset and populates it with data from the array.
  94:      * <p>
  95:      * The arrays are indexed as data[series][category].  Series and category
  96:      * names are automatically generated - you can change them using the
  97:      * {@link #setSeriesKeys(Comparable[])} and 
  98:      * {@link #setCategoryKeys(Comparable[])} methods.
  99:      *
 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: }