001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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 * DefaultXYZDataset.java 029 * ---------------------- 030 * (C) Copyright 2006-2008, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 12-Jul-2006 : Version 1 (DG); 038 * 06-Oct-2006 : Fixed API doc warnings (DG); 039 * 02-Nov-2006 : Fixed a problem with adding a new series with the same key 040 * as an existing series (see bug 1589392) (DG); 041 * 22-Apr-2008 : Implemented PublicCloneable (DG); 042 * 043 */ 044 045package org.jfree.data.xy; 046 047import java.util.ArrayList; 048import java.util.Arrays; 049import java.util.List; 050 051import org.jfree.data.DomainOrder; 052import org.jfree.data.general.DatasetChangeEvent; 053import org.jfree.util.PublicCloneable; 054 055/** 056 * A default implementation of the {@link XYZDataset} interface that stores 057 * data values in arrays of double primitives. 058 * 059 * @since 1.0.2 060 */ 061public class DefaultXYZDataset extends AbstractXYZDataset 062 implements XYZDataset, PublicCloneable { 063 064 /** 065 * Storage for the series keys. This list must be kept in sync with the 066 * seriesList. 067 */ 068 private List seriesKeys; 069 070 /** 071 * Storage for the series in the dataset. We use a list because the 072 * order of the series is significant. This list must be kept in sync 073 * with the seriesKeys list. 074 */ 075 private List seriesList; 076 077 /** 078 * Creates a new <code>DefaultXYZDataset</code> instance, initially 079 * containing no data. 080 */ 081 public DefaultXYZDataset() { 082 this.seriesKeys = new java.util.ArrayList(); 083 this.seriesList = new java.util.ArrayList(); 084 } 085 086 /** 087 * Returns the number of series in the dataset. 088 * 089 * @return The series count. 090 */ 091 @Override 092 public int getSeriesCount() { 093 return this.seriesList.size(); 094 } 095 096 /** 097 * Returns the key for a series. 098 * 099 * @param series the series index (in the range <code>0</code> to 100 * <code>getSeriesCount() - 1</code>). 101 * 102 * @return The key for the series. 103 * 104 * @throws IllegalArgumentException if <code>series</code> is not in the 105 * specified range. 106 */ 107 @Override 108 public Comparable getSeriesKey(int series) { 109 if ((series < 0) || (series >= getSeriesCount())) { 110 throw new IllegalArgumentException("Series index out of bounds"); 111 } 112 return (Comparable) this.seriesKeys.get(series); 113 } 114 115 /** 116 * Returns the index of the series with the specified key, or -1 if there 117 * is no such series in the dataset. 118 * 119 * @param seriesKey the series key (<code>null</code> permitted). 120 * 121 * @return The index, or -1. 122 */ 123 @Override 124 public int indexOf(Comparable seriesKey) { 125 return this.seriesKeys.indexOf(seriesKey); 126 } 127 128 /** 129 * Returns the order of the domain (x-) values in the dataset. In this 130 * implementation, we cannot guarantee that the x-values are ordered, so 131 * this method returns <code>DomainOrder.NONE</code>. 132 * 133 * @return <code>DomainOrder.NONE</code>. 134 */ 135 @Override 136 public DomainOrder getDomainOrder() { 137 return DomainOrder.NONE; 138 } 139 140 /** 141 * Returns the number of items in the specified series. 142 * 143 * @param series the series index (in the range <code>0</code> to 144 * <code>getSeriesCount() - 1</code>). 145 * 146 * @return The item count. 147 * 148 * @throws IllegalArgumentException if <code>series</code> is not in the 149 * specified range. 150 */ 151 @Override 152 public int getItemCount(int series) { 153 if ((series < 0) || (series >= getSeriesCount())) { 154 throw new IllegalArgumentException("Series index out of bounds"); 155 } 156 double[][] seriesArray = (double[][]) this.seriesList.get(series); 157 return seriesArray[0].length; 158 } 159 160 /** 161 * Returns the x-value for an item within a series. 162 * 163 * @param series the series index (in the range <code>0</code> to 164 * <code>getSeriesCount() - 1</code>). 165 * @param item the item index (in the range <code>0</code> to 166 * <code>getItemCount(series)</code>). 167 * 168 * @return The x-value. 169 * 170 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 171 * within the specified range. 172 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 173 * within the specified range. 174 * 175 * @see #getX(int, int) 176 */ 177 @Override 178 public double getXValue(int series, int item) { 179 double[][] seriesData = (double[][]) this.seriesList.get(series); 180 return seriesData[0][item]; 181 } 182 183 /** 184 * Returns the x-value for an item within a series. 185 * 186 * @param series the series index (in the range <code>0</code> to 187 * <code>getSeriesCount() - 1</code>). 188 * @param item the item index (in the range <code>0</code> to 189 * <code>getItemCount(series)</code>). 190 * 191 * @return The x-value. 192 * 193 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 194 * within the specified range. 195 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 196 * within the specified range. 197 * 198 * @see #getXValue(int, int) 199 */ 200 @Override 201 public Number getX(int series, int item) { 202 return new Double(getXValue(series, item)); 203 } 204 205 /** 206 * Returns the y-value for an item within a series. 207 * 208 * @param series the series index (in the range <code>0</code> to 209 * <code>getSeriesCount() - 1</code>). 210 * @param item the item index (in the range <code>0</code> to 211 * <code>getItemCount(series)</code>). 212 * 213 * @return The y-value. 214 * 215 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 216 * within the specified range. 217 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 218 * within the specified range. 219 * 220 * @see #getY(int, int) 221 */ 222 @Override 223 public double getYValue(int series, int item) { 224 double[][] seriesData = (double[][]) this.seriesList.get(series); 225 return seriesData[1][item]; 226 } 227 228 /** 229 * Returns the y-value for an item within a series. 230 * 231 * @param series the series index (in the range <code>0</code> to 232 * <code>getSeriesCount() - 1</code>). 233 * @param item the item index (in the range <code>0</code> to 234 * <code>getItemCount(series)</code>). 235 * 236 * @return The y-value. 237 * 238 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 239 * within the specified range. 240 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 241 * within the specified range. 242 * 243 * @see #getX(int, int) 244 */ 245 @Override 246 public Number getY(int series, int item) { 247 return new Double(getYValue(series, item)); 248 } 249 250 /** 251 * Returns the z-value for an item within a series. 252 * 253 * @param series the series index (in the range <code>0</code> to 254 * <code>getSeriesCount() - 1</code>). 255 * @param item the item index (in the range <code>0</code> to 256 * <code>getItemCount(series)</code>). 257 * 258 * @return The z-value. 259 * 260 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 261 * within the specified range. 262 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 263 * within the specified range. 264 * 265 * @see #getZ(int, int) 266 */ 267 @Override 268 public double getZValue(int series, int item) { 269 double[][] seriesData = (double[][]) this.seriesList.get(series); 270 return seriesData[2][item]; 271 } 272 273 /** 274 * Returns the z-value for an item within a series. 275 * 276 * @param series the series index (in the range <code>0</code> to 277 * <code>getSeriesCount() - 1</code>). 278 * @param item the item index (in the range <code>0</code> to 279 * <code>getItemCount(series)</code>). 280 * 281 * @return The z-value. 282 * 283 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 284 * within the specified range. 285 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 286 * within the specified range. 287 * 288 * @see #getZ(int, int) 289 */ 290 @Override 291 public Number getZ(int series, int item) { 292 return new Double(getZValue(series, item)); 293 } 294 295 /** 296 * Adds a series or if a series with the same key already exists replaces 297 * the data for that series, then sends a {@link DatasetChangeEvent} to 298 * all registered listeners. 299 * 300 * @param seriesKey the series key (<code>null</code> not permitted). 301 * @param data the data (must be an array with length 3, containing three 302 * arrays of equal length, the first containing the x-values, the 303 * second containing the y-values and the third containing the 304 * z-values). 305 */ 306 public void addSeries(Comparable seriesKey, double[][] data) { 307 if (seriesKey == null) { 308 throw new IllegalArgumentException( 309 "The 'seriesKey' cannot be null."); 310 } 311 if (data == null) { 312 throw new IllegalArgumentException("The 'data' is null."); 313 } 314 if (data.length != 3) { 315 throw new IllegalArgumentException( 316 "The 'data' array must have length == 3."); 317 } 318 if (data[0].length != data[1].length 319 || data[0].length != data[2].length) { 320 throw new IllegalArgumentException("The 'data' array must contain " 321 + "three arrays all having the same length."); 322 } 323 int seriesIndex = indexOf(seriesKey); 324 if (seriesIndex == -1) { // add a new series 325 this.seriesKeys.add(seriesKey); 326 this.seriesList.add(data); 327 } 328 else { // replace an existing series 329 this.seriesList.remove(seriesIndex); 330 this.seriesList.add(seriesIndex, data); 331 } 332 notifyListeners(new DatasetChangeEvent(this, this)); 333 } 334 335 /** 336 * Removes a series from the dataset, then sends a 337 * {@link DatasetChangeEvent} to all registered listeners. 338 * 339 * @param seriesKey the series key (<code>null</code> not permitted). 340 * 341 */ 342 public void removeSeries(Comparable seriesKey) { 343 int seriesIndex = indexOf(seriesKey); 344 if (seriesIndex >= 0) { 345 this.seriesKeys.remove(seriesIndex); 346 this.seriesList.remove(seriesIndex); 347 notifyListeners(new DatasetChangeEvent(this, this)); 348 } 349 } 350 351 /** 352 * Tests this <code>DefaultXYDataset</code> instance for equality with an 353 * arbitrary object. This method returns <code>true</code> if and only if: 354 * <ul> 355 * <li><code>obj</code> is not <code>null</code>;</li> 356 * <li><code>obj</code> is an instance of 357 * <code>DefaultXYDataset</code>;</li> 358 * <li>both datasets have the same number of series, each containing 359 * exactly the same values.</li> 360 * </ul> 361 * 362 * @param obj the object (<code>null</code> permitted). 363 * 364 * @return A boolean. 365 */ 366 @Override 367 public boolean equals(Object obj) { 368 if (obj == this) { 369 return true; 370 } 371 if (!(obj instanceof DefaultXYZDataset)) { 372 return false; 373 } 374 DefaultXYZDataset that = (DefaultXYZDataset) obj; 375 if (!this.seriesKeys.equals(that.seriesKeys)) { 376 return false; 377 } 378 for (int i = 0; i < this.seriesList.size(); i++) { 379 double[][] d1 = (double[][]) this.seriesList.get(i); 380 double[][] d2 = (double[][]) that.seriesList.get(i); 381 double[] d1x = d1[0]; 382 double[] d2x = d2[0]; 383 if (!Arrays.equals(d1x, d2x)) { 384 return false; 385 } 386 double[] d1y = d1[1]; 387 double[] d2y = d2[1]; 388 if (!Arrays.equals(d1y, d2y)) { 389 return false; 390 } 391 double[] d1z = d1[2]; 392 double[] d2z = d2[2]; 393 if (!Arrays.equals(d1z, d2z)) { 394 return false; 395 } 396 } 397 return true; 398 } 399 400 /** 401 * Returns a hash code for this instance. 402 * 403 * @return A hash code. 404 */ 405 @Override 406 public int hashCode() { 407 int result; 408 result = this.seriesKeys.hashCode(); 409 result = 29 * result + this.seriesList.hashCode(); 410 return result; 411 } 412 413 /** 414 * Creates an independent copy of this dataset. 415 * 416 * @return The cloned dataset. 417 * 418 * @throws CloneNotSupportedException if there is a problem cloning the 419 * dataset (for instance, if a non-cloneable object is used for a 420 * series key). 421 */ 422 @Override 423 public Object clone() throws CloneNotSupportedException { 424 DefaultXYZDataset clone = (DefaultXYZDataset) super.clone(); 425 clone.seriesKeys = new java.util.ArrayList(this.seriesKeys); 426 clone.seriesList = new ArrayList(this.seriesList.size()); 427 for (int i = 0; i < this.seriesList.size(); i++) { 428 double[][] data = (double[][]) this.seriesList.get(i); 429 double[] x = data[0]; 430 double[] y = data[1]; 431 double[] z = data[2]; 432 double[] xx = new double[x.length]; 433 double[] yy = new double[y.length]; 434 double[] zz = new double[z.length]; 435 System.arraycopy(x, 0, xx, 0, x.length); 436 System.arraycopy(y, 0, yy, 0, y.length); 437 System.arraycopy(z, 0, zz, 0, z.length); 438 clone.seriesList.add(i, new double[][] {xx, yy, zz}); 439 } 440 return clone; 441 } 442 443}