    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2011, 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, 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    }