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