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 }