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 }