    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * --------------------
    028 * CombinedDataset.java
    029 * --------------------
    030 * (C) Copyright 2001-2009, by Bill Kelemen and Contributors.
    031 *
    032 * Original Author:  Bill Kelemen;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *
    035 * Changes
    036 * -------
    037 * 06-Dec-2001 : Version 1 (BK);
    038 * 27-Dec-2001 : Fixed bug in getChildPosition method (BK);
    039 * 29-Dec-2001 : Fixed bug in getChildPosition method with complex
    040 *               CombinePlot (BK);
    041 * 05-Feb-2002 : Small addition to the interface HighLowDataset, as requested
    042 *               by Sylvain Vieujot (DG);
    043 * 14-Feb-2002 : Added bug fix for IntervalXYDataset methods, submitted by
    044 *               Gyula Kun-Szabo (DG);
    045 * 11-Jun-2002 : Updated for change in event constructor (DG);
    046 * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    047 * 06-May-2004 : Now extends AbstractIntervalXYDataset and added other methods
    048 *               that return double primitives (DG);
    049 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    050 *               getYValue() (DG);
    051 * ------------- JFREECHART 1.0.x ---------------------------------------------
    052 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
    053 * 04-Feb-2009 : Deprecated the class (DG);
    054 *
    055 */
    056
    057package org.jfree.data.general;
    058
    059import java.util.List;
    060
    061import org.jfree.data.xy.AbstractIntervalXYDataset;
    062import org.jfree.data.xy.IntervalXYDataset;
    063import org.jfree.data.xy.OHLCDataset;
    064import org.jfree.data.xy.XYDataset;
    065
    066/**
    067 * This class can combine instances of {@link XYDataset}, {@link OHLCDataset}
    068 * and {@link IntervalXYDataset} together exposing the union of all the series
    069 * under one dataset.
    070 *
    071 * @deprecated As of version 1.0.13.  This class will be removed from
    072 *     JFreeChart 1.2.0 onwards.  Anyone needing this facility will need to
    073 *     maintain it outside of JFreeChart.
    074 */
    075public class CombinedDataset extends AbstractIntervalXYDataset
    076        implements XYDataset, OHLCDataset, IntervalXYDataset,
    077        CombinationDataset {
    078
    079    /** Storage for the datasets we combine. */
    080    private List datasetInfo = new java.util.ArrayList();
    081
    082    /**
    083     * Default constructor for an empty combination.
    084     */
    085    public CombinedDataset() {
    086        super();
    087    }
    088
    089    /**
    090     * Creates a CombinedDataset initialized with an array of SeriesDatasets.
    091     *
    092     * @param data  array of SeriesDataset that contains the SeriesDatasets to
    093     *              combine.
    094     */
    095    public CombinedDataset(SeriesDataset[] data) {
    096        add(data);
    097    }
    098
    099    /**
    100     * Adds one SeriesDataset to the combination. Listeners are notified of the
    101     * change.
    102     *
    103     * @param data  the SeriesDataset to add.
    104     */
    105    public void add(SeriesDataset data) {
    106        fastAdd(data);
    107        DatasetChangeEvent event = new DatasetChangeEvent(this, this);
    108        notifyListeners(event);
    109    }
    110
    111    /**
    112     * Adds an array of SeriesDataset's to the combination. Listeners are
    113     * notified of the change.
    114     *
    115     * @param data  array of SeriesDataset to add
    116     */
    117    public void add(SeriesDataset[] data) {
    118
    119        for (int i = 0; i < data.length; i++) {
    120            fastAdd(data[i]);
    121        }
    122        DatasetChangeEvent event = new DatasetChangeEvent(this, this);
    123        notifyListeners(event);
    124
    125    }
    126
    127    /**
    128     * Adds one series from a SeriesDataset to the combination. Listeners are
    129     * notified of the change.
    130     *
    131     * @param data  the SeriesDataset where series is contained
    132     * @param series  series to add
    133     */
    134    public void add(SeriesDataset data, int series) {
    135        add(new SubSeriesDataset(data, series));
    136    }
    137
    138    /**
    139     * Fast add of a SeriesDataset. Does not notify listeners of the change.
    140     *
    141     * @param data  SeriesDataset to add
    142     */
    143    private void fastAdd(SeriesDataset data) {
    144        for (int i = 0; i < data.getSeriesCount(); i++) {
    145            this.datasetInfo.add(new DatasetInfo(data, i));
    146        }
    147    }
    148
    149    ///////////////////////////////////////////////////////////////////////////
    150    // From SeriesDataset
    151    ///////////////////////////////////////////////////////////////////////////
    152
    153    /**
    154     * Returns the number of series in the dataset.
    155     *
    156     * @return The number of series in the dataset.
    157     */
    158    public int getSeriesCount() {
    159        return this.datasetInfo.size();
    160    }
    161
    162    /**
    163     * Returns the key for a series.
    164     *
    165     * @param series  the series (zero-based index).
    166     *
    167     * @return The key for a series.
    168     */
    169    public Comparable getSeriesKey(int series) {
    170        DatasetInfo di = getDatasetInfo(series);
    171        return di.data.getSeriesKey(di.series);
    172    }
    173
    174    ///////////////////////////////////////////////////////////////////////////
    175    // From XYDataset
    176    ///////////////////////////////////////////////////////////////////////////
    177
    178    /**
    179     * Returns the X-value for the specified series and item.
    180     * <P>
    181     * Note:  throws <code>ClassCastException</code> if the series is not from
    182     * a {@link XYDataset}.
    183     *
    184     * @param series  the index of the series of interest (zero-based).
    185     * @param item  the index of the item of interest (zero-based).
    186     *
    187     * @return The X-value for the specified series and item.
    188     */
    189    public Number getX(int series, int item) {
    190        DatasetInfo di = getDatasetInfo(series);
    191        return ((XYDataset) di.data).getX(di.series, item);
    192    }
    193
    194    /**
    195     * Returns the Y-value for the specified series and item.
    196     * <P>
    197     * Note:  throws <code>ClassCastException</code> if the series is not from
    198     * a {@link XYDataset}.
    199     *
    200     * @param series  the index of the series of interest (zero-based).
    201     * @param item  the index of the item of interest (zero-based).
    202     *
    203     * @return The Y-value for the specified series and item.
    204     */
    205    public Number getY(int series, int item) {
    206        DatasetInfo di = getDatasetInfo(series);
    207        return ((XYDataset) di.data).getY(di.series, item);
    208    }
    209
    210    /**
    211     * Returns the number of items in a series.
    212     * <P>
    213     * Note:  throws <code>ClassCastException</code> if the series is not from
    214     * a {@link XYDataset}.
    215     *
    216     * @param series  the index of the series of interest (zero-based).
    217     *
    218     * @return The number of items in a series.
    219     */
    220    public int getItemCount(int series) {
    221        DatasetInfo di = getDatasetInfo(series);
    222        return ((XYDataset) di.data).getItemCount(di.series);
    223    }
    224
    225    ///////////////////////////////////////////////////////////////////////////
    226    // From HighLowDataset
    227    ///////////////////////////////////////////////////////////////////////////
    228
    229    /**
    230     * Returns the high-value for the specified series and item.
    231     * <P>
    232     * Note:  throws <code>ClassCastException</code> if the series is not from a
    233     * {@link OHLCDataset}.
    234     *
    235     * @param series  the index of the series of interest (zero-based).
    236     * @param item  the index of the item of interest (zero-based).
    237     *
    238     * @return The high-value for the specified series and item.
    239     */
    240    public Number getHigh(int series, int item) {
    241        DatasetInfo di = getDatasetInfo(series);
    242        return ((OHLCDataset) di.data).getHigh(di.series, item);
    243    }
    244
    245    /**
    246     * Returns the high-value (as a double primitive) for an item within a
    247     * series.
    248     *
    249     * @param series  the series (zero-based index).
    250     * @param item  the item (zero-based index).
    251     *
    252     * @return The high-value.
    253     */
    254    public double getHighValue(int series, int item) {
    255        double result = Double.NaN;
    256        Number high = getHigh(series, item);
    257        if (high != null) {
    258            result = high.doubleValue();
    259        }
    260        return result;
    261    }
    262
    263    /**
    264     * Returns the low-value for the specified series and item.
    265     * <P>
    266     * Note:  throws <code>ClassCastException</code> if the series is not from a
    267     * {@link OHLCDataset}.
    268     *
    269     * @param series  the index of the series of interest (zero-based).
    270     * @param item  the index of the item of interest (zero-based).
    271     *
    272     * @return The low-value for the specified series and item.
    273     */
    274    public Number getLow(int series, int item) {
    275        DatasetInfo di = getDatasetInfo(series);
    276        return ((OHLCDataset) di.data).getLow(di.series, item);
    277    }
    278
    279    /**
    280     * Returns the low-value (as a double primitive) for an item within a
    281     * series.
    282     *
    283     * @param series  the series (zero-based index).
    284     * @param item  the item (zero-based index).
    285     *
    286     * @return The low-value.
    287     */
    288    public double getLowValue(int series, int item) {
    289        double result = Double.NaN;
    290        Number low = getLow(series, item);
    291        if (low != null) {
    292            result = low.doubleValue();
    293        }
    294        return result;
    295    }
    296
    297    /**
    298     * Returns the open-value for the specified series and item.
    299     * <P>
    300     * Note:  throws <code>ClassCastException</code> if the series is not from a
    301     * {@link OHLCDataset}.
    302     *
    303     * @param series  the index of the series of interest (zero-based).
    304     * @param item  the index of the item of interest (zero-based).
    305     *
    306     * @return The open-value for the specified series and item.
    307     */
    308    public Number getOpen(int series, int item) {
    309        DatasetInfo di = getDatasetInfo(series);
    310        return ((OHLCDataset) di.data).getOpen(di.series, item);
    311    }
    312
    313    /**
    314     * Returns the open-value (as a double primitive) for an item within a
    315     * series.
    316     *
    317     * @param series  the series (zero-based index).
    318     * @param item  the item (zero-based index).
    319     *
    320     * @return The open-value.
    321     */
    322    public double getOpenValue(int series, int item) {
    323        double result = Double.NaN;
    324        Number open = getOpen(series, item);
    325        if (open != null) {
    326            result = open.doubleValue();
    327        }
    328        return result;
    329    }
    330
    331    /**
    332     * Returns the close-value for the specified series and item.
    333     * <P>
    334     * Note:  throws <code>ClassCastException</code> if the series is not from a
    335     * {@link OHLCDataset}.
    336     *
    337     * @param series  the index of the series of interest (zero-based).
    338     * @param item  the index of the item of interest (zero-based).
    339     *
    340     * @return The close-value for the specified series and item.
    341     */
    342    public Number getClose(int series, int item) {
    343        DatasetInfo di = getDatasetInfo(series);
    344        return ((OHLCDataset) di.data).getClose(di.series, item);
    345    }
    346
    347    /**
    348     * Returns the close-value (as a double primitive) for an item within a
    349     * series.
    350     *
    351     * @param series  the series (zero-based index).
    352     * @param item  the item (zero-based index).
    353     *
    354     * @return The close-value.
    355     */
    356    public double getCloseValue(int series, int item) {
    357        double result = Double.NaN;
    358        Number close = getClose(series, item);
    359        if (close != null) {
    360            result = close.doubleValue();
    361        }
    362        return result;
    363    }
    364
    365    /**
    366     * Returns the volume value for the specified series and item.
    367     * <P>
    368     * Note:  throws <code>ClassCastException</code> if the series is not from a
    369     * {@link OHLCDataset}.
    370     *
    371     * @param series  the index of the series of interest (zero-based).
    372     * @param item  the index of the item of interest (zero-based).
    373     *
    374     * @return The volume value for the specified series and item.
    375     */
    376    public Number getVolume(int series, int item) {
    377        DatasetInfo di = getDatasetInfo(series);
    378        return ((OHLCDataset) di.data).getVolume(di.series, item);
    379    }
    380
    381    /**
    382     * Returns the volume-value (as a double primitive) for an item within a
    383     * series.
    384     *
    385     * @param series  the series (zero-based index).
    386     * @param item  the item (zero-based index).
    387     *
    388     * @return The volume-value.
    389     */
    390    public double getVolumeValue(int series, int item) {
    391        double result = Double.NaN;
    392        Number volume = getVolume(series, item);
    393        if (volume != null) {
    394            result = volume.doubleValue();
    395        }
    396        return result;
    397    }
    398
    399    ///////////////////////////////////////////////////////////////////////////
    400    // From IntervalXYDataset
    401    ///////////////////////////////////////////////////////////////////////////
    402
    403    /**
    404     * Returns the starting X value for the specified series and item.
    405     *
    406     * @param series  the index of the series of interest (zero-based).
    407     * @param item  the index of the item of interest (zero-based).
    408     *
    409     * @return The value.
    410     */
    411    public Number getStartX(int series, int item) {
    412        DatasetInfo di = getDatasetInfo(series);
    413        if (di.data instanceof IntervalXYDataset) {
    414            return ((IntervalXYDataset) di.data).getStartX(di.series, item);
    415        }
    416        else {
    417            return getX(series, item);
    418        }
    419    }
    420
    421    /**
    422     * Returns the ending X value for the specified series and item.
    423     *
    424     * @param series  the index of the series of interest (zero-based).
    425     * @param item  the index of the item of interest (zero-based).
    426     *
    427     * @return The value.
    428     */
    429    public Number getEndX(int series, int item) {
    430        DatasetInfo di = getDatasetInfo(series);
    431        if (di.data instanceof IntervalXYDataset) {
    432            return ((IntervalXYDataset) di.data).getEndX(di.series, item);
    433        }
    434        else {
    435            return getX(series, item);
    436        }
    437    }
    438
    439    /**
    440     * Returns the starting Y value for the specified series and item.
    441     *
    442     * @param series  the index of the series of interest (zero-based).
    443     * @param item  the index of the item of interest (zero-based).
    444     *
    445     * @return The starting Y value for the specified series and item.
    446     */
    447    public Number getStartY(int series, int item) {
    448        DatasetInfo di = getDatasetInfo(series);
    449        if (di.data instanceof IntervalXYDataset) {
    450            return ((IntervalXYDataset) di.data).getStartY(di.series, item);
    451        }
    452        else {
    453            return getY(series, item);
    454        }
    455    }
    456
    457    /**
    458     * Returns the ending Y value for the specified series and item.
    459     *
    460     * @param series  the index of the series of interest (zero-based).
    461     * @param item  the index of the item of interest (zero-based).
    462     *
    463     * @return The ending Y value for the specified series and item.
    464     */
    465    public Number getEndY(int series, int item) {
    466        DatasetInfo di = getDatasetInfo(series);
    467        if (di.data instanceof IntervalXYDataset) {
    468            return ((IntervalXYDataset) di.data).getEndY(di.series, item);
    469        }
    470        else {
    471            return getY(series, item);
    472        }
    473    }
    474
    475    ///////////////////////////////////////////////////////////////////////////
    476    // New methods from CombinationDataset
    477    ///////////////////////////////////////////////////////////////////////////
    478
    479    /**
    480     * Returns the parent Dataset of this combination. If there is more than
    481     * one parent, or a child is found that is not a CombinationDataset, then
    482     * returns <code>null</code>.
    483     *
    484     * @return The parent Dataset of this combination or <code>null</code>.
    485     */
    486    public SeriesDataset getParent() {
    487
    488        SeriesDataset parent = null;
    489        for (int i = 0; i < this.datasetInfo.size(); i++) {
    490            SeriesDataset child = getDatasetInfo(i).data;
    491            if (child instanceof CombinationDataset) {
    492                SeriesDataset childParent
    493                    = ((CombinationDataset) child).getParent();
    494                if (parent == null) {
    495                    parent = childParent;
    496                }
    497                else if (parent != childParent) {
    498                    return null;
    499                }
    500            }
    501            else {
    502                return null;
    503            }
    504        }
    505        return parent;
    506
    507    }
    508
    509    /**
    510     * Returns a map or indirect indexing form our series into parent's series.
    511     * Prior to calling this method, the client should check getParent() to make
    512     * sure the CombinationDataset uses the same parent. If not, the map
    513     * returned by this method will be invalid or null.
    514     *
    515     * @return A map or indirect indexing form our series into parent's series.
    516     *
    517     * @see #getParent()
    518     */
    519    public int[] getMap() {
    520
    521        int[] map = null;
    522        for (int i = 0; i < this.datasetInfo.size(); i++) {
    523            SeriesDataset child = getDatasetInfo(i).data;
    524            if (child instanceof CombinationDataset) {
    525                int[] childMap = ((CombinationDataset) child).getMap();
    526                if (childMap == null) {
    527                    return null;
    528                }
    529                map = joinMap(map, childMap);
    530            }
    531            else {
    532                return null;
    533            }
    534        }
    535        return map;
    536    }
    537
    538    ///////////////////////////////////////////////////////////////////////////
    539    // New Methods
    540    ///////////////////////////////////////////////////////////////////////////
    541
    542    /**
    543     * Returns the child position.
    544     *
    545     * @param child  the child dataset.
    546     *
    547     * @return The position.
    548     */
    549    public int getChildPosition(Dataset child) {
    550
    551        int n = 0;
    552        for (int i = 0; i < this.datasetInfo.size(); i++) {
    553            SeriesDataset childDataset = getDatasetInfo(i).data;
    554            if (childDataset instanceof CombinedDataset) {
    555                int m = ((CombinedDataset) childDataset)
    556                    .getChildPosition(child);
    557                if (m >= 0) {
    558                    return n + m;
    559                }
    560                n++;
    561            }
    562            else {
    563                if (child == childDataset) {
    564                    return n;
    565                }
    566                n++;
    567            }
    568        }
    569        return -1;
    570    }
    571
    572    ///////////////////////////////////////////////////////////////////////////
    573    // Private
    574    ///////////////////////////////////////////////////////////////////////////
    575
    576    /**
    577     * Returns the DatasetInfo object associated with the series.
    578     *
    579     * @param series  the index of the series.
    580     *
    581     * @return The DatasetInfo object associated with the series.
    582     */
    583    private DatasetInfo getDatasetInfo(int series) {
    584        return (DatasetInfo) this.datasetInfo.get(series);
    585    }
    586
    587    /**
    588     * Joins two map arrays (int[]) together.
    589     *
    590     * @param a  the first array.
    591     * @param b  the second array.
    592     *
    593     * @return A copy of { a[], b[] }.
    594     */
    595    private int[] joinMap(int[] a, int[] b) {
    596        if (a == null) {
    597            return b;
    598        }
    599        if (b == null) {
    600            return a;
    601        }
    602        int[] result = new int[a.length + b.length];
    603        System.arraycopy(a, 0, result, 0, a.length);
    604        System.arraycopy(b, 0, result, a.length, b.length);
    605        return result;
    606    }
    607
    608    /**
    609     * Private class to store as pairs (SeriesDataset, series) for all combined
    610     * series.
    611     */
    612    private class DatasetInfo {
    613
    614        /** The dataset. */
    615        private SeriesDataset data;
    616
    617        /** The series. */
    618        private int series;
    619
    620        /**
    621         * Creates a new dataset info record.
    622         *
    623         * @param data  the dataset.
    624         * @param series  the series.
    625         */
    626        DatasetInfo(SeriesDataset data, int series) {
    627            this.data = data;
    628            this.series = series;
    629        }
    630    }
    631
    632}