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