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     * $Id: CombinedDataset.java,v 1.6.2.2 2007/02/02 15:50:44 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     * ------------- JFREECHART 1.0.x ---------------------------------------------
054     * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
055     *
056     */
057    
058    package org.jfree.data.general;
059    
060    import java.util.List;
061    
062    import org.jfree.data.xy.AbstractIntervalXYDataset;
063    import org.jfree.data.xy.OHLCDataset;
064    import org.jfree.data.xy.IntervalXYDataset;
065    import org.jfree.data.xy.XYDataset;
066    
067    /**
068     * This class can combine instances of {@link XYDataset}, {@link OHLCDataset} 
069     * and {@link IntervalXYDataset} together exposing the union of all the series 
070     * under one dataset.  
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    }