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