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