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