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    }