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-2007, 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-2007, by Achilleus Mantzios and Contributors.
  31:  *
  32:  * Original Author:  Achilleus Mantzios;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 06-Feb-2002 : Version 1, based on code contributed by Achilleus 
  38:  *               Mantzios (DG);
  39:  * 05-May-2004 : Now extends AbstractXYDataset (DG);
  40:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  41:  *               getYValue() (DG);
  42:  * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
  43:  *
  44:  */
  45: 
  46: package org.jfree.data.xy;
  47: 
  48: import java.io.Serializable;
  49: import java.util.Arrays;
  50: import java.util.Collections;
  51: import java.util.Date;
  52: import java.util.List;
  53: 
  54: /**
  55:  * A default implementation of the {@link WindDataset} interface.
  56:  */
  57: public class DefaultWindDataset extends AbstractXYDataset 
  58:                                 implements WindDataset {
  59: 
  60:     /** The keys for the series. */
  61:     private List seriesKeys;
  62: 
  63:     /** Storage for the series data. */
  64:     private List allSeriesData;
  65: 
  66:     /**
  67:      * Constructs a new, empty, dataset.  Since there are currently no methods
  68:      * to add data to an existing dataset, you should probably use a different
  69:      * constructor.
  70:      */
  71:     public DefaultWindDataset() {
  72:         this.seriesKeys = new java.util.ArrayList();
  73:         this.allSeriesData = new java.util.ArrayList();
  74:     }
  75: 
  76:     /**
  77:      * Constructs a dataset based on the specified data array.
  78:      *
  79:      * @param data  the data (<code>null</code> not permitted).
  80:      * 
  81:      * @throws NullPointerException if <code>data</code> is <code>null</code>.
  82:      */
  83:     public DefaultWindDataset(Object[][][] data) {
  84:         this(seriesNameListFromDataArray(data), data);
  85:     }
  86: 
  87:     /**
  88:      * Constructs a dataset based on the specified data array.
  89:      *
  90:      * @param seriesNames  the names of the series (<code>null</code> not 
  91:      *     permitted).
  92:      * @param data  the wind data.
  93:      * 
  94:      * @throws NullPointerException if <code>seriesNames</code> is 
  95:      *     <code>null</code>.
  96:      */
  97:     public DefaultWindDataset(String[] seriesNames, Object[][][] data) {
  98:         this(Arrays.asList(seriesNames), data);
  99:     }
 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:  */
 325: class 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: }