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    }