001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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 * [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 * XYTaskDataset.java
029 * ------------------
030 * (C) Copyright 2008-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.data.gantt;
038
039import java.util.Date;
040
041import org.jfree.chart.axis.SymbolAxis;
042import org.jfree.chart.renderer.xy.XYBarRenderer;
043import org.jfree.chart.util.Args;
044import org.jfree.data.general.DatasetChangeEvent;
045import org.jfree.data.general.DatasetChangeListener;
046import org.jfree.data.time.TimePeriod;
047import org.jfree.data.xy.AbstractXYDataset;
048import org.jfree.data.xy.IntervalXYDataset;
049
050/**
051 * A dataset implementation that wraps a {@link TaskSeriesCollection} and
052 * presents it as an {@link IntervalXYDataset}, allowing a set of tasks to
053 * be displayed using an {@link XYBarRenderer} (and usually a
054 * {@link SymbolAxis}).  This is a very specialised dataset implementation
055 * ---before using it, you should take some time to understand the use-cases
056 * that it is designed for.
057 */
058public class XYTaskDataset extends AbstractXYDataset
059        implements IntervalXYDataset, DatasetChangeListener {
060
061    /** The underlying tasks. */
062    private TaskSeriesCollection underlying;
063
064    /** The series interval width (typically 0.0 < w <= 1.0). */
065    private double seriesWidth;
066
067    /** A flag that controls whether or not the data values are transposed. */
068    private boolean transposed;
069
070    /**
071     * Creates a new dataset based on the supplied collection of tasks.
072     *
073     * @param tasks  the underlying dataset ({@code null} not permitted).
074     */
075    public XYTaskDataset(TaskSeriesCollection tasks) {
076        Args.nullNotPermitted(tasks, "tasks");
077        this.underlying = tasks;
078        this.seriesWidth = 0.8;
079        this.underlying.addChangeListener(this);
080    }
081
082    /**
083     * Returns the underlying task series collection that was supplied to the
084     * constructor.
085     *
086     * @return The underlying collection (never {@code null}).
087     */
088    public TaskSeriesCollection getTasks() {
089        return this.underlying;
090    }
091
092    /**
093     * Returns the width of the interval for each series this dataset.
094     *
095     * @return The width of the series interval.
096     *
097     * @see #setSeriesWidth(double)
098     */
099    public double getSeriesWidth() {
100        return this.seriesWidth;
101    }
102
103    /**
104     * Sets the series interval width and sends a {@link DatasetChangeEvent} to
105     * all registered listeners.
106     *
107     * @param w  the width.
108     *
109     * @see #getSeriesWidth()
110     */
111    public void setSeriesWidth(double w) {
112        if (w <= 0.0) {
113            throw new IllegalArgumentException("Requires 'w' > 0.0.");
114        }
115        this.seriesWidth = w;
116        fireDatasetChanged();
117    }
118
119    /**
120     * Returns a flag that indicates whether or not the dataset is transposed.
121     * The default is {@code false} which means the x-values are integers
122     * corresponding to the series indices, and the y-values are millisecond
123     * values corresponding to the task date/time intervals.  If the flag
124     * is set to {@code true}, the x and y-values are reversed.
125     *
126     * @return The flag.
127     *
128     * @see #setTransposed(boolean)
129     */
130    public boolean isTransposed() {
131        return this.transposed;
132    }
133
134    /**
135     * Sets the flag that controls whether or not the dataset is transposed
136     * and sends a {@link DatasetChangeEvent} to all registered listeners.
137     *
138     * @param transposed  the new flag value.
139     *
140     * @see #isTransposed()
141     */
142    public void setTransposed(boolean transposed) {
143        this.transposed = transposed;
144        fireDatasetChanged();
145    }
146
147    /**
148     * Returns the number of series in the dataset.
149     *
150     * @return The series count.
151     */
152    @Override
153    public int getSeriesCount() {
154        return this.underlying.getSeriesCount();
155    }
156
157    /**
158     * Returns the name of a series.
159     *
160     * @param series  the series index (zero-based).
161     *
162     * @return The name of a series.
163     */
164    @Override
165    public Comparable getSeriesKey(int series) {
166        return this.underlying.getSeriesKey(series);
167    }
168
169    /**
170     * Returns the number of items (tasks) in the specified series.
171     *
172     * @param series  the series index (zero-based).
173     *
174     * @return The item count.
175     */
176    @Override
177    public int getItemCount(int series) {
178        return this.underlying.getSeries(series).getItemCount();
179    }
180
181    /**
182     * Returns the x-value (as a double primitive) for an item within a series.
183     *
184     * @param series  the series index (zero-based).
185     * @param item  the item index (zero-based).
186     *
187     * @return The value.
188     */
189    @Override
190    public double getXValue(int series, int item) {
191        if (!this.transposed) {
192            return getSeriesValue(series);
193        }
194        else {
195            return getItemValue(series, item);
196        }
197    }
198
199    /**
200     * Returns the starting date/time for the specified item (task) in the
201     * given series, measured in milliseconds since 1-Jan-1970 (as in
202     * java.util.Date).
203     *
204     * @param series  the series index.
205     * @param item  the item (or task) index.
206     *
207     * @return The start date/time.
208     */
209    @Override
210    public double getStartXValue(int series, int item) {
211        if (!this.transposed) {
212            return getSeriesStartValue(series);
213        }
214        else {
215            return getItemStartValue(series, item);
216        }
217    }
218
219    /**
220     * Returns the ending date/time for the specified item (task) in the
221     * given series, measured in milliseconds since 1-Jan-1970 (as in
222     * java.util.Date).
223     *
224     * @param series  the series index.
225     * @param item  the item (or task) index.
226     *
227     * @return The end date/time.
228     */
229    @Override
230    public double getEndXValue(int series, int item) {
231        if (!this.transposed) {
232            return getSeriesEndValue(series);
233        }
234        else {
235            return getItemEndValue(series, item);
236        }
237    }
238
239    /**
240     * Returns the x-value for the specified series.
241     *
242     * @param series  the series index.
243     * @param item  the item index.
244     *
245     * @return The x-value (in milliseconds).
246     */
247    @Override
248    public Number getX(int series, int item) {
249        return getXValue(series, item);
250    }
251
252    /**
253     * Returns the starting date/time for the specified item (task) in the
254     * given series, measured in milliseconds since 1-Jan-1970 (as in
255     * java.util.Date).
256     *
257     * @param series  the series index.
258     * @param item  the item (or task) index.
259     *
260     * @return The start date/time.
261     */
262    @Override
263    public Number getStartX(int series, int item) {
264        return getStartXValue(series, item);
265    }
266
267    /**
268     * Returns the ending date/time for the specified item (task) in the
269     * given series, measured in milliseconds since 1-Jan-1970 (as in
270     * java.util.Date).
271     *
272     * @param series  the series index.
273     * @param item  the item (or task) index.
274     *
275     * @return The end date/time.
276     */
277    @Override
278    public Number getEndX(int series, int item) {
279        return getEndXValue(series, item);
280    }
281
282    /**
283     * Returns the y-value (as a double primitive) for an item within a series.
284     *
285     * @param series  the series index (zero-based).
286     * @param item  the item index (zero-based).
287     *
288     * @return The value.
289     */
290    @Override
291    public double getYValue(int series, int item) {
292        if (!this.transposed) {
293            return getItemValue(series, item);
294        }
295        else {
296            return getSeriesValue(series);
297        }
298    }
299
300    /**
301     * Returns the starting value of the y-interval for an item in the
302     * given series.
303     *
304     * @param series  the series index.
305     * @param item  the item (or task) index.
306     *
307     * @return The y-interval start.
308     */
309    @Override
310    public double getStartYValue(int series, int item) {
311        if (!this.transposed) {
312            return getItemStartValue(series, item);
313        }
314        else {
315            return getSeriesStartValue(series);
316        }
317    }
318
319    /**
320     * Returns the ending value of the y-interval for an item in the
321     * given series.
322     *
323     * @param series  the series index.
324     * @param item  the item (or task) index.
325     *
326     * @return The y-interval end.
327     */
328    @Override
329    public double getEndYValue(int series, int item) {
330        if (!this.transposed) {
331            return getItemEndValue(series, item);
332        }
333        else {
334            return getSeriesEndValue(series);
335        }
336    }
337
338    /**
339     * Returns the y-value for the specified series/item.  In this
340     * implementation, we return the series index as the y-value (this means
341     * that every item in the series has a constant integer value).
342     *
343     * @param series  the series index.
344     * @param item  the item index.
345     *
346     * @return The y-value.
347     */
348    @Override
349    public Number getY(int series, int item) {
350        return getYValue(series, item);
351    }
352
353    /**
354     * Returns the starting value of the y-interval for an item in the
355     * given series.
356     *
357     * @param series  the series index.
358     * @param item  the item (or task) index.
359     *
360     * @return The y-interval start.
361     */
362    @Override
363    public Number getStartY(int series, int item) {
364        return getStartYValue(series, item);
365    }
366
367    /**
368     * Returns the ending value of the y-interval for an item in the
369     * given series.
370     *
371     * @param series  the series index.
372     * @param item  the item (or task) index.
373     *
374     * @return The y-interval end.
375     */
376    @Override
377    public Number getEndY(int series, int item) {
378        return getEndYValue(series, item);
379    }
380
381    private double getSeriesValue(int series) {
382        return series;
383    }
384
385    private double getSeriesStartValue(int series) {
386        return series - this.seriesWidth / 2.0;
387    }
388
389    private double getSeriesEndValue(int series) {
390        return series + this.seriesWidth / 2.0;
391    }
392
393    private double getItemValue(int series, int item) {
394        TaskSeries s = this.underlying.getSeries(series);
395        Task t = s.get(item);
396        TimePeriod duration = t.getDuration();
397        Date start = duration.getStart();
398        Date end = duration.getEnd();
399        return (start.getTime() + end.getTime()) / 2.0;
400    }
401
402    private double getItemStartValue(int series, int item) {
403        TaskSeries s = this.underlying.getSeries(series);
404        Task t = s.get(item);
405        TimePeriod duration = t.getDuration();
406        Date start = duration.getStart();
407        return start.getTime();
408    }
409
410    private double getItemEndValue(int series, int item) {
411        TaskSeries s = this.underlying.getSeries(series);
412        Task t = s.get(item);
413        TimePeriod duration = t.getDuration();
414        Date end = duration.getEnd();
415        return end.getTime();
416    }
417
418
419    /**
420     * Receives a change event from the underlying dataset and responds by
421     * firing a change event for this dataset.
422     *
423     * @param event  the event.
424     */
425    @Override
426    public void datasetChanged(DatasetChangeEvent event) {
427        fireDatasetChanged();
428    }
429
430    /**
431     * Tests this dataset for equality with an arbitrary object.
432     *
433     * @param obj  the object ({@code null} permitted).
434     *
435     * @return A boolean.
436     */
437    @Override
438    public boolean equals(Object obj) {
439        if (obj == this) {
440            return true;
441        }
442        if (!(obj instanceof XYTaskDataset)) {
443            return false;
444        }
445        XYTaskDataset that = (XYTaskDataset) obj;
446        if (this.seriesWidth != that.seriesWidth) {
447            return false;
448        }
449        if (this.transposed != that.transposed) {
450            return false;
451        }
452        if (!this.underlying.equals(that.underlying)) {
453            return false;
454        }
455        return true;
456    }
457
458    /**
459     * Returns a clone of this dataset.
460     *
461     * @return A clone of this dataset.
462     *
463     * @throws CloneNotSupportedException if there is a problem cloning.
464     */
465    @Override
466    public Object clone() throws CloneNotSupportedException {
467        XYTaskDataset clone = (XYTaskDataset) super.clone();
468        clone.underlying = (TaskSeriesCollection) this.underlying.clone();
469        return clone;
470    }
471
472}