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