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
046package org.jfree.data.xy;
047
048import java.io.Serializable;
049import java.util.Arrays;
050import java.util.Collections;
051import java.util.Date;
052import java.util.List;
053
054/**
055 * A default implementation of the {@link WindDataset} interface.
056 */
057public 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 */
325class 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}