Frames | No Frames |
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: }