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: * 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: }