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