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