Source for org.jfree.data.general.CombinedDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, 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:  * CombinedDataset.java
  29:  * --------------------
  30:  * (C) Copyright 2001-2007, by Bill Kelemen and Contributors.
  31:  *
  32:  * Original Author:  Bill Kelemen;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: CombinedDataset.java,v 1.6.2.2 2007/02/02 15:50:44 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 06-Dec-2001 : Version 1 (BK);
  40:  * 27-Dec-2001 : Fixed bug in getChildPosition method (BK);
  41:  * 29-Dec-2001 : Fixed bug in getChildPosition method with complex 
  42:  *               CombinePlot (BK);
  43:  * 05-Feb-2002 : Small addition to the interface HighLowDataset, as requested 
  44:  *               by Sylvain Vieujot (DG);
  45:  * 14-Feb-2002 : Added bug fix for IntervalXYDataset methods, submitted by 
  46:  *               Gyula Kun-Szabo (DG);
  47:  * 11-Jun-2002 : Updated for change in event constructor (DG);
  48:  * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  49:  * 06-May-2004 : Now extends AbstractIntervalXYDataset and added other methods 
  50:  *               that return double primitives (DG);
  51:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  52:  *               getYValue() (DG);
  53:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  54:  * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
  55:  *
  56:  */
  57: 
  58: package org.jfree.data.general;
  59: 
  60: import java.util.List;
  61: 
  62: import org.jfree.data.xy.AbstractIntervalXYDataset;
  63: import org.jfree.data.xy.OHLCDataset;
  64: import org.jfree.data.xy.IntervalXYDataset;
  65: import org.jfree.data.xy.XYDataset;
  66: 
  67: /**
  68:  * This class can combine instances of {@link XYDataset}, {@link OHLCDataset} 
  69:  * and {@link IntervalXYDataset} together exposing the union of all the series 
  70:  * under one dataset.  
  71:  */
  72: public class CombinedDataset extends AbstractIntervalXYDataset
  73:                              implements XYDataset, 
  74:                                         OHLCDataset, 
  75:                                         IntervalXYDataset,
  76:                                         CombinationDataset {
  77: 
  78:     /** Storage for the datasets we combine. */
  79:     private List datasetInfo = new java.util.ArrayList();
  80: 
  81:     /**
  82:      * Default constructor for an empty combination.
  83:      */
  84:     public CombinedDataset() {
  85:         super();
  86:     }
  87: 
  88:     /**
  89:      * Creates a CombinedDataset initialized with an array of SeriesDatasets.
  90:      *
  91:      * @param data  array of SeriesDataset that contains the SeriesDatasets to 
  92:      *              combine.
  93:      */
  94:     public CombinedDataset(SeriesDataset[] data) {
  95:         add(data);
  96:     }
  97: 
  98:     /**
  99:      * Adds one SeriesDataset to the combination. Listeners are notified of the
 100:      * change.
 101:      *
 102:      * @param data  the SeriesDataset to add.
 103:      */
 104:     public void add(SeriesDataset data) {
 105:         fastAdd(data);
 106:         DatasetChangeEvent event = new DatasetChangeEvent(this, this);
 107:         notifyListeners(event);
 108:     }
 109: 
 110:     /**
 111:      * Adds an array of SeriesDataset's to the combination. Listeners are
 112:      * notified of the change.
 113:      *
 114:      * @param data  array of SeriesDataset to add
 115:      */
 116:     public void add(SeriesDataset[] data) {
 117: 
 118:         for (int i = 0; i < data.length; i++) {
 119:             fastAdd(data[i]);
 120:         }
 121:         DatasetChangeEvent event = new DatasetChangeEvent(this, this);
 122:         notifyListeners(event);
 123: 
 124:     }
 125: 
 126:     /**
 127:      * Adds one series from a SeriesDataset to the combination. Listeners are
 128:      * notified of the change.
 129:      *
 130:      * @param data  the SeriesDataset where series is contained
 131:      * @param series  series to add
 132:      */
 133:     public void add(SeriesDataset data, int series) {
 134:         add(new SubSeriesDataset(data, series));
 135:     }
 136: 
 137:     /**
 138:      * Fast add of a SeriesDataset. Does not notify listeners of the change.
 139:      *
 140:      * @param data  SeriesDataset to add
 141:      */
 142:     private void fastAdd(SeriesDataset data) {
 143:         for (int i = 0; i < data.getSeriesCount(); i++) {
 144:             this.datasetInfo.add(new DatasetInfo(data, i));
 145:         }
 146:     }
 147: 
 148:     ///////////////////////////////////////////////////////////////////////////
 149:     // From SeriesDataset
 150:     ///////////////////////////////////////////////////////////////////////////
 151: 
 152:     /**
 153:      * Returns the number of series in the dataset.
 154:      *
 155:      * @return The number of series in the dataset.
 156:      */
 157:     public int getSeriesCount() {
 158:         return this.datasetInfo.size();
 159:     }
 160: 
 161:     /**
 162:      * Returns the key for a series.
 163:      *
 164:      * @param series  the series (zero-based index).
 165:      *
 166:      * @return The key for a series.
 167:      */
 168:     public Comparable getSeriesKey(int series) {
 169:         DatasetInfo di = getDatasetInfo(series);
 170:         return di.data.getSeriesKey(di.series);
 171:     }
 172: 
 173:     ///////////////////////////////////////////////////////////////////////////
 174:     // From XYDataset
 175:     ///////////////////////////////////////////////////////////////////////////
 176: 
 177:     /**
 178:      * Returns the X-value for the specified series and item.
 179:      * <P>
 180:      * Note:  throws <code>ClassCastException</code> if the series is not from 
 181:      * a {@link XYDataset}.
 182:      *
 183:      * @param series  the index of the series of interest (zero-based).
 184:      * @param item  the index of the item of interest (zero-based).
 185:      *
 186:      * @return The X-value for the specified series and item.
 187:      */
 188:     public Number getX(int series, int item) {
 189:         DatasetInfo di = getDatasetInfo(series);
 190:         return ((XYDataset) di.data).getX(di.series, item);
 191:     }
 192: 
 193:     /**
 194:      * Returns the Y-value for the specified series and item.
 195:      * <P>
 196:      * Note:  throws <code>ClassCastException</code> if the series is not from 
 197:      * a {@link XYDataset}.
 198:      *
 199:      * @param series  the index of the series of interest (zero-based).
 200:      * @param item  the index of the item of interest (zero-based).
 201:      *
 202:      * @return The Y-value for the specified series and item.
 203:      */
 204:     public Number getY(int series, int item) {
 205:         DatasetInfo di = getDatasetInfo(series);
 206:         return ((XYDataset) di.data).getY(di.series, item);
 207:     }
 208: 
 209:     /**
 210:      * Returns the number of items in a series.
 211:      * <P>
 212:      * Note:  throws <code>ClassCastException</code> if the series is not from 
 213:      * a {@link XYDataset}.
 214:      *
 215:      * @param series  the index of the series of interest (zero-based).
 216:      *
 217:      * @return The number of items in a series.
 218:      */
 219:     public int getItemCount(int series) {
 220:         DatasetInfo di = getDatasetInfo(series);
 221:         return ((XYDataset) di.data).getItemCount(di.series);
 222:     }
 223: 
 224:     ///////////////////////////////////////////////////////////////////////////
 225:     // From HighLowDataset
 226:     ///////////////////////////////////////////////////////////////////////////
 227: 
 228:     /**
 229:      * Returns the high-value for the specified series and item.
 230:      * <P>
 231:      * Note:  throws <code>ClassCastException</code> if the series is not from a
 232:      * {@link OHLCDataset}.
 233:      *
 234:      * @param series  the index of the series of interest (zero-based).
 235:      * @param item  the index of the item of interest (zero-based).
 236:      *
 237:      * @return The high-value for the specified series and item.
 238:      */
 239:     public Number getHigh(int series, int item) {
 240:         DatasetInfo di = getDatasetInfo(series);
 241:         return ((OHLCDataset) di.data).getHigh(di.series, item);
 242:     }
 243: 
 244:     /**
 245:      * Returns the high-value (as a double primitive) for an item within a 
 246:      * series.
 247:      * 
 248:      * @param series  the series (zero-based index).
 249:      * @param item  the item (zero-based index).
 250:      * 
 251:      * @return The high-value.
 252:      */
 253:     public double getHighValue(int series, int item) {
 254:         double result = Double.NaN;
 255:         Number high = getHigh(series, item);
 256:         if (high != null) {
 257:             result = high.doubleValue();   
 258:         }
 259:         return result;   
 260:     }
 261: 
 262:     /**
 263:      * Returns the low-value for the specified series and item.
 264:      * <P>
 265:      * Note:  throws <code>ClassCastException</code> if the series is not from a
 266:      * {@link OHLCDataset}.
 267:      *
 268:      * @param series  the index of the series of interest (zero-based).
 269:      * @param item  the index of the item of interest (zero-based).
 270:      *
 271:      * @return The low-value for the specified series and item.
 272:      */
 273:     public Number getLow(int series, int item) {
 274:         DatasetInfo di = getDatasetInfo(series);
 275:         return ((OHLCDataset) di.data).getLow(di.series, item);
 276:     }
 277: 
 278:     /**
 279:      * Returns the low-value (as a double primitive) for an item within a 
 280:      * series.
 281:      * 
 282:      * @param series  the series (zero-based index).
 283:      * @param item  the item (zero-based index).
 284:      * 
 285:      * @return The low-value.
 286:      */
 287:     public double getLowValue(int series, int item) {
 288:         double result = Double.NaN;
 289:         Number low = getLow(series, item);
 290:         if (low != null) {
 291:             result = low.doubleValue();   
 292:         }
 293:         return result;   
 294:     }
 295: 
 296:     /**
 297:      * Returns the open-value for the specified series and item.
 298:      * <P>
 299:      * Note:  throws <code>ClassCastException</code> if the series is not from a
 300:      * {@link OHLCDataset}.
 301:      *
 302:      * @param series  the index of the series of interest (zero-based).
 303:      * @param item  the index of the item of interest (zero-based).
 304:      *
 305:      * @return The open-value for the specified series and item.
 306:      */
 307:     public Number getOpen(int series, int item) {
 308:         DatasetInfo di = getDatasetInfo(series);
 309:         return ((OHLCDataset) di.data).getOpen(di.series, item);
 310:     }
 311: 
 312:     /**
 313:      * Returns the open-value (as a double primitive) for an item within a 
 314:      * series.
 315:      * 
 316:      * @param series  the series (zero-based index).
 317:      * @param item  the item (zero-based index).
 318:      * 
 319:      * @return The open-value.
 320:      */
 321:     public double getOpenValue(int series, int item) {
 322:         double result = Double.NaN;
 323:         Number open = getOpen(series, item);
 324:         if (open != null) {
 325:             result = open.doubleValue();   
 326:         }
 327:         return result;   
 328:     }
 329: 
 330:     /**
 331:      * Returns the close-value for the specified series and item.
 332:      * <P>
 333:      * Note:  throws <code>ClassCastException</code> if the series is not from a
 334:      * {@link OHLCDataset}.
 335:      *
 336:      * @param series  the index of the series of interest (zero-based).
 337:      * @param item  the index of the item of interest (zero-based).
 338:      *
 339:      * @return The close-value for the specified series and item.
 340:      */
 341:     public Number getClose(int series, int item) {
 342:         DatasetInfo di = getDatasetInfo(series);
 343:         return ((OHLCDataset) di.data).getClose(di.series, item);
 344:     }
 345: 
 346:     /**
 347:      * Returns the close-value (as a double primitive) for an item within a 
 348:      * series.
 349:      * 
 350:      * @param series  the series (zero-based index).
 351:      * @param item  the item (zero-based index).
 352:      * 
 353:      * @return The close-value.
 354:      */
 355:     public double getCloseValue(int series, int item) {
 356:         double result = Double.NaN;
 357:         Number close = getClose(series, item);
 358:         if (close != null) {
 359:             result = close.doubleValue();   
 360:         }
 361:         return result;   
 362:     }
 363: 
 364:     /**
 365:      * Returns the volume value for the specified series and item.
 366:      * <P>
 367:      * Note:  throws <code>ClassCastException</code> if the series is not from a
 368:      * {@link OHLCDataset}.
 369:      *
 370:      * @param series  the index of the series of interest (zero-based).
 371:      * @param item  the index of the item of interest (zero-based).
 372:      *
 373:      * @return The volume value for the specified series and item.
 374:      */
 375:     public Number getVolume(int series, int item) {
 376:         DatasetInfo di = getDatasetInfo(series);
 377:         return ((OHLCDataset) di.data).getVolume(di.series, item);
 378:     }
 379: 
 380:     /**
 381:      * Returns the volume-value (as a double primitive) for an item within a 
 382:      * series.
 383:      * 
 384:      * @param series  the series (zero-based index).
 385:      * @param item  the item (zero-based index).
 386:      * 
 387:      * @return The volume-value.
 388:      */
 389:     public double getVolumeValue(int series, int item) {
 390:         double result = Double.NaN;
 391:         Number volume = getVolume(series, item);
 392:         if (volume != null) {
 393:             result = volume.doubleValue();   
 394:         }
 395:         return result;   
 396:     }
 397: 
 398:     ///////////////////////////////////////////////////////////////////////////
 399:     // From IntervalXYDataset
 400:     ///////////////////////////////////////////////////////////////////////////
 401: 
 402:     /**
 403:      * Returns the starting X value for the specified series and item.
 404:      *
 405:      * @param series  the index of the series of interest (zero-based).
 406:      * @param item  the index of the item of interest (zero-based).
 407:      *
 408:      * @return The value.
 409:      */
 410:     public Number getStartX(int series, int item) {
 411:         DatasetInfo di = getDatasetInfo(series);
 412:         if (di.data instanceof IntervalXYDataset) {
 413:             return ((IntervalXYDataset) di.data).getStartX(di.series, item);
 414:         }
 415:         else {
 416:             return getX(series, item);
 417:         }
 418:     }
 419: 
 420:     /**
 421:      * Returns the ending X value for the specified series and item.
 422:      *
 423:      * @param series  the index of the series of interest (zero-based).
 424:      * @param item  the index of the item of interest (zero-based).
 425:      *
 426:      * @return The value.
 427:      */
 428:     public Number getEndX(int series, int item) {
 429:         DatasetInfo di = getDatasetInfo(series);
 430:         if (di.data instanceof IntervalXYDataset) {
 431:             return ((IntervalXYDataset) di.data).getEndX(di.series, item);
 432:         }
 433:         else {
 434:             return getX(series, item);
 435:         }
 436:     }
 437: 
 438:     /**
 439:      * Returns the starting Y value for the specified series and item.
 440:      *
 441:      * @param series  the index of the series of interest (zero-based).
 442:      * @param item  the index of the item of interest (zero-based).
 443:      *
 444:      * @return The starting Y value for the specified series and item.
 445:      */
 446:     public Number getStartY(int series, int item) {
 447:         DatasetInfo di = getDatasetInfo(series);
 448:         if (di.data instanceof IntervalXYDataset) {
 449:             return ((IntervalXYDataset) di.data).getStartY(di.series, item);
 450:         }
 451:         else {
 452:             return getY(series, item);
 453:         }
 454:     }
 455: 
 456:     /**
 457:      * Returns the ending Y value for the specified series and item.
 458:      *
 459:      * @param series  the index of the series of interest (zero-based).
 460:      * @param item  the index of the item of interest (zero-based).
 461:      *
 462:      * @return The ending Y value for the specified series and item.
 463:      */
 464:     public Number getEndY(int series, int item) {
 465:         DatasetInfo di = getDatasetInfo(series);
 466:         if (di.data instanceof IntervalXYDataset) {
 467:             return ((IntervalXYDataset) di.data).getEndY(di.series, item);
 468:         }
 469:         else {
 470:             return getY(series, item);
 471:         }
 472:     }
 473: 
 474:     ///////////////////////////////////////////////////////////////////////////
 475:     // New methods from CombinationDataset
 476:     ///////////////////////////////////////////////////////////////////////////
 477: 
 478:     /**
 479:      * Returns the parent Dataset of this combination. If there is more than
 480:      * one parent, or a child is found that is not a CombinationDataset, then
 481:      * returns <code>null</code>.
 482:      *
 483:      * @return The parent Dataset of this combination or <code>null</code>.
 484:      */
 485:     public SeriesDataset getParent() {
 486: 
 487:         SeriesDataset parent = null;
 488:         for (int i = 0; i < this.datasetInfo.size(); i++) {
 489:             SeriesDataset child = getDatasetInfo(i).data;
 490:             if (child instanceof CombinationDataset) {
 491:                 SeriesDataset childParent 
 492:                     = ((CombinationDataset) child).getParent();
 493:                 if (parent == null) {
 494:                     parent = childParent;
 495:                 }
 496:                 else if (parent != childParent) {
 497:                     return null;
 498:                 }
 499:             }
 500:             else {
 501:                 return null;
 502:             }
 503:         }
 504:         return parent;
 505: 
 506:     }
 507: 
 508:     /**
 509:      * Returns a map or indirect indexing form our series into parent's series.
 510:      * Prior to calling this method, the client should check getParent() to make
 511:      * sure the CombinationDataset uses the same parent. If not, the map
 512:      * returned by this method will be invalid or null.
 513:      *
 514:      * @return A map or indirect indexing form our series into parent's series.
 515:      *
 516:      * @see #getParent()
 517:      */
 518:     public int[] getMap() {
 519: 
 520:         int[] map = null;
 521:         for (int i = 0; i < this.datasetInfo.size(); i++) {
 522:             SeriesDataset child = getDatasetInfo(i).data;
 523:             if (child instanceof CombinationDataset) {
 524:                 int[] childMap = ((CombinationDataset) child).getMap();
 525:                 if (childMap == null) {
 526:                     return null;
 527:                 }
 528:                 map = joinMap(map, childMap);
 529:             }
 530:             else {
 531:                 return null;
 532:             }
 533:         }
 534:         return map;
 535:     }
 536: 
 537:     ///////////////////////////////////////////////////////////////////////////
 538:     // New Methods
 539:     ///////////////////////////////////////////////////////////////////////////
 540: 
 541:     /**
 542:      * Returns the child position.
 543:      *
 544:      * @param child  the child dataset.
 545:      *
 546:      * @return The position.
 547:      */
 548:     public int getChildPosition(Dataset child) {
 549: 
 550:         int n = 0;
 551:         for (int i = 0; i < this.datasetInfo.size(); i++) {
 552:             SeriesDataset childDataset = getDatasetInfo(i).data;
 553:             if (childDataset instanceof CombinedDataset) {
 554:                 int m = ((CombinedDataset) childDataset)
 555:                     .getChildPosition(child);
 556:                 if (m >= 0) {
 557:                     return n + m;
 558:                 }
 559:                 n++;
 560:             }
 561:             else {
 562:                 if (child == childDataset) {
 563:                     return n;
 564:                 }
 565:                 n++;
 566:             }
 567:         }
 568:         return -1;
 569:     }
 570: 
 571:     ///////////////////////////////////////////////////////////////////////////
 572:     // Private
 573:     ///////////////////////////////////////////////////////////////////////////
 574: 
 575:     /**
 576:      * Returns the DatasetInfo object associated with the series.
 577:      *
 578:      * @param series  the index of the series.
 579:      *
 580:      * @return The DatasetInfo object associated with the series.
 581:      */
 582:     private DatasetInfo getDatasetInfo(int series) {
 583:         return (DatasetInfo) this.datasetInfo.get(series);
 584:     }
 585: 
 586:     /**
 587:      * Joins two map arrays (int[]) together.
 588:      *
 589:      * @param a  the first array.
 590:      * @param b  the second array.
 591:      *
 592:      * @return A copy of { a[], b[] }.
 593:      */
 594:     private int[] joinMap(int[] a, int[] b) {
 595:         if (a == null) {
 596:             return b;
 597:         }
 598:         if (b == null) {
 599:             return a;
 600:         }
 601:         int[] result = new int[a.length + b.length];
 602:         System.arraycopy(a, 0, result, 0, a.length);
 603:         System.arraycopy(b, 0, result, a.length, b.length);
 604:         return result;
 605:     }
 606: 
 607:     /**
 608:      * Private class to store as pairs (SeriesDataset, series) for all combined
 609:      * series.
 610:      */
 611:     private class DatasetInfo {
 612: 
 613:         /** The dataset. */
 614:         private SeriesDataset data;
 615: 
 616:         /** The series. */
 617:         private int series;
 618: 
 619:         /**
 620:          * Creates a new dataset info record.
 621:          *
 622:          * @param data  the dataset.
 623:          * @param series  the series.
 624:          */
 625:         DatasetInfo(SeriesDataset data, int series) {
 626:             this.data = data;
 627:             this.series = series;
 628:         }
 629:     }
 630: 
 631: }