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