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-2008, 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-2008, 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:  * 22-Apr-2008 : Implemented PublicCloneable (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: import org.jfree.util.PublicCloneable;
  56: 
  57: /**
  58:  * A default implementation of the {@link WindDataset} interface.
  59:  */
  60: public class DefaultWindDataset extends AbstractXYDataset
  61:         implements WindDataset, PublicCloneable {
  62: 
  63:     /** The keys for the series. */
  64:     private List seriesKeys;
  65: 
  66:     /** Storage for the series data. */
  67:     private List allSeriesData;
  68: 
  69:     /**
  70:      * Constructs a new, empty, dataset.  Since there are currently no methods
  71:      * to add data to an existing dataset, you should probably use a different
  72:      * constructor.
  73:      */
  74:     public DefaultWindDataset() {
  75:         this.seriesKeys = new java.util.ArrayList();
  76:         this.allSeriesData = new java.util.ArrayList();
  77:     }
  78: 
  79:     /**
  80:      * Constructs a dataset based on the specified data array.
  81:      *
  82:      * @param data  the data (<code>null</code> not permitted).
  83:      *
  84:      * @throws NullPointerException if <code>data</code> is <code>null</code>.
  85:      */
  86:     public DefaultWindDataset(Object[][][] data) {
  87:         this(seriesNameListFromDataArray(data), data);
  88:     }
  89: 
  90:     /**
  91:      * Constructs a dataset based on the specified data array.
  92:      *
  93:      * @param seriesNames  the names of the series (<code>null</code> not
  94:      *     permitted).
  95:      * @param data  the wind data.
  96:      *
  97:      * @throws NullPointerException if <code>seriesNames</code> is
  98:      *     <code>null</code>.
  99:      */
 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: }