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    }