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