001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ------------------
028 * XYTaskDataset.java
029 * ------------------
030 * (C) Copyright 2008, 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 *
039 */
040
041package org.jfree.data.gantt;
042
043import java.util.Date;
044
045import org.jfree.chart.axis.SymbolAxis;
046import org.jfree.chart.renderer.xy.XYBarRenderer;
047import org.jfree.data.general.DatasetChangeEvent;
048import org.jfree.data.general.DatasetChangeListener;
049import org.jfree.data.time.TimePeriod;
050import org.jfree.data.xy.AbstractXYDataset;
051import org.jfree.data.xy.IntervalXYDataset;
052
053/**
054 * A dataset implementation that wraps a {@link TaskSeriesCollection} and
055 * presents it as an {@link IntervalXYDataset}, allowing a set of tasks to
056 * be displayed using an {@link XYBarRenderer} (and usually a
057 * {@link SymbolAxis}).  This is a very specialised dataset implementation
058 * ---before using it, you should take some time to understand the use-cases
059 * that it is designed for.
060 *
061 * @since 1.0.11
062 */
063public class XYTaskDataset extends AbstractXYDataset
064        implements IntervalXYDataset, DatasetChangeListener {
065
066    /** The underlying tasks. */
067    private TaskSeriesCollection underlying;
068
069    /** The series interval width (typically 0.0 < w <= 1.0). */
070    private double seriesWidth;
071
072    /** A flag that controls whether or not the data values are transposed. */
073    private boolean transposed;
074
075    /**
076     * Creates a new dataset based on the supplied collection of tasks.
077     *
078     * @param tasks  the underlying dataset (<code>null</code> not permitted).
079     */
080    public XYTaskDataset(TaskSeriesCollection tasks) {
081        if (tasks == null) {
082            throw new IllegalArgumentException("Null 'tasks' argument.");
083        }
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    public int getSeriesCount() {
160        return this.underlying.getSeriesCount();
161    }
162
163    /**
164     * Returns the name of a series.
165     *
166     * @param series  the series index (zero-based).
167     *
168     * @return The name of a series.
169     */
170    public Comparable getSeriesKey(int series) {
171        return this.underlying.getSeriesKey(series);
172    }
173
174    /**
175     * Returns the number of items (tasks) in the specified series.
176     *
177     * @param series  the series index (zero-based).
178     *
179     * @return The item count.
180     */
181    public int getItemCount(int series) {
182        return this.underlying.getSeries(series).getItemCount();
183    }
184
185    /**
186     * Returns the x-value (as a double primitive) for an item within a series.
187     *
188     * @param series  the series index (zero-based).
189     * @param item  the item index (zero-based).
190     *
191     * @return The value.
192     */
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    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    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    public Number getX(int series, int item) {
249        return new Double(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    public Number getStartX(int series, int item) {
263        return new Double(getStartXValue(series, item));
264    }
265
266    /**
267     * Returns the ending date/time for the specified item (task) in the
268     * given series, measured in milliseconds since 1-Jan-1970 (as in
269     * java.util.Date).
270     *
271     * @param series  the series index.
272     * @param item  the item (or task) index.
273     *
274     * @return The end date/time.
275     */
276    public Number getEndX(int series, int item) {
277        return new Double(getEndXValue(series, item));
278    }
279
280    /**
281     * Returns the y-value (as a double primitive) for an item within a series.
282     *
283     * @param series  the series index (zero-based).
284     * @param item  the item index (zero-based).
285     *
286     * @return The value.
287     */
288    public double getYValue(int series, int item) {
289        if (!this.transposed) {
290            return getItemValue(series, item);
291        }
292        else {
293            return getSeriesValue(series);
294        }
295    }
296
297    /**
298     * Returns the starting value of the y-interval for an item in the
299     * given series.
300     *
301     * @param series  the series index.
302     * @param item  the item (or task) index.
303     *
304     * @return The y-interval start.
305     */
306    public double getStartYValue(int series, int item) {
307        if (!this.transposed) {
308            return getItemStartValue(series, item);
309        }
310        else {
311            return getSeriesStartValue(series);
312        }
313    }
314
315    /**
316     * Returns the ending value of the y-interval for an item in the
317     * given series.
318     *
319     * @param series  the series index.
320     * @param item  the item (or task) index.
321     *
322     * @return The y-interval end.
323     */
324    public double getEndYValue(int series, int item) {
325        if (!this.transposed) {
326            return getItemEndValue(series, item);
327        }
328        else {
329            return getSeriesEndValue(series);
330        }
331    }
332
333    /**
334     * Returns the y-value for the specified series/item.  In this
335     * implementation, we return the series index as the y-value (this means
336     * that every item in the series has a constant integer value).
337     *
338     * @param series  the series index.
339     * @param item  the item index.
340     *
341     * @return The y-value.
342     */
343    public Number getY(int series, int item) {
344        return new Double(getYValue(series, item));
345    }
346
347    /**
348     * Returns the starting value of the y-interval for an item in the
349     * given series.
350     *
351     * @param series  the series index.
352     * @param item  the item (or task) index.
353     *
354     * @return The y-interval start.
355     */
356    public Number getStartY(int series, int item) {
357        return new Double(getStartYValue(series, item));
358    }
359
360    /**
361     * Returns the ending 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 end.
368     */
369    public Number getEndY(int series, int item) {
370        return new Double(getEndYValue(series, item));
371    }
372
373    private double getSeriesValue(int series) {
374        return series;
375    }
376
377    private double getSeriesStartValue(int series) {
378        return series - this.seriesWidth / 2.0;
379    }
380
381    private double getSeriesEndValue(int series) {
382        return series + this.seriesWidth / 2.0;
383    }
384
385    private double getItemValue(int series, int item) {
386        TaskSeries s = this.underlying.getSeries(series);
387        Task t = s.get(item);
388        TimePeriod duration = t.getDuration();
389        Date start = duration.getStart();
390        Date end = duration.getEnd();
391        return (start.getTime() + end.getTime()) / 2.0;
392    }
393
394    private double getItemStartValue(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        return start.getTime();
400    }
401
402    private double getItemEndValue(int series, int item) {
403        TaskSeries s = this.underlying.getSeries(series);
404        Task t = s.get(item);
405        TimePeriod duration = t.getDuration();
406        Date end = duration.getEnd();
407        return end.getTime();
408    }
409
410
411    /**
412     * Receives a change event from the underlying dataset and responds by
413     * firing a change event for this dataset.
414     *
415     * @param event  the event.
416     */
417    public void datasetChanged(DatasetChangeEvent event) {
418        fireDatasetChanged();
419    }
420
421    /**
422     * Tests this dataset for equality with an arbitrary object.
423     *
424     * @param obj  the object (<code>null</code> permitted).
425     *
426     * @return A boolean.
427     */
428    public boolean equals(Object obj) {
429        if (obj == this) {
430            return true;
431        }
432        if (!(obj instanceof XYTaskDataset)) {
433            return false;
434        }
435        XYTaskDataset that = (XYTaskDataset) obj;
436        if (this.seriesWidth != that.seriesWidth) {
437            return false;
438        }
439        if (this.transposed != that.transposed) {
440            return false;
441        }
442        if (!this.underlying.equals(that.underlying)) {
443            return false;
444        }
445        return true;
446    }
447
448    /**
449     * Returns a clone of this dataset.
450     *
451     * @return A clone of this dataset.
452     *
453     * @throws CloneNotSupportedException if there is a problem cloning.
454     */
455    public Object clone() throws CloneNotSupportedException {
456        XYTaskDataset clone = (XYTaskDataset) super.clone();
457        clone.underlying = (TaskSeriesCollection) this.underlying.clone();
458        return clone;
459    }
460
461}