    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * --------------------
    028     * CombinedDataset.java
    029     * --------------------
    030     * (C) Copyright 2001-2007, 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     *
    054     */
    055
    056    package org.jfree.data.general;
    057
    058    import java.util.List;
    059
    060    import org.jfree.data.xy.AbstractIntervalXYDataset;
    061    import org.jfree.data.xy.IntervalXYDataset;
    062    import org.jfree.data.xy.OHLCDataset;
    063    import org.jfree.data.xy.XYDataset;
    064
    065    /**
    066     * This class can combine instances of {@link XYDataset}, {@link OHLCDataset}
    067     * and {@link IntervalXYDataset} together exposing the union of all the series
    068     * under one dataset.
    069     */
    070    public class CombinedDataset extends AbstractIntervalXYDataset
    071                                 implements XYDataset,
    072                                            OHLCDataset,
    073                                            IntervalXYDataset,
    074                                            CombinationDataset {
    075
    076        /** Storage for the datasets we combine. */
    077        private List datasetInfo = new java.util.ArrayList();
    078
    079        /**
    080         * Default constructor for an empty combination.
    081         */
    082        public CombinedDataset() {
    083            super();
    084        }
    085
    086        /**
    087         * Creates a CombinedDataset initialized with an array of SeriesDatasets.
    088         *
    089         * @param data  array of SeriesDataset that contains the SeriesDatasets to
    090         *              combine.
    091         */
    092        public CombinedDataset(SeriesDataset[] data) {
    093            add(data);
    094        }
    095
    096        /**
    097         * Adds one SeriesDataset to the combination. Listeners are notified of the
    098         * change.
    099         *
    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    }