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