001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
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    @Override
092    public int getSeriesCount() {
093        return this.seriesList.size();
094    }
095
096    /**
097     * Returns the key for a series.
098     *
099     * @param series  the series index (in the range <code>0</code> to
100     *     <code>getSeriesCount() - 1</code>).
101     *
102     * @return The key for the series.
103     *
104     * @throws IllegalArgumentException if <code>series</code> is not in the
105     *     specified range.
106     */
107    @Override
108    public Comparable getSeriesKey(int series) {
109        if ((series < 0) || (series >= getSeriesCount())) {
110            throw new IllegalArgumentException("Series index out of bounds");
111        }
112        return (Comparable) this.seriesKeys.get(series);
113    }
114
115    /**
116     * Returns the index of the series with the specified key, or -1 if there
117     * is no such series in the dataset.
118     *
119     * @param seriesKey  the series key (<code>null</code> permitted).
120     *
121     * @return The index, or -1.
122     */
123    @Override
124    public int indexOf(Comparable seriesKey) {
125        return this.seriesKeys.indexOf(seriesKey);
126    }
127
128    /**
129     * Returns the order of the domain (x-) values in the dataset.  In this
130     * implementation, we cannot guarantee that the x-values are ordered, so
131     * this method returns <code>DomainOrder.NONE</code>.
132     *
133     * @return <code>DomainOrder.NONE</code>.
134     */
135    @Override
136    public DomainOrder getDomainOrder() {
137        return DomainOrder.NONE;
138    }
139
140    /**
141     * Returns the number of items in the specified series.
142     *
143     * @param series  the series index (in the range <code>0</code> to
144     *     <code>getSeriesCount() - 1</code>).
145     *
146     * @return The item count.
147     *
148     * @throws IllegalArgumentException if <code>series</code> is not in the
149     *     specified range.
150     */
151    @Override
152    public int getItemCount(int series) {
153        if ((series < 0) || (series >= getSeriesCount())) {
154            throw new IllegalArgumentException("Series index out of bounds");
155        }
156        double[][] seriesArray = (double[][]) this.seriesList.get(series);
157        return seriesArray[0].length;
158    }
159
160    /**
161     * Returns the x-value for an item within a series.
162     *
163     * @param series  the series index (in the range <code>0</code> to
164     *     <code>getSeriesCount() - 1</code>).
165     * @param item  the item index (in the range <code>0</code> to
166     *     <code>getItemCount(series)</code>).
167     *
168     * @return The x-value.
169     *
170     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
171     *     within the specified range.
172     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
173     *     within the specified range.
174     *
175     * @see #getX(int, int)
176     */
177    @Override
178    public double getXValue(int series, int item) {
179        double[][] seriesData = (double[][]) this.seriesList.get(series);
180        return seriesData[0][item];
181    }
182
183    /**
184     * Returns the x-value for an item within a series.
185     *
186     * @param series  the series index (in the range <code>0</code> to
187     *     <code>getSeriesCount() - 1</code>).
188     * @param item  the item index (in the range <code>0</code> to
189     *     <code>getItemCount(series)</code>).
190     *
191     * @return The x-value.
192     *
193     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
194     *     within the specified range.
195     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
196     *     within the specified range.
197     *
198     * @see #getXValue(int, int)
199     */
200    @Override
201    public Number getX(int series, int item) {
202        return new Double(getXValue(series, item));
203    }
204
205    /**
206     * Returns the y-value for an item within a series.
207     *
208     * @param series  the series index (in the range <code>0</code> to
209     *     <code>getSeriesCount() - 1</code>).
210     * @param item  the item index (in the range <code>0</code> to
211     *     <code>getItemCount(series)</code>).
212     *
213     * @return The y-value.
214     *
215     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
216     *     within the specified range.
217     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
218     *     within the specified range.
219     *
220     * @see #getY(int, int)
221     */
222    @Override
223    public double getYValue(int series, int item) {
224        double[][] seriesData = (double[][]) this.seriesList.get(series);
225        return seriesData[1][item];
226    }
227
228    /**
229     * Returns the y-value for an item within a series.
230     *
231     * @param series  the series index (in the range <code>0</code> to
232     *     <code>getSeriesCount() - 1</code>).
233     * @param item  the item index (in the range <code>0</code> to
234     *     <code>getItemCount(series)</code>).
235     *
236     * @return The y-value.
237     *
238     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
239     *     within the specified range.
240     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
241     *     within the specified range.
242     *
243     * @see #getX(int, int)
244     */
245    @Override
246    public Number getY(int series, int item) {
247        return new Double(getYValue(series, item));
248    }
249
250    /**
251     * Returns the z-value for an item within a series.
252     *
253     * @param series  the series index (in the range <code>0</code> to
254     *     <code>getSeriesCount() - 1</code>).
255     * @param item  the item index (in the range <code>0</code> to
256     *     <code>getItemCount(series)</code>).
257     *
258     * @return The z-value.
259     *
260     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
261     *     within the specified range.
262     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
263     *     within the specified range.
264     *
265     * @see #getZ(int, int)
266     */
267    @Override
268    public double getZValue(int series, int item) {
269        double[][] seriesData = (double[][]) this.seriesList.get(series);
270        return seriesData[2][item];
271    }
272
273    /**
274     * Returns the z-value for an item within a series.
275     *
276     * @param series  the series index (in the range <code>0</code> to
277     *     <code>getSeriesCount() - 1</code>).
278     * @param item  the item index (in the range <code>0</code> to
279     *     <code>getItemCount(series)</code>).
280     *
281     * @return The z-value.
282     *
283     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
284     *     within the specified range.
285     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
286     *     within the specified range.
287     *
288     * @see #getZ(int, int)
289     */
290    @Override
291    public Number getZ(int series, int item) {
292        return new Double(getZValue(series, item));
293    }
294
295    /**
296     * Adds a series or if a series with the same key already exists replaces
297     * the data for that series, then sends a {@link DatasetChangeEvent} to
298     * all registered listeners.
299     *
300     * @param seriesKey  the series key (<code>null</code> not permitted).
301     * @param data  the data (must be an array with length 3, containing three
302     *     arrays of equal length, the first containing the x-values, the
303     *     second containing the y-values and the third containing the
304     *     z-values).
305     */
306    public void addSeries(Comparable seriesKey, double[][] data) {
307        if (seriesKey == null) {
308            throw new IllegalArgumentException(
309                    "The 'seriesKey' cannot be null.");
310        }
311        if (data == null) {
312            throw new IllegalArgumentException("The 'data' is null.");
313        }
314        if (data.length != 3) {
315            throw new IllegalArgumentException(
316                    "The 'data' array must have length == 3.");
317        }
318        if (data[0].length != data[1].length
319                || data[0].length != data[2].length) {
320            throw new IllegalArgumentException("The 'data' array must contain "
321                    + "three arrays all having the same length.");
322        }
323        int seriesIndex = indexOf(seriesKey);
324        if (seriesIndex == -1) {  // add a new series
325            this.seriesKeys.add(seriesKey);
326            this.seriesList.add(data);
327        }
328        else {  // replace an existing series
329            this.seriesList.remove(seriesIndex);
330            this.seriesList.add(seriesIndex, data);
331        }
332        notifyListeners(new DatasetChangeEvent(this, this));
333    }
334
335    /**
336     * Removes a series from the dataset, then sends a
337     * {@link DatasetChangeEvent} to all registered listeners.
338     *
339     * @param seriesKey  the series key (<code>null</code> not permitted).
340     *
341     */
342    public void removeSeries(Comparable seriesKey) {
343        int seriesIndex = indexOf(seriesKey);
344        if (seriesIndex >= 0) {
345            this.seriesKeys.remove(seriesIndex);
346            this.seriesList.remove(seriesIndex);
347            notifyListeners(new DatasetChangeEvent(this, this));
348        }
349    }
350
351    /**
352     * Tests this <code>DefaultXYDataset</code> instance for equality with an
353     * arbitrary object.  This method returns <code>true</code> if and only if:
354     * <ul>
355     * <li><code>obj</code> is not <code>null</code>;</li>
356     * <li><code>obj</code> is an instance of
357     *         <code>DefaultXYDataset</code>;</li>
358     * <li>both datasets have the same number of series, each containing
359     *         exactly the same values.</li>
360     * </ul>
361     *
362     * @param obj  the object (<code>null</code> permitted).
363     *
364     * @return A boolean.
365     */
366    @Override
367    public boolean equals(Object obj) {
368        if (obj == this) {
369            return true;
370        }
371        if (!(obj instanceof DefaultXYZDataset)) {
372            return false;
373        }
374        DefaultXYZDataset that = (DefaultXYZDataset) obj;
375        if (!this.seriesKeys.equals(that.seriesKeys)) {
376            return false;
377        }
378        for (int i = 0; i < this.seriesList.size(); i++) {
379            double[][] d1 = (double[][]) this.seriesList.get(i);
380            double[][] d2 = (double[][]) that.seriesList.get(i);
381            double[] d1x = d1[0];
382            double[] d2x = d2[0];
383            if (!Arrays.equals(d1x, d2x)) {
384                return false;
385            }
386            double[] d1y = d1[1];
387            double[] d2y = d2[1];
388            if (!Arrays.equals(d1y, d2y)) {
389                return false;
390            }
391            double[] d1z = d1[2];
392            double[] d2z = d2[2];
393            if (!Arrays.equals(d1z, d2z)) {
394                return false;
395            }
396        }
397        return true;
398    }
399
400    /**
401     * Returns a hash code for this instance.
402     *
403     * @return A hash code.
404     */
405    @Override
406    public int hashCode() {
407        int result;
408        result = this.seriesKeys.hashCode();
409        result = 29 * result + this.seriesList.hashCode();
410        return result;
411    }
412
413    /**
414     * Creates an independent copy of this dataset.
415     *
416     * @return The cloned dataset.
417     *
418     * @throws CloneNotSupportedException if there is a problem cloning the
419     *     dataset (for instance, if a non-cloneable object is used for a
420     *     series key).
421     */
422    @Override
423    public Object clone() throws CloneNotSupportedException {
424        DefaultXYZDataset clone = (DefaultXYZDataset) super.clone();
425        clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
426        clone.seriesList = new ArrayList(this.seriesList.size());
427        for (int i = 0; i < this.seriesList.size(); i++) {
428            double[][] data = (double[][]) this.seriesList.get(i);
429            double[] x = data[0];
430            double[] y = data[1];
431            double[] z = data[2];
432            double[] xx = new double[x.length];
433            double[] yy = new double[y.length];
434            double[] zz = new double[z.length];
435            System.arraycopy(x, 0, xx, 0, x.length);
436            System.arraycopy(y, 0, yy, 0, y.length);
437            System.arraycopy(z, 0, zz, 0, z.length);
438            clone.seriesList.add(i, new double[][] {xx, yy, zz});
439        }
440        return clone;
441    }
442
443}