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 * DefaultXYDataset.java 029 * --------------------- 030 * (C) Copyright 2006-present, by David Gilbert and Contributors. 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 XYDataset} interface that stores 049 * data values in arrays of double primitives. 050 */ 051public class DefaultXYDataset extends AbstractXYDataset 052 implements XYDataset, 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 DefaultXYDataset} instance, initially 069 * containing no data. 070 */ 071 public DefaultXYDataset() { 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 * Adds a series or if a series with the same key already exists replaces 242 * the data for that series, then sends a {@link DatasetChangeEvent} to 243 * all registered listeners. 244 * 245 * @param seriesKey the series key ({@code null} not permitted). 246 * @param data the data (must be an array with length 2, containing two 247 * arrays of equal length, the first containing the x-values and the 248 * second containing the y-values). 249 */ 250 public void addSeries(Comparable seriesKey, double[][] data) { 251 if (seriesKey == null) { 252 throw new IllegalArgumentException( 253 "The 'seriesKey' cannot be null."); 254 } 255 if (data == null) { 256 throw new IllegalArgumentException("The 'data' is null."); 257 } 258 if (data.length != 2) { 259 throw new IllegalArgumentException( 260 "The 'data' array must have length == 2."); 261 } 262 if (data[0].length != data[1].length) { 263 throw new IllegalArgumentException( 264 "The 'data' array must contain two arrays with equal length."); 265 } 266 int seriesIndex = indexOf(seriesKey); 267 if (seriesIndex == -1) { // add a new series 268 this.seriesKeys.add(seriesKey); 269 this.seriesList.add(data); 270 } 271 else { // replace an existing series 272 this.seriesList.remove(seriesIndex); 273 this.seriesList.add(seriesIndex, data); 274 } 275 notifyListeners(new DatasetChangeEvent(this, this)); 276 } 277 278 /** 279 * Removes a series from the dataset, then sends a 280 * {@link DatasetChangeEvent} to all registered listeners. 281 * 282 * @param seriesKey the series key ({@code null} not permitted). 283 * 284 */ 285 public void removeSeries(Comparable seriesKey) { 286 int seriesIndex = indexOf(seriesKey); 287 if (seriesIndex >= 0) { 288 this.seriesKeys.remove(seriesIndex); 289 this.seriesList.remove(seriesIndex); 290 notifyListeners(new DatasetChangeEvent(this, this)); 291 } 292 } 293 294 /** 295 * Tests this {@code DefaultXYDataset} instance for equality with an 296 * arbitrary object. This method returns {@code true} if and only if: 297 * <ul> 298 * <li>{@code obj} is not {@code null};</li> 299 * <li>{@code obj} is an instance of {@code DefaultXYDataset};</li> 300 * <li>both datasets have the same number of series, each containing 301 * exactly the same values.</li> 302 * </ul> 303 * 304 * @param obj the object ({@code null} permitted). 305 * 306 * @return A boolean. 307 */ 308 @Override 309 public boolean equals(Object obj) { 310 if (obj == this) { 311 return true; 312 } 313 if (!(obj instanceof DefaultXYDataset)) { 314 return false; 315 } 316 DefaultXYDataset that = (DefaultXYDataset) obj; 317 if (!this.seriesKeys.equals(that.seriesKeys)) { 318 return false; 319 } 320 for (int i = 0; i < this.seriesList.size(); i++) { 321 double[][] d1 = (double[][]) this.seriesList.get(i); 322 double[][] d2 = (double[][]) that.seriesList.get(i); 323 double[] d1x = d1[0]; 324 double[] d2x = d2[0]; 325 if (!Arrays.equals(d1x, d2x)) { 326 return false; 327 } 328 double[] d1y = d1[1]; 329 double[] d2y = d2[1]; 330 if (!Arrays.equals(d1y, d2y)) { 331 return false; 332 } 333 } 334 return true; 335 } 336 337 /** 338 * Returns a hash code for this instance. 339 * 340 * @return A hash code. 341 */ 342 @Override 343 public int hashCode() { 344 int result; 345 result = this.seriesKeys.hashCode(); 346 result = 29 * result + this.seriesList.hashCode(); 347 return result; 348 } 349 350 /** 351 * Creates an independent copy of this dataset. 352 * 353 * @return The cloned dataset. 354 * 355 * @throws CloneNotSupportedException if there is a problem cloning the 356 * dataset (for instance, if a non-cloneable object is used for a 357 * series key). 358 */ 359 @Override 360 public Object clone() throws CloneNotSupportedException { 361 DefaultXYDataset clone = (DefaultXYDataset) super.clone(); 362 clone.seriesKeys = new java.util.ArrayList(this.seriesKeys); 363 clone.seriesList = new ArrayList(this.seriesList.size()); 364 for (int i = 0; i < this.seriesList.size(); i++) { 365 double[][] data = (double[][]) this.seriesList.get(i); 366 double[] x = data[0]; 367 double[] y = data[1]; 368 double[] xx = new double[x.length]; 369 double[] yy = new double[y.length]; 370 System.arraycopy(x, 0, xx, 0, x.length); 371 System.arraycopy(y, 0, yy, 0, y.length); 372 clone.seriesList.add(i, new double[][] {xx, yy}); 373 } 374 return clone; 375 } 376 377}