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