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    
041    package org.jfree.data.gantt;
042    
043    import java.util.Date;
044    
045    import org.jfree.chart.axis.SymbolAxis;
046    import org.jfree.chart.renderer.xy.XYBarRenderer;
047    import org.jfree.data.general.DatasetChangeEvent;
048    import org.jfree.data.general.DatasetChangeListener;
049    import org.jfree.data.time.TimePeriod;
050    import org.jfree.data.xy.AbstractXYDataset;
051    import 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     */
063    public 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    }