001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2009, 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-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    }