    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, by Bill Kelemen and Contributors.
    031     *
    032     * Original Author:  Bill Kelemen;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: CombinedDataset.java,v 1.6.2.1 2005/10/25 21:32:29 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 06-Dec-2001 : Version 1 (BK);
    040     * 27-Dec-2001 : Fixed bug in getChildPosition method (BK);
    041     * 29-Dec-2001 : Fixed bug in getChildPosition method with complex
    042     *               CombinePlot (BK);
    043     * 05-Feb-2002 : Small addition to the interface HighLowDataset, as requested
    044     *               by Sylvain Vieujot (DG);
    045     * 14-Feb-2002 : Added bug fix for IntervalXYDataset methods, submitted by
    046     *               Gyula Kun-Szabo (DG);
    047     * 11-Jun-2002 : Updated for change in event constructor (DG);
    048     * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    049     * 06-May-2004 : Now extends AbstractIntervalXYDataset and added other methods
    050     *               that return double primitives (DG);
    051     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    052     *               getYValue() (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.OHLCDataset;
    062    import org.jfree.data.xy.IntervalXYDataset;
    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     * @author Bill Kelemen (bill@kelemen-usa.com)
    071     */
    072    public class CombinedDataset extends AbstractIntervalXYDataset
    073                                 implements XYDataset,
    074                                            OHLCDataset,
    075                                            IntervalXYDataset,
    076                                            CombinationDataset {
    077
    078        /** Storage for the datasets we combine. */
    079        private List datasetInfo = new java.util.ArrayList();
    080
    081        /**
    082         * Default constructor for an empty combination.
    083         */
    084        public CombinedDataset() {
    085            super();
    086        }
    087
    088        /**
    089         * Creates a CombinedDataset initialized with an array of SeriesDatasets.
    090         *
    091         * @param data  array of SeriesDataset that contains the SeriesDatasets to
    092         *              combine.
    093         */
    094        public CombinedDataset(SeriesDataset[] data) {
    095            add(data);
    096        }
    097
    098        /**
    099         * 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    }