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