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    }