    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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     * DefaultWindDataset.java
    029     * -----------------------
    030     * (C) Copyright 2001-2007, by Achilleus Mantzios and Contributors.
    031     *
    032     * Original Author:  Achilleus Mantzios;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: DefaultWindDataset.java,v 1.5.2.4 2007/02/02 15:14:53 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 06-Feb-2002 : Version 1, based on code contributed by Achilleus
    040     *               Mantzios (DG);
    041     * 05-May-2004 : Now extends AbstractXYDataset (DG);
    042     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    043     *               getYValue() (DG);
    044     * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
    045     *
    046     */
    047
    048    package org.jfree.data.xy;
    049
    050    import java.io.Serializable;
    051    import java.util.Arrays;
    052    import java.util.Collections;
    053    import java.util.Date;
    054    import java.util.List;
    055
    056    /**
    057     * A default implementation of the {@link WindDataset} interface.
    058     */
    059    public class DefaultWindDataset extends AbstractXYDataset
    060                                    implements WindDataset {
    061
    062        /** The keys for the series. */
    063        private List seriesKeys;
    064
    065        /** Storage for the series data. */
    066        private List allSeriesData;
    067
    068        /**
    069         * Constructs a new, empty, dataset.  Since there are currently no methods
    070         * to add data to an existing dataset, you should probably use a different
    071         * constructor.
    072         */
    073        public DefaultWindDataset() {
    074            this.seriesKeys = new java.util.ArrayList();
    075            this.allSeriesData = new java.util.ArrayList();
    076        }
    077
    078        /**
    079         * Constructs a dataset based on the specified data array.
    080         *
    081         * @param data  the data (<code>null</code> not permitted).
    082         *
    083         * @throws NullPointerException if <code>data</code> is <code>null</code>.
    084         */
    085        public DefaultWindDataset(Object[][][] data) {
    086            this(seriesNameListFromDataArray(data), data);
    087        }
    088
    089        /**
    090         * Constructs a dataset based on the specified data array.
    091         *
    092         * @param seriesNames  the names of the series (<code>null</code> not
    093         *     permitted).
    094         * @param data  the wind data.
    095         *
    096         * @throws NullPointerException if <code>seriesNames</code> is
    097         *     <code>null</code>.
    098         */
    099        public DefaultWindDataset(String[] seriesNames, Object[][][] data) {
    100            this(Arrays.asList(seriesNames), data);
    101        }
    102
    103        /**
    104         * Constructs a dataset based on the specified data array.  The array
    105         * can contain multiple series, each series can contain multiple items,
    106         * and each item is as follows:
    107         * <ul>
    108         * <li><code>data[series][item][0]</code> - the date (either a
    109         *   <code>Date</code> or a <code>Number</code> that is the milliseconds
    110         *   since 1-Jan-1970);</li>
    111         * <li><code>data[series][item][1]</code> - the wind direction (1 - 12,
    112         *   like the numbers on a clock face);</li>
    113         * <li><code>data[series][item][2]</code> - the wind force (1 - 12 on the
    114         *   Beaufort scale)</li>
    115         * </ul>
    116         *
    117         * @param seriesKeys  the names of the series (<code>null</code> not
    118         *     permitted).
    119         * @param data  the wind dataset (<code>null</code> not permitted).
    120         *
    121         * @throws IllegalArgumentException if <code>seriesKeys</code> is
    122         *     <code>null</code>.
    123         * @throws IllegalArgumentException if the number of series keys does not
    124         *     match the number of series in the array.
    125         * @throws NullPointerException if <code>data</code> is <code>null</code>.
    126         */
    127        public DefaultWindDataset(List seriesKeys, Object[][][] data) {
    128            if (seriesKeys == null) {
    129                throw new IllegalArgumentException("Null 'seriesKeys' argument.");
    130            }
    131            if (seriesKeys.size() != data.length) {
    132                throw new IllegalArgumentException("The number of series keys does "
    133                        + "not match the number of series in the data array.");
    134            }
    135            this.seriesKeys = seriesKeys;
    136            int seriesCount = data.length;
    137            this.allSeriesData = new java.util.ArrayList(seriesCount);
    138
    139            for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {
    140                List oneSeriesData = new java.util.ArrayList();
    141                int maxItemCount = data[seriesIndex].length;
    142                for (int itemIndex = 0; itemIndex < maxItemCount; itemIndex++) {
    143                    Object xObject = data[seriesIndex][itemIndex][0];
    144                    if (xObject != null) {
    145                        Number xNumber;
    146                        if (xObject instanceof Number) {
    147                            xNumber = (Number) xObject;
    148                        }
    149                        else {
    150                            if (xObject instanceof Date) {
    151                                Date xDate = (Date) xObject;
    152                                xNumber = new Long(xDate.getTime());
    153                            }
    154                            else {
    155                                xNumber = new Integer(0);
    156                            }
    157                        }
    158                        Number windDir = (Number) data[seriesIndex][itemIndex][1];
    159                        Number windForce = (Number) data[seriesIndex][itemIndex][2];
    160                        oneSeriesData.add(new WindDataItem(xNumber, windDir,
    161                                windForce));
    162                    }
    163                }
    164                Collections.sort(oneSeriesData);
    165                this.allSeriesData.add(seriesIndex, oneSeriesData);
    166            }
    167
    168        }
    169
    170        /**
    171         * Returns the number of series in the dataset.
    172         *
    173         * @return The series count.
    174         */
    175        public int getSeriesCount() {
    176            return this.allSeriesData.size();
    177        }
    178
    179        /**
    180         * Returns the number of items in a series.
    181         *
    182         * @param series  the series (zero-based index).
    183         *
    184         * @return The item count.
    185         */
    186        public int getItemCount(int series) {
    187            if (series < 0 || series >= getSeriesCount()) {
    188                throw new IllegalArgumentException("Invalid series index: "
    189                        + series);
    190            }
    191            List oneSeriesData = (List) this.allSeriesData.get(series);
    192            return oneSeriesData.size();
    193        }
    194
    195        /**
    196         * Returns the key for a series.
    197         *
    198         * @param series  the series (zero-based index).
    199         *
    200         * @return The series key.
    201         */
    202        public Comparable getSeriesKey(int series) {
    203            if (series < 0 || series >= getSeriesCount()) {
    204                throw new IllegalArgumentException("Invalid series index: "
    205                        + series);
    206            }
    207            return (Comparable) this.seriesKeys.get(series);
    208        }
    209
    210        /**
    211         * Returns the x-value for one item within a series.  This should represent
    212         * a point in time, encoded as milliseconds in the same way as
    213         * java.util.Date.
    214         *
    215         * @param series  the series (zero-based index).
    216         * @param item  the item (zero-based index).
    217         *
    218         * @return The x-value for the item within the series.
    219         */
    220        public Number getX(int series, int item) {
    221            List oneSeriesData = (List) this.allSeriesData.get(series);
    222            WindDataItem windItem = (WindDataItem) oneSeriesData.get(item);
    223            return windItem.getX();
    224        }
    225
    226        /**
    227         * Returns the y-value for one item within a series.  This maps to the
    228         * {@link #getWindForce(int, int)} method and is implemented because
    229         * <code>WindDataset</code> is an extension of {@link XYDataset}.
    230         *
    231         * @param series  the series (zero-based index).
    232         * @param item  the item (zero-based index).
    233         *
    234         * @return The y-value for the item within the series.
    235         */
    236        public Number getY(int series, int item) {
    237            return getWindForce(series, item);
    238        }
    239
    240        /**
    241         * Returns the wind direction for one item within a series.  This is a
    242         * number between 0 and 12, like the numbers on an upside-down clock face.
    243         *
    244         * @param series  the series (zero-based index).
    245         * @param item  the item (zero-based index).
    246         *
    247         * @return The wind direction for the item within the series.
    248         */
    249        public Number getWindDirection(int series, int item) {
    250            List oneSeriesData = (List) this.allSeriesData.get(series);
    251            WindDataItem windItem = (WindDataItem) oneSeriesData.get(item);
    252            return windItem.getWindDirection();
    253        }
    254
    255        /**
    256         * Returns the wind force for one item within a series.  This is a number
    257         * between 0 and 12, as defined by the Beaufort scale.
    258         *
    259         * @param series  the series (zero-based index).
    260         * @param item  the item (zero-based index).
    261         *
    262         * @return The wind force for the item within the series.
    263         */
    264        public Number getWindForce(int series, int item) {
    265            List oneSeriesData = (List) this.allSeriesData.get(series);
    266            WindDataItem windItem = (WindDataItem) oneSeriesData.get(item);
    267            return windItem.getWindForce();
    268        }
    269
    270        /**
    271         * Utility method for automatically generating series names.
    272         *
    273         * @param data  the wind data (<code>null</code> not permitted).
    274         *
    275         * @return An array of <i>Series N</i> with N = { 1 .. data.length }.
    276         *
    277         * @throws NullPointerException if <code>data</code> is <code>null</code>.
    278         */
    279        public static List seriesNameListFromDataArray(Object[][] data) {
    280
    281            int seriesCount = data.length;
    282            List seriesNameList = new java.util.ArrayList(seriesCount);
    283            for (int i = 0; i < seriesCount; i++) {
    284                seriesNameList.add("Series " + (i + 1));
    285            }
    286            return seriesNameList;
    287
    288        }
    289
    290        /**
    291         * Checks this <code>WindDataset</code> for equality with an arbitrary
    292         * object.  This method returns <code>true</code> if and only if:
    293         * <ul>
    294         *   <li><code>obj</code> is not <code>null</code>;</li>
    295         *   <li><code>obj</code> is an instance of
    296         *       <code>DefaultWindDataset</code>;</li>
    297         *   <li>both datasets have the same number of series containing identical
    298         *       values.</li>
    299         * <ul>
    300         *
    301         * @param obj  the object (<code>null</code> permitted).
    302         *
    303         * @return A boolean.
    304         */
    305        public boolean equals(Object obj) {
    306            if (this == obj) {
    307                return true;
    308            }
    309            if (!(obj instanceof DefaultWindDataset)) {
    310                return false;
    311            }
    312            DefaultWindDataset that = (DefaultWindDataset) obj;
    313            if (!this.seriesKeys.equals(that.seriesKeys)) {
    314                return false;
    315            }
    316            if (!this.allSeriesData.equals(that.allSeriesData)) {
    317                return false;
    318            }
    319            return true;
    320        }
    321
    322    }
    323
    324    /**
    325     * A wind data item.
    326     */
    327    class WindDataItem implements Comparable, Serializable {
    328
    329        /** The x-value. */
    330        private Number x;
    331
    332        /** The wind direction. */
    333        private Number windDir;
    334
    335        /** The wind force. */
    336        private Number windForce;
    337
    338        /**
    339         * Creates a new wind data item.
    340         *
    341         * @param x  the x-value.
    342         * @param windDir  the direction.
    343         * @param windForce  the force.
    344         */
    345        public WindDataItem(Number x, Number windDir, Number windForce) {
    346            this.x = x;
    347            this.windDir = windDir;
    348            this.windForce = windForce;
    349        }
    350
    351        /**
    352         * Returns the x-value.
    353         *
    354         * @return The x-value.
    355         */
    356        public Number getX() {
    357            return this.x;
    358        }
    359
    360        /**
    361         * Returns the wind direction.
    362         *
    363         * @return The wind direction.
    364         */
    365        public Number getWindDirection() {
    366            return this.windDir;
    367        }
    368
    369        /**
    370         * Returns the wind force.
    371         *
    372         * @return The wind force.
    373         */
    374        public Number getWindForce() {
    375            return this.windForce;
    376        }
    377
    378        /**
    379         * Compares this item to another object.
    380         *
    381         * @param object  the other object.
    382         *
    383         * @return An int that indicates the relative comparison.
    384         */
    385        public int compareTo(Object object) {
    386            if (object instanceof WindDataItem) {
    387                WindDataItem item = (WindDataItem) object;
    388                if (this.x.doubleValue() > item.x.doubleValue()) {
    389                    return 1;
    390                }
    391                else if (this.x.equals(item.x)) {
    392                    return 0;
    393                }
    394                else {
    395                    return -1;
    396                }
    397            }
    398            else {
    399                throw new ClassCastException("WindDataItem.compareTo(error)");
    400            }
    401        }
    402
    403        /**
    404         * Tests this <code>WindDataItem</code> for equality with an arbitrary
    405         * object.
    406         *
    407         * @param obj  the object (<code>null</code> permitted).
    408         *
    409         * @return A boolean.
    410         */
    411        public boolean equals(Object obj) {
    412            if (this == obj) {
    413                return false;
    414            }
    415            if (!(obj instanceof WindDataItem)) {
    416                return false;
    417            }
    418            WindDataItem that = (WindDataItem) obj;
    419            if (!this.x.equals(that.x)) {
    420                return false;
    421            }
    422            if (!this.windDir.equals(that.windDir)) {
    423                return false;
    424            }
    425            if (!this.windForce.equals(that.windForce)) {
    426                return false;
    427            }
    428            return true;
    429        }
    430
    431    }