001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: DefaultXYZDataset.java,v 1.1.2.4 2006/11/28 14:06:14 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 12-Jul-2006 : Version 1 (DG);
040     * 06-Oct-2006 : Fixed API doc warnings (DG);
041     * 02-Nov-2006 : Fixed a problem with adding a new series with the same key
042     *               as an existing series (see bug 1589392) (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    
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 {
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    }