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