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
044package org.jfree.data.xy;
045
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.List;
049
050import org.jfree.data.DomainOrder;
051import 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 */
059public 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}