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