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