    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
    057    package org.jfree.data.general;
    058
    059    import java.util.List;
    060
    061    import org.jfree.data.xy.AbstractIntervalXYDataset;
    062    import org.jfree.data.xy.IntervalXYDataset;
    063    import org.jfree.data.xy.OHLCDataset;
    064    import 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     */
    075    public 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    }