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