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