Source for org.jfree.data.xy.DefaultWindDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -----------------------
  28:  * DefaultWindDataset.java
  29:  * -----------------------
  30:  * (C) Copyright 2001-2006, by Achilleus Mantzios and Contributors.
  31:  *
  32:  * Original Author:  Achilleus Mantzios;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: DefaultWindDataset.java,v 1.5.2.2 2006/07/12 13:50:48 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 06-Feb-2002 : Version 1, based on code contributed by Achilleus 
  40:  *               Mantzios (DG);
  41:  * 05-May-2004 : Now extends AbstractXYDataset (DG);
  42:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  43:  *               getYValue() (DG);
  44:  *
  45:  */
  46: 
  47: package org.jfree.data.xy;
  48: 
  49: import java.io.Serializable;
  50: import java.util.Arrays;
  51: import java.util.Collections;
  52: import java.util.Date;
  53: import java.util.List;
  54: 
  55: /**
  56:  * A default implementation of the {@link WindDataset} interface.
  57:  */
  58: public class DefaultWindDataset extends AbstractXYDataset 
  59:                                 implements WindDataset {
  60: 
  61:     /** The keys for the series. */
  62:     private List seriesKeys;
  63: 
  64:     /** Storage for the series data. */
  65:     private List allSeriesData;
  66: 
  67:     /**
  68:      * Constructs a new, empty, dataset.  Since there are currently no methods
  69:      * to add data to an existing dataset, you should probably use a different
  70:      * constructor.
  71:      */
  72:     public DefaultWindDataset() {
  73:         this.seriesKeys = new java.util.ArrayList();
  74:         this.allSeriesData = new java.util.ArrayList();
  75:     }
  76: 
  77:     /**
  78:      * Constructs a dataset based on the specified data array.
  79:      *
  80:      * @param data  the data (<code>null</code> not permitted).
  81:      * 
  82:      * @throws NullPointerException if <code>data</code> is <code>null</code>.
  83:      */
  84:     public DefaultWindDataset(Object[][][] data) {
  85:         this(seriesNameListFromDataArray(data), data);
  86:     }
  87: 
  88:     /**
  89:      * Constructs a dataset based on the specified data array.
  90:      *
  91:      * @param seriesNames  the names of the series (<code>null</code> not 
  92:      *     permitted).
  93:      * @param data  the wind data.
  94:      * 
  95:      * @throws NullPointerException if <code>seriesNames</code> is 
  96:      *     <code>null</code>.
  97:      */
  98:     public DefaultWindDataset(String[] seriesNames, Object[][][] data) {
  99:         this(Arrays.asList(seriesNames), data);
 100:     }
 101: 
 102:     /**
 103:      * Constructs a dataset based on the specified data array.  The array
 104:      * can contain multiple series, each series can contain multiple items,
 105:      * and each item is as follows:
 106:      * <ul>
 107:      * <li><code>data[series][item][0]</code> - the date (either a 
 108:      *   <code>Date</code> or a <code>Number</code> that is the milliseconds 
 109:      *   since 1-Jan-1970);</li>
 110:      * <li><code>data[series][item][1]</code> - the wind direction (1 - 12, 
 111:      *   like the numbers on a clock face);</li>
 112:      * <li><code>data[series][item][2]</code> - the wind force (1 - 12 on the
 113:      *   Beaufort scale)</li>
 114:      * </ul>
 115:      * 
 116:      * @param seriesKeys  the names of the series (<code>null</code> not 
 117:      *     permitted).
 118:      * @param data  the wind dataset (<code>null</code> not permitted).
 119:      * 
 120:      * @throws IllegalArgumentException if <code>seriesKeys</code> is 
 121:      *     <code>null</code>.
 122:      * @throws IllegalArgumentException if the number of series keys does not
 123:      *     match the number of series in the array.
 124:      * @throws NullPointerException if <code>data</code> is <code>null</code>.
 125:      */
 126:     public DefaultWindDataset(List seriesKeys, Object[][][] data) {
 127:         if (seriesKeys == null) {
 128:             throw new IllegalArgumentException("Null 'seriesKeys' argument.");
 129:         }
 130:         if (seriesKeys.size() != data.length) {
 131:             throw new IllegalArgumentException("The number of series keys does "
 132:                     + "not match the number of series in the data array.");
 133:         }
 134:         this.seriesKeys = seriesKeys;
 135:         int seriesCount = data.length;
 136:         this.allSeriesData = new java.util.ArrayList(seriesCount);
 137: 
 138:         for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {
 139:             List oneSeriesData = new java.util.ArrayList();
 140:             int maxItemCount = data[seriesIndex].length;
 141:             for (int itemIndex = 0; itemIndex < maxItemCount; itemIndex++) {
 142:                 Object xObject = data[seriesIndex][itemIndex][0];
 143:                 if (xObject != null) {
 144:                     Number xNumber;
 145:                     if (xObject instanceof Number) {
 146:                         xNumber = (Number) xObject;
 147:                     }
 148:                     else {
 149:                         if (xObject instanceof Date) {
 150:                             Date xDate = (Date) xObject;
 151:                             xNumber = new Long(xDate.getTime());
 152:                         }
 153:                         else {
 154:                             xNumber = new Integer(0);
 155:                         }
 156:                     }
 157:                     Number windDir = (Number) data[seriesIndex][itemIndex][1];
 158:                     Number windForce = (Number) data[seriesIndex][itemIndex][2];
 159:                     oneSeriesData.add(new WindDataItem(xNumber, windDir, 
 160:                             windForce));
 161:                 }
 162:             }
 163:             Collections.sort(oneSeriesData);
 164:             this.allSeriesData.add(seriesIndex, oneSeriesData);
 165:         }
 166: 
 167:     }
 168: 
 169:     /**
 170:      * Returns the number of series in the dataset.
 171:      * 
 172:      * @return The series count.
 173:      */
 174:     public int getSeriesCount() {
 175:         return this.allSeriesData.size();
 176:     }
 177: 
 178:     /**
 179:      * Returns the number of items in a series.
 180:      * 
 181:      * @param series  the series (zero-based index).
 182:      * 
 183:      * @return The item count.
 184:      */
 185:     public int getItemCount(int series) {
 186:         if (series < 0 || series >= getSeriesCount()) {
 187:             throw new IllegalArgumentException("Invalid series index: " 
 188:                     + series);
 189:         }
 190:         List oneSeriesData = (List) this.allSeriesData.get(series);
 191:         return oneSeriesData.size();
 192:     }
 193: 
 194:     /**
 195:      * Returns the key for a series.
 196:      * 
 197:      * @param series  the series (zero-based index).
 198:      * 
 199:      * @return The series key.
 200:      */
 201:     public Comparable getSeriesKey(int series) {
 202:         if (series < 0 || series >= getSeriesCount()) {
 203:             throw new IllegalArgumentException("Invalid series index: " 
 204:                     + series);
 205:         }
 206:         return (Comparable) this.seriesKeys.get(series);
 207:     }
 208: 
 209:     /**
 210:      * Returns the x-value for one item within a series.  This should represent
 211:      * a point in time, encoded as milliseconds in the same way as
 212:      * java.util.Date.
 213:      *
 214:      * @param series  the series (zero-based index).
 215:      * @param item  the item (zero-based index).
 216:      * 
 217:      * @return The x-value for the item within the series.
 218:      */
 219:     public Number getX(int series, int item) {
 220:         List oneSeriesData = (List) this.allSeriesData.get(series);
 221:         WindDataItem windItem = (WindDataItem) oneSeriesData.get(item);
 222:         return windItem.getX();
 223:     }
 224: 
 225:     /**
 226:      * Returns the y-value for one item within a series.  This maps to the
 227:      * {@link #getWindForce(int, int)} method and is implemented because 
 228:      * <code>WindDataset</code> is an extension of {@link XYDataset}.
 229:      *
 230:      * @param series  the series (zero-based index).
 231:      * @param item  the item (zero-based index).
 232:      * 
 233:      * @return The y-value for the item within the series.
 234:      */
 235:     public Number getY(int series, int item) {
 236:         return getWindForce(series, item);
 237:     }
 238: 
 239:     /**
 240:      * Returns the wind direction for one item within a series.  This is a
 241:      * number between 0 and 12, like the numbers on an upside-down clock face.
 242:      * 
 243:      * @param series  the series (zero-based index).
 244:      * @param item  the item (zero-based index).
 245:      * 
 246:      * @return The wind direction for the item within the series.
 247:      */
 248:     public Number getWindDirection(int series, int item) {
 249:         List oneSeriesData = (List) this.allSeriesData.get(series);
 250:         WindDataItem windItem = (WindDataItem) oneSeriesData.get(item);
 251:         return windItem.getWindDirection();
 252:     }
 253: 
 254:     /**
 255:      * Returns the wind force for one item within a series.  This is a number
 256:      * between 0 and 12, as defined by the Beaufort scale.
 257:      * 
 258:      * @param series  the series (zero-based index).
 259:      * @param item  the item (zero-based index).
 260:      * 
 261:      * @return The wind force for the item within the series.
 262:      */
 263:     public Number getWindForce(int series, int item) {
 264:         List oneSeriesData = (List) this.allSeriesData.get(series);
 265:         WindDataItem windItem = (WindDataItem) oneSeriesData.get(item);
 266:         return windItem.getWindForce();
 267:     }
 268: 
 269:     /**
 270:      * Utility method for automatically generating series names.
 271:      * 
 272:      * @param data  the wind data (<code>null</code> not permitted).
 273:      *
 274:      * @return An array of <i>Series N</i> with N = { 1 .. data.length }.
 275:      * 
 276:      * @throws NullPointerException if <code>data</code> is <code>null</code>.
 277:      */
 278:     public static List seriesNameListFromDataArray(Object[][] data) {
 279: 
 280:         int seriesCount = data.length;
 281:         List seriesNameList = new java.util.ArrayList(seriesCount);
 282:         for (int i = 0; i < seriesCount; i++) {
 283:             seriesNameList.add("Series " + (i + 1));
 284:         }
 285:         return seriesNameList;
 286: 
 287:     }
 288:     
 289:     /**
 290:      * Checks this <code>WindDataset</code> for equality with an arbitrary
 291:      * object.  This method returns <code>true</code> if and only if:
 292:      * <ul>
 293:      *   <li><code>obj</code> is not <code>null</code>;</li>
 294:      *   <li><code>obj</code> is an instance of 
 295:      *       <code>DefaultWindDataset</code>;</li>
 296:      *   <li>both datasets have the same number of series containing identical
 297:      *       values.</li>
 298:      * <ul>
 299:      * 
 300:      * @param obj  the object (<code>null</code> permitted).
 301:      * 
 302:      * @return A boolean.
 303:      */
 304:     public boolean equals(Object obj) {
 305:         if (this == obj) {
 306:             return true;
 307:         }
 308:         if (! (obj instanceof DefaultWindDataset)) {
 309:             return false;
 310:         }
 311:         DefaultWindDataset that = (DefaultWindDataset) obj;
 312:         if (!this.seriesKeys.equals(that.seriesKeys)) {
 313:             return false;
 314:         }
 315:         if (!this.allSeriesData.equals(that.allSeriesData)) {
 316:             return false;
 317:         }
 318:         return true;
 319:     }
 320: 
 321: }
 322: 
 323: /**
 324:  * A wind data item.
 325:  *
 326:  * @author Achilleus Mantzios
 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: }