1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * ----------------------
28  * DefaultXYZDataset.java
29  * ----------------------
30  * (C) Copyright 2006-2008, by Object Refinery Limited.
31  *
32  * Original Author:  David Gilbert (for Object Refinery Limited);
33  * Contributor(s):   -;
34  *
35  * Changes
36  * -------
37  * 12-Jul-2006 : Version 1 (DG);
38  * 06-Oct-2006 : Fixed API doc warnings (DG);
39  * 02-Nov-2006 : Fixed a problem with adding a new series with the same key
40  *               as an existing series (see bug 1589392) (DG);
41  * 22-Apr-2008 : Implemented PublicCloneable (DG);
42  *
43  */
44 
45 package org.jfree.data.xy;
46 
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.List;
50 
51 import org.jfree.data.DomainOrder;
52 import org.jfree.data.general.DatasetChangeEvent;
53 import org.jfree.util.PublicCloneable;
54 
55 /**
56  * A default implementation of the {@link XYZDataset} interface that stores
57  * data values in arrays of double primitives.
58  *
59  * @since 1.0.2
60  */
61 public class DefaultXYZDataset extends AbstractXYZDataset
62         implements XYZDataset, PublicCloneable {
63 
64     /**
65      * Storage for the series keys.  This list must be kept in sync with the
66      * seriesList.
67      */
68     private List seriesKeys;
69 
70     /**
71      * Storage for the series in the dataset.  We use a list because the
72      * order of the series is significant.  This list must be kept in sync
73      * with the seriesKeys list.
74      */
75     private List seriesList;
76 
77     /**
78      * Creates a new <code>DefaultXYZDataset</code> instance, initially
79      * containing no data.
80      */
DefaultXYZDataset()81     public DefaultXYZDataset() {
82         this.seriesKeys = new java.util.ArrayList();
83         this.seriesList = new java.util.ArrayList();
84     }
85 
86     /**
87      * Returns the number of series in the dataset.
88      *
89      * @return The series count.
90      */
91     @Override
getSeriesCount()92     public int getSeriesCount() {
93         return this.seriesList.size();
94     }
95 
96     /**
97      * Returns the key for a series.
98      *
99      * @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     @Override
getSeriesKey(int series)108     public Comparable getSeriesKey(int series) {
109         if ((series < 0) || (series >= getSeriesCount())) {
110             throw new IllegalArgumentException("Series index out of bounds");
111         }
112         return (Comparable) this.seriesKeys.get(series);
113     }
114 
115     /**
116      * Returns the index of the series with the specified key, or -1 if there
117      * is no such series in the dataset.
118      *
119      * @param seriesKey  the series key (<code>null</code> permitted).
120      *
121      * @return The index, or -1.
122      */
123     @Override
indexOf(Comparable seriesKey)124     public int indexOf(Comparable seriesKey) {
125         return this.seriesKeys.indexOf(seriesKey);
126     }
127 
128     /**
129      * Returns the order of the domain (x-) values in the dataset.  In this
130      * implementation, we cannot guarantee that the x-values are ordered, so
131      * this method returns <code>DomainOrder.NONE</code>.
132      *
133      * @return <code>DomainOrder.NONE</code>.
134      */
135     @Override
getDomainOrder()136     public DomainOrder getDomainOrder() {
137         return DomainOrder.NONE;
138     }
139 
140     /**
141      * Returns the number of items in the specified series.
142      *
143      * @param series  the series index (in the range <code>0</code> to
144      *     <code>getSeriesCount() - 1</code>).
145      *
146      * @return The item count.
147      *
148      * @throws IllegalArgumentException if <code>series</code> is not in the
149      *     specified range.
150      */
151     @Override
getItemCount(int series)152     public int getItemCount(int series) {
153         if ((series < 0) || (series >= getSeriesCount())) {
154             throw new IllegalArgumentException("Series index out of bounds");
155         }
156         double[][] seriesArray = (double[][]) this.seriesList.get(series);
157         return seriesArray[0].length;
158     }
159 
160     /**
161      * Returns the x-value for an item within a series.
162      *
163      * @param series  the series index (in the range <code>0</code> to
164      *     <code>getSeriesCount() - 1</code>).
165      * @param item  the item index (in the range <code>0</code> to
166      *     <code>getItemCount(series)</code>).
167      *
168      * @return The x-value.
169      *
170      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
171      *     within the specified range.
172      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
173      *     within the specified range.
174      *
175      * @see #getX(int, int)
176      */
177     @Override
getXValue(int series, int item)178     public double getXValue(int series, int item) {
179         double[][] seriesData = (double[][]) this.seriesList.get(series);
180         return seriesData[0][item];
181     }
182 
183     /**
184      * Returns the x-value for an item within a series.
185      *
186      * @param series  the series index (in the range <code>0</code> to
187      *     <code>getSeriesCount() - 1</code>).
188      * @param item  the item index (in the range <code>0</code> to
189      *     <code>getItemCount(series)</code>).
190      *
191      * @return The x-value.
192      *
193      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
194      *     within the specified range.
195      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
196      *     within the specified range.
197      *
198      * @see #getXValue(int, int)
199      */
200     @Override
getX(int series, int item)201     public Number getX(int series, int item) {
202         return new Double(getXValue(series, item));
203     }
204 
205     /**
206      * Returns the y-value for an item within a series.
207      *
208      * @param series  the series index (in the range <code>0</code> to
209      *     <code>getSeriesCount() - 1</code>).
210      * @param item  the item index (in the range <code>0</code> to
211      *     <code>getItemCount(series)</code>).
212      *
213      * @return The y-value.
214      *
215      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
216      *     within the specified range.
217      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
218      *     within the specified range.
219      *
220      * @see #getY(int, int)
221      */
222     @Override
getYValue(int series, int item)223     public double getYValue(int series, int item) {
224         double[][] seriesData = (double[][]) this.seriesList.get(series);
225         return seriesData[1][item];
226     }
227 
228     /**
229      * Returns the y-value for an item within a series.
230      *
231      * @param series  the series index (in the range <code>0</code> to
232      *     <code>getSeriesCount() - 1</code>).
233      * @param item  the item index (in the range <code>0</code> to
234      *     <code>getItemCount(series)</code>).
235      *
236      * @return The y-value.
237      *
238      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
239      *     within the specified range.
240      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
241      *     within the specified range.
242      *
243      * @see #getX(int, int)
244      */
245     @Override
getY(int series, int item)246     public Number getY(int series, int item) {
247         return new Double(getYValue(series, item));
248     }
249 
250     /**
251      * Returns the z-value for an item within a series.
252      *
253      * @param series  the series index (in the range <code>0</code> to
254      *     <code>getSeriesCount() - 1</code>).
255      * @param item  the item index (in the range <code>0</code> to
256      *     <code>getItemCount(series)</code>).
257      *
258      * @return The z-value.
259      *
260      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
261      *     within the specified range.
262      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
263      *     within the specified range.
264      *
265      * @see #getZ(int, int)
266      */
267     @Override
getZValue(int series, int item)268     public double getZValue(int series, int item) {
269         double[][] seriesData = (double[][]) this.seriesList.get(series);
270         return seriesData[2][item];
271     }
272 
273     /**
274      * Returns the z-value for an item within a series.
275      *
276      * @param series  the series index (in the range <code>0</code> to
277      *     <code>getSeriesCount() - 1</code>).
278      * @param item  the item index (in the range <code>0</code> to
279      *     <code>getItemCount(series)</code>).
280      *
281      * @return The z-value.
282      *
283      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
284      *     within the specified range.
285      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
286      *     within the specified range.
287      *
288      * @see #getZ(int, int)
289      */
290     @Override
getZ(int series, int item)291     public Number getZ(int series, int item) {
292         return new Double(getZValue(series, item));
293     }
294 
295     /**
296      * Adds a series or if a series with the same key already exists replaces
297      * the data for that series, then sends a {@link DatasetChangeEvent} to
298      * all registered listeners.
299      *
300      * @param seriesKey  the series key (<code>null</code> not permitted).
301      * @param data  the data (must be an array with length 3, containing three
302      *     arrays of equal length, the first containing the x-values, the
303      *     second containing the y-values and the third containing the
304      *     z-values).
305      */
addSeries(Comparable seriesKey, double[][] data)306     public void addSeries(Comparable seriesKey, double[][] data) {
307         if (seriesKey == null) {
308             throw new IllegalArgumentException(
309                     "The 'seriesKey' cannot be null.");
310         }
311         if (data == null) {
312             throw new IllegalArgumentException("The 'data' is null.");
313         }
314         if (data.length != 3) {
315             throw new IllegalArgumentException(
316                     "The 'data' array must have length == 3.");
317         }
318         if (data[0].length != data[1].length
319                 || data[0].length != data[2].length) {
320             throw new IllegalArgumentException("The 'data' array must contain "
321                     + "three arrays all having the same length.");
322         }
323         int seriesIndex = indexOf(seriesKey);
324         if (seriesIndex == -1) {  // add a new series
325             this.seriesKeys.add(seriesKey);
326             this.seriesList.add(data);
327         }
328         else {  // replace an existing series
329             this.seriesList.remove(seriesIndex);
330             this.seriesList.add(seriesIndex, data);
331         }
332         notifyListeners(new DatasetChangeEvent(this, this));
333     }
334 
335     /**
336      * Removes a series from the dataset, then sends a
337      * {@link DatasetChangeEvent} to all registered listeners.
338      *
339      * @param seriesKey  the series key (<code>null</code> not permitted).
340      *
341      */
removeSeries(Comparable seriesKey)342     public void removeSeries(Comparable seriesKey) {
343         int seriesIndex = indexOf(seriesKey);
344         if (seriesIndex >= 0) {
345             this.seriesKeys.remove(seriesIndex);
346             this.seriesList.remove(seriesIndex);
347             notifyListeners(new DatasetChangeEvent(this, this));
348         }
349     }
350 
351     /**
352      * Tests this <code>DefaultXYDataset</code> instance for equality with an
353      * arbitrary object.  This method returns <code>true</code> if and only if:
354      * <ul>
355      * <li><code>obj</code> is not <code>null</code>;</li>
356      * <li><code>obj</code> is an instance of
357      *         <code>DefaultXYDataset</code>;</li>
358      * <li>both datasets have the same number of series, each containing
359      *         exactly the same values.</li>
360      * </ul>
361      *
362      * @param obj  the object (<code>null</code> permitted).
363      *
364      * @return A boolean.
365      */
366     @Override
equals(Object obj)367     public boolean equals(Object obj) {
368         if (obj == this) {
369             return true;
370         }
371         if (!(obj instanceof DefaultXYZDataset)) {
372             return false;
373         }
374         DefaultXYZDataset that = (DefaultXYZDataset) obj;
375         if (!this.seriesKeys.equals(that.seriesKeys)) {
376             return false;
377         }
378         for (int i = 0; i < this.seriesList.size(); i++) {
379             double[][] d1 = (double[][]) this.seriesList.get(i);
380             double[][] d2 = (double[][]) that.seriesList.get(i);
381             double[] d1x = d1[0];
382             double[] d2x = d2[0];
383             if (!Arrays.equals(d1x, d2x)) {
384                 return false;
385             }
386             double[] d1y = d1[1];
387             double[] d2y = d2[1];
388             if (!Arrays.equals(d1y, d2y)) {
389                 return false;
390             }
391             double[] d1z = d1[2];
392             double[] d2z = d2[2];
393             if (!Arrays.equals(d1z, d2z)) {
394                 return false;
395             }
396         }
397         return true;
398     }
399 
400     /**
401      * Returns a hash code for this instance.
402      *
403      * @return A hash code.
404      */
405     @Override
hashCode()406     public int hashCode() {
407         int result;
408         result = this.seriesKeys.hashCode();
409         result = 29 * result + this.seriesList.hashCode();
410         return result;
411     }
412 
413     /**
414      * Creates an independent copy of this dataset.
415      *
416      * @return The cloned dataset.
417      *
418      * @throws CloneNotSupportedException if there is a problem cloning the
419      *     dataset (for instance, if a non-cloneable object is used for a
420      *     series key).
421      */
422     @Override
clone()423     public Object clone() throws CloneNotSupportedException {
424         DefaultXYZDataset clone = (DefaultXYZDataset) super.clone();
425         clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
426         clone.seriesList = new ArrayList(this.seriesList.size());
427         for (int i = 0; i < this.seriesList.size(); i++) {
428             double[][] data = (double[][]) this.seriesList.get(i);
429             double[] x = data[0];
430             double[] y = data[1];
431             double[] z = data[2];
432             double[] xx = new double[x.length];
433             double[] yy = new double[y.length];
434             double[] zz = new double[z.length];
435             System.arraycopy(x, 0, xx, 0, x.length);
436             System.arraycopy(y, 0, yy, 0, y.length);
437             System.arraycopy(z, 0, zz, 0, z.length);
438             clone.seriesList.add(i, new double[][] {xx, yy, zz});
439         }
440         return clone;
441     }
442 
443 }
444