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