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 * DefaultXYDataset.java 029 * --------------------- 030 * (C) Copyright 2006, 2007, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: DefaultXYDataset.java,v 1.1.2.5 2007/01/25 14:02:04 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 06-Jul-2006 : Version 1 (DG); 040 * 02-Nov-2006 : Fixed a problem with adding a new series with the same key 041 * as an existing series (see bug 1589392) (DG); 042 * 25-Jan-2007 : Implemented PublicCloneable (DG); 043 * 044 */ 045 046 package org.jfree.data.xy; 047 048 import java.util.ArrayList; 049 import java.util.Arrays; 050 import java.util.List; 051 052 import org.jfree.data.DomainOrder; 053 import org.jfree.data.general.DatasetChangeEvent; 054 import org.jfree.util.PublicCloneable; 055 056 /** 057 * A default implementation of the {@link XYDataset} interface that stores 058 * data values in arrays of double primitives. 059 * 060 * @since 1.0.2 061 */ 062 public class DefaultXYDataset extends AbstractXYDataset 063 implements XYDataset, PublicCloneable { 064 065 /** 066 * Storage for the series keys. This list must be kept in sync with the 067 * seriesList. 068 */ 069 private List seriesKeys; 070 071 /** 072 * Storage for the series in the dataset. We use a list because the 073 * order of the series is significant. This list must be kept in sync 074 * with the seriesKeys list. 075 */ 076 private List seriesList; 077 078 /** 079 * Creates a new <code>DefaultXYDataset</code> instance, initially 080 * containing no data. 081 */ 082 public DefaultXYDataset() { 083 this.seriesKeys = new java.util.ArrayList(); 084 this.seriesList = new java.util.ArrayList(); 085 } 086 087 /** 088 * Returns the number of series in the dataset. 089 * 090 * @return The series count. 091 */ 092 public int getSeriesCount() { 093 return this.seriesList.size(); 094 } 095 096 /** 097 * Returns the key for a series. 098 * 099 * @param series the series index (in the range <code>0</code> to 100 * <code>getSeriesCount() - 1</code>). 101 * 102 * @return The key for the series. 103 * 104 * @throws IllegalArgumentException if <code>series</code> is not in the 105 * specified range. 106 */ 107 public Comparable getSeriesKey(int series) { 108 if ((series < 0) || (series >= getSeriesCount())) { 109 throw new IllegalArgumentException("Series index out of bounds"); 110 } 111 return (Comparable) this.seriesKeys.get(series); 112 } 113 114 /** 115 * Returns the index of the series with the specified key, or -1 if there 116 * is no such series in the dataset. 117 * 118 * @param seriesKey the series key (<code>null</code> permitted). 119 * 120 * @return The index, or -1. 121 */ 122 public int indexOf(Comparable seriesKey) { 123 return this.seriesKeys.indexOf(seriesKey); 124 } 125 126 /** 127 * Returns the order of the domain (x-) values in the dataset. In this 128 * implementation, we cannot guarantee that the x-values are ordered, so 129 * this method returns <code>DomainOrder.NONE</code>. 130 * 131 * @return <code>DomainOrder.NONE</code>. 132 */ 133 public DomainOrder getDomainOrder() { 134 return DomainOrder.NONE; 135 } 136 137 /** 138 * Returns the number of items in the specified series. 139 * 140 * @param series the series index (in the range <code>0</code> to 141 * <code>getSeriesCount() - 1</code>). 142 * 143 * @return The item count. 144 * 145 * @throws IllegalArgumentException if <code>series</code> is not in the 146 * specified range. 147 */ 148 public int getItemCount(int series) { 149 if ((series < 0) || (series >= getSeriesCount())) { 150 throw new IllegalArgumentException("Series index out of bounds"); 151 } 152 double[][] seriesArray = (double[][]) this.seriesList.get(series); 153 return seriesArray[0].length; 154 } 155 156 /** 157 * Returns the x-value for an item within a series. 158 * 159 * @param series the series index (in the range <code>0</code> to 160 * <code>getSeriesCount() - 1</code>). 161 * @param item the item index (in the range <code>0</code> to 162 * <code>getItemCount(series)</code>). 163 * 164 * @return The x-value. 165 * 166 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 167 * within the specified range. 168 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 169 * within the specified range. 170 * 171 * @see #getX(int, int) 172 */ 173 public double getXValue(int series, int item) { 174 double[][] seriesData = (double[][]) this.seriesList.get(series); 175 return seriesData[0][item]; 176 } 177 178 /** 179 * Returns the x-value for an item within a series. 180 * 181 * @param series the series index (in the range <code>0</code> to 182 * <code>getSeriesCount() - 1</code>). 183 * @param item the item index (in the range <code>0</code> to 184 * <code>getItemCount(series)</code>). 185 * 186 * @return The x-value. 187 * 188 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 189 * within the specified range. 190 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 191 * within the specified range. 192 * 193 * @see #getXValue(int, int) 194 */ 195 public Number getX(int series, int item) { 196 return new Double(getXValue(series, item)); 197 } 198 199 /** 200 * Returns the y-value for an item within a series. 201 * 202 * @param series the series index (in the range <code>0</code> to 203 * <code>getSeriesCount() - 1</code>). 204 * @param item the item index (in the range <code>0</code> to 205 * <code>getItemCount(series)</code>). 206 * 207 * @return The y-value. 208 * 209 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 210 * within the specified range. 211 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 212 * within the specified range. 213 * 214 * @see #getY(int, int) 215 */ 216 public double getYValue(int series, int item) { 217 double[][] seriesData = (double[][]) this.seriesList.get(series); 218 return seriesData[1][item]; 219 } 220 221 /** 222 * Returns the y-value for an item within a series. 223 * 224 * @param series the series index (in the range <code>0</code> to 225 * <code>getSeriesCount() - 1</code>). 226 * @param item the item index (in the range <code>0</code> to 227 * <code>getItemCount(series)</code>). 228 * 229 * @return The y-value. 230 * 231 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 232 * within the specified range. 233 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 234 * within the specified range. 235 * 236 * @see #getX(int, int) 237 */ 238 public Number getY(int series, int item) { 239 return new Double(getYValue(series, item)); 240 } 241 242 /** 243 * Adds a series or if a series with the same key already exists replaces 244 * the data for that series, then sends a {@link DatasetChangeEvent} to 245 * all registered listeners. 246 * 247 * @param seriesKey the series key (<code>null</code> not permitted). 248 * @param data the data (must be an array with length 2, containing two 249 * arrays of equal length, the first containing the x-values and the 250 * second containing the y-values). 251 */ 252 public void addSeries(Comparable seriesKey, double[][] data) { 253 if (seriesKey == null) { 254 throw new IllegalArgumentException( 255 "The 'seriesKey' cannot be null."); 256 } 257 if (data == null) { 258 throw new IllegalArgumentException("The 'data' is null."); 259 } 260 if (data.length != 2) { 261 throw new IllegalArgumentException( 262 "The 'data' array must have length == 2."); 263 } 264 if (data[0].length != data[1].length) { 265 throw new IllegalArgumentException( 266 "The 'data' array must contain two arrays with equal length."); 267 } 268 int seriesIndex = indexOf(seriesKey); 269 if (seriesIndex == -1) { // add a new series 270 this.seriesKeys.add(seriesKey); 271 this.seriesList.add(data); 272 } 273 else { // replace an existing series 274 this.seriesList.remove(seriesIndex); 275 this.seriesList.add(seriesIndex, data); 276 } 277 notifyListeners(new DatasetChangeEvent(this, this)); 278 } 279 280 /** 281 * Removes a series from the dataset, then sends a 282 * {@link DatasetChangeEvent} to all registered listeners. 283 * 284 * @param seriesKey the series key (<code>null</code> not permitted). 285 * 286 */ 287 public void removeSeries(Comparable seriesKey) { 288 int seriesIndex = indexOf(seriesKey); 289 if (seriesIndex >= 0) { 290 this.seriesKeys.remove(seriesIndex); 291 this.seriesList.remove(seriesIndex); 292 notifyListeners(new DatasetChangeEvent(this, this)); 293 } 294 } 295 296 /** 297 * Tests this <code>DefaultXYDataset</code> instance for equality with an 298 * arbitrary object. This method returns <code>true</code> if and only if: 299 * <ul> 300 * <li><code>obj</code> is not <code>null</code>;</li> 301 * <li><code>obj</code> is an instance of 302 * <code>DefaultXYDataset</code>;</li> 303 * <li>both datasets have the same number of series, each containing 304 * exactly the same values.</li> 305 * </ul> 306 * 307 * @param obj the object (<code>null</code> permitted). 308 * 309 * @return A boolean. 310 */ 311 public boolean equals(Object obj) { 312 if (obj == this) { 313 return true; 314 } 315 if (!(obj instanceof DefaultXYDataset)) { 316 return false; 317 } 318 DefaultXYDataset that = (DefaultXYDataset) obj; 319 if (!this.seriesKeys.equals(that.seriesKeys)) { 320 return false; 321 } 322 for (int i = 0; i < this.seriesList.size(); i++) { 323 double[][] d1 = (double[][]) this.seriesList.get(i); 324 double[][] d2 = (double[][]) that.seriesList.get(i); 325 double[] d1x = d1[0]; 326 double[] d2x = d2[0]; 327 if (!Arrays.equals(d1x, d2x)) { 328 return false; 329 } 330 double[] d1y = d1[1]; 331 double[] d2y = d2[1]; 332 if (!Arrays.equals(d1y, d2y)) { 333 return false; 334 } 335 } 336 return true; 337 } 338 339 /** 340 * Returns a hash code for this instance. 341 * 342 * @return A hash code. 343 */ 344 public int hashCode() { 345 int result; 346 result = this.seriesKeys.hashCode(); 347 result = 29 * result + this.seriesList.hashCode(); 348 return result; 349 } 350 351 /** 352 * Creates an independent copy of this dataset. 353 * 354 * @return The cloned dataset. 355 * 356 * @throws CloneNotSupportedException if there is a problem cloning the 357 * dataset (for instance, if a non-cloneable object is used for a 358 * series key). 359 */ 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 }