001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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     * TimePeriodValuesCollection.java
029     * -------------------------------
030     * (C) Copyright 2003-2008, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 22-Apr-2003 : Version 1 (DG);
038     * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
039     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
040     *               getYValue() (DG);
041     * 06-Oct-2004 : Updated for changes in DomainInfo interface (DG);
042     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
043     * ------------- JFREECHART 1.0.x ---------------------------------------------
044     * 03-Oct-2006 : Deprecated get/setDomainIsPointsInTime() (DG);
045     * 11-Jun-2007 : Fixed bug in getDomainBounds() method, and changed default
046     *               value for domainIsPointsInTime to false (DG);
047     *
048     */
049    
050    package org.jfree.data.time;
051    
052    import java.io.Serializable;
053    import java.util.Iterator;
054    import java.util.List;
055    
056    import org.jfree.data.DomainInfo;
057    import org.jfree.data.Range;
058    import org.jfree.data.xy.AbstractIntervalXYDataset;
059    import org.jfree.data.xy.IntervalXYDataset;
060    import org.jfree.util.ObjectUtilities;
061    
062    /**
063     * A collection of {@link TimePeriodValues} objects.
064     * <P>
065     * This class implements the {@link org.jfree.data.xy.XYDataset} interface, as
066     * well as the extended {@link IntervalXYDataset} interface.  This makes it a
067     * convenient dataset for use with the {@link org.jfree.chart.plot.XYPlot}
068     * class.
069     */
070    public class TimePeriodValuesCollection extends AbstractIntervalXYDataset
071            implements IntervalXYDataset, DomainInfo, Serializable {
072    
073        /** For serialization. */
074        private static final long serialVersionUID = -3077934065236454199L;
075    
076        /** Storage for the time series. */
077        private List data;
078    
079        /**
080         * The position within a time period to return as the x-value (START,
081         * MIDDLE or END).
082         */
083        private TimePeriodAnchor xPosition;
084    
085        /**
086         * A flag that indicates that the domain is 'points in time'.  If this
087         * flag is true, only the x-value is used to determine the range of values
088         * in the domain, the start and end x-values are ignored.
089         */
090        private boolean domainIsPointsInTime;
091    
092        /**
093         * Constructs an empty dataset.
094         */
095        public TimePeriodValuesCollection() {
096            this((TimePeriodValues) null);
097        }
098    
099        /**
100         * Constructs a dataset containing a single series.  Additional series can
101         * be added.
102         *
103         * @param series  the series (<code>null</code> ignored).
104         */
105        public TimePeriodValuesCollection(TimePeriodValues series) {
106            this.data = new java.util.ArrayList();
107            this.xPosition = TimePeriodAnchor.MIDDLE;
108            this.domainIsPointsInTime = false;
109            if (series != null) {
110                this.data.add(series);
111                series.addChangeListener(this);
112            }
113        }
114    
115        /**
116         * Returns the position of the X value within each time period.
117         *
118         * @return The position (never <code>null</code>).
119         *
120         * @see #setXPosition(TimePeriodAnchor)
121         */
122        public TimePeriodAnchor getXPosition() {
123            return this.xPosition;
124        }
125    
126        /**
127         * Sets the position of the x axis within each time period.
128         *
129         * @param position  the position (<code>null</code> not permitted).
130         *
131         * @see #getXPosition()
132         */
133        public void setXPosition(TimePeriodAnchor position) {
134            if (position == null) {
135                throw new IllegalArgumentException("Null 'position' argument.");
136            }
137            this.xPosition = position;
138        }
139    
140        /**
141         * Returns the number of series in the collection.
142         *
143         * @return The series count.
144         */
145        public int getSeriesCount() {
146            return this.data.size();
147        }
148    
149        /**
150         * Returns a series.
151         *
152         * @param series  the index of the series (zero-based).
153         *
154         * @return The series.
155         */
156        public TimePeriodValues getSeries(int series) {
157            if ((series < 0) || (series >= getSeriesCount())) {
158                throw new IllegalArgumentException("Index 'series' out of range.");
159            }
160            return (TimePeriodValues) this.data.get(series);
161        }
162    
163        /**
164         * Returns the key for a series.
165         *
166         * @param series  the index of the series (zero-based).
167         *
168         * @return The key for a series.
169         */
170        public Comparable getSeriesKey(int series) {
171            // defer argument checking
172            return getSeries(series).getKey();
173        }
174    
175        /**
176         * Adds a series to the collection.  A
177         * {@link org.jfree.data.general.DatasetChangeEvent} is sent to all
178         * registered listeners.
179         *
180         * @param series  the time series.
181         */
182        public void addSeries(TimePeriodValues series) {
183    
184            if (series == null) {
185                throw new IllegalArgumentException("Null 'series' argument.");
186            }
187    
188            this.data.add(series);
189            series.addChangeListener(this);
190            fireDatasetChanged();
191    
192        }
193    
194        /**
195         * Removes the specified series from the collection.
196         *
197         * @param series  the series to remove (<code>null</code> not permitted).
198         */
199        public void removeSeries(TimePeriodValues series) {
200    
201            if (series == null) {
202                throw new IllegalArgumentException("Null 'series' argument.");
203            }
204            this.data.remove(series);
205            series.removeChangeListener(this);
206            fireDatasetChanged();
207    
208        }
209    
210        /**
211         * Removes a series from the collection.
212         *
213         * @param index  the series index (zero-based).
214         */
215        public void removeSeries(int index) {
216            TimePeriodValues series = getSeries(index);
217            if (series != null) {
218                removeSeries(series);
219            }
220        }
221    
222        /**
223         * Returns the number of items in the specified series.
224         * <P>
225         * This method is provided for convenience.
226         *
227         * @param series  the index of the series of interest (zero-based).
228         *
229         * @return The number of items in the specified series.
230         */
231        public int getItemCount(int series) {
232            return getSeries(series).getItemCount();
233        }
234    
235        /**
236         * Returns the x-value for the specified series and item.
237         *
238         * @param series  the series (zero-based index).
239         * @param item  the item (zero-based index).
240         *
241         * @return The x-value for the specified series and item.
242         */
243        public Number getX(int series, int item) {
244            TimePeriodValues ts = (TimePeriodValues) this.data.get(series);
245            TimePeriodValue dp = ts.getDataItem(item);
246            TimePeriod period = dp.getPeriod();
247            return new Long(getX(period));
248        }
249    
250        /**
251         * Returns the x-value for a time period.
252         *
253         * @param period  the time period.
254         *
255         * @return The x-value.
256         */
257        private long getX(TimePeriod period) {
258    
259            if (this.xPosition == TimePeriodAnchor.START) {
260                return period.getStart().getTime();
261            }
262            else if (this.xPosition == TimePeriodAnchor.MIDDLE) {
263                return period.getStart().getTime()
264                    / 2 + period.getEnd().getTime() / 2;
265            }
266            else if (this.xPosition == TimePeriodAnchor.END) {
267                return period.getEnd().getTime();
268            }
269            else {
270                throw new IllegalStateException("TimePeriodAnchor unknown.");
271            }
272    
273        }
274    
275        /**
276         * Returns the starting X value for the specified series and item.
277         *
278         * @param series  the series (zero-based index).
279         * @param item  the item (zero-based index).
280         *
281         * @return The starting X value for the specified series and item.
282         */
283        public Number getStartX(int series, int item) {
284            TimePeriodValues ts = (TimePeriodValues) this.data.get(series);
285            TimePeriodValue dp = ts.getDataItem(item);
286            return new Long(dp.getPeriod().getStart().getTime());
287        }
288    
289        /**
290         * Returns the ending X value for the specified series and item.
291         *
292         * @param series  the series (zero-based index).
293         * @param item  the item (zero-based index).
294         *
295         * @return The ending X value for the specified series and item.
296         */
297        public Number getEndX(int series, int item) {
298            TimePeriodValues ts = (TimePeriodValues) this.data.get(series);
299            TimePeriodValue dp = ts.getDataItem(item);
300            return new Long(dp.getPeriod().getEnd().getTime());
301        }
302    
303        /**
304         * Returns the y-value for the specified series and item.
305         *
306         * @param series  the series (zero-based index).
307         * @param item  the item (zero-based index).
308         *
309         * @return The y-value for the specified series and item.
310         */
311        public Number getY(int series, int item) {
312            TimePeriodValues ts = (TimePeriodValues) this.data.get(series);
313            TimePeriodValue dp = ts.getDataItem(item);
314            return dp.getValue();
315        }
316    
317        /**
318         * Returns the starting Y value for the specified series and item.
319         *
320         * @param series  the series (zero-based index).
321         * @param item  the item (zero-based index).
322         *
323         * @return The starting Y value for the specified series and item.
324         */
325        public Number getStartY(int series, int item) {
326            return getY(series, item);
327        }
328    
329        /**
330         * Returns the ending Y value for the specified series and item.
331         *
332         * @param series  the series (zero-based index).
333         * @param item  the item (zero-based index).
334         *
335         * @return The ending Y value for the specified series and item.
336         */
337        public Number getEndY(int series, int item) {
338            return getY(series, item);
339        }
340    
341        /**
342         * Returns the minimum x-value in the dataset.
343         *
344         * @param includeInterval  a flag that determines whether or not the
345         *                         x-interval is taken into account.
346         *
347         * @return The minimum value.
348         */
349        public double getDomainLowerBound(boolean includeInterval) {
350            double result = Double.NaN;
351            Range r = getDomainBounds(includeInterval);
352            if (r != null) {
353                result = r.getLowerBound();
354            }
355            return result;
356        }
357    
358        /**
359         * Returns the maximum x-value in the dataset.
360         *
361         * @param includeInterval  a flag that determines whether or not the
362         *                         x-interval is taken into account.
363         *
364         * @return The maximum value.
365         */
366        public double getDomainUpperBound(boolean includeInterval) {
367            double result = Double.NaN;
368            Range r = getDomainBounds(includeInterval);
369            if (r != null) {
370                result = r.getUpperBound();
371            }
372            return result;
373        }
374    
375        /**
376         * Returns the range of the values in this dataset's domain.
377         *
378         * @param includeInterval  a flag that determines whether or not the
379         *                         x-interval is taken into account.
380         *
381         * @return The range.
382         */
383        public Range getDomainBounds(boolean includeInterval) {
384            boolean interval = includeInterval || this.domainIsPointsInTime;
385            Range result = null;
386            Range temp = null;
387            Iterator iterator = this.data.iterator();
388            while (iterator.hasNext()) {
389                TimePeriodValues series = (TimePeriodValues) iterator.next();
390                int count = series.getItemCount();
391                if (count > 0) {
392                    TimePeriod start = series.getTimePeriod(
393                            series.getMinStartIndex());
394                    TimePeriod end = series.getTimePeriod(series.getMaxEndIndex());
395                    if (!interval) {
396                        if (this.xPosition == TimePeriodAnchor.START) {
397                            TimePeriod maxStart = series.getTimePeriod(
398                                    series.getMaxStartIndex());
399                            temp = new Range(start.getStart().getTime(),
400                                    maxStart.getStart().getTime());
401                        }
402                        else if (this.xPosition == TimePeriodAnchor.MIDDLE) {
403                            TimePeriod minMiddle = series.getTimePeriod(
404                                    series.getMinMiddleIndex());
405                            long s1 = minMiddle.getStart().getTime();
406                            long e1 = minMiddle.getEnd().getTime();
407                            TimePeriod maxMiddle = series.getTimePeriod(
408                                    series.getMaxMiddleIndex());
409                            long s2 = maxMiddle.getStart().getTime();
410                            long e2 = maxMiddle.getEnd().getTime();
411                            temp = new Range(s1 + (e1 - s1) / 2,
412                                    s2 + (e2 - s2) / 2);
413                        }
414                        else if (this.xPosition == TimePeriodAnchor.END) {
415                            TimePeriod minEnd = series.getTimePeriod(
416                                    series.getMinEndIndex());
417                            temp = new Range(minEnd.getEnd().getTime(),
418                                    end.getEnd().getTime());
419                        }
420                    }
421                    else {
422                        temp = new Range(start.getStart().getTime(),
423                                end.getEnd().getTime());
424                    }
425                    result = Range.combine(result, temp);
426                }
427            }
428            return result;
429        }
430    
431        /**
432         * Tests this instance for equality with an arbitrary object.
433         *
434         * @param obj  the object (<code>null</code> permitted).
435         *
436         * @return A boolean.
437         */
438        public boolean equals(Object obj) {
439            if (obj == this) {
440                return true;
441            }
442            if (!(obj instanceof TimePeriodValuesCollection)) {
443                return false;
444            }
445            TimePeriodValuesCollection that = (TimePeriodValuesCollection) obj;
446            if (this.domainIsPointsInTime != that.domainIsPointsInTime) {
447                return false;
448            }
449            if (this.xPosition != that.xPosition) {
450                return false;
451            }
452            if (!ObjectUtilities.equal(this.data, that.data)) {
453                return false;
454            }
455            return true;
456        }
457    
458        // --- DEPRECATED METHODS -------------------------------------------------
459    
460        /**
461         * Returns a flag that controls whether the domain is treated as 'points
462         * in time'.  This flag is used when determining the max and min values for
463         * the domain.  If true, then only the x-values are considered for the max
464         * and min values.  If false, then the start and end x-values will also be
465         * taken into consideration
466         *
467         * @return The flag.
468         *
469         * @deprecated This flag is no longer used by JFreeChart (as of version
470         *     1.0.3).
471         */
472        public boolean getDomainIsPointsInTime() {
473            return this.domainIsPointsInTime;
474        }
475    
476        /**
477         * Sets a flag that controls whether the domain is treated as 'points in
478         * time', or time periods.
479         *
480         * @param flag  the new value of the flag.
481         *
482         * @deprecated This flag is no longer used by JFreeChart (as of version
483         *     1.0.3).
484         */
485        public void setDomainIsPointsInTime(boolean flag) {
486            this.domainIsPointsInTime = flag;
487        }
488    
489    }