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