    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
    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}