001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.data.xy;
038
039import java.util.ArrayList;
040import java.util.Arrays;
041import java.util.List;
042import org.jfree.chart.internal.Args;
043import org.jfree.chart.api.PublicCloneable;
044
045import org.jfree.data.DomainOrder;
046import org.jfree.data.general.DatasetChangeEvent;
047
048/**
049 * A default implementation of the {@link XYZDataset} interface that stores
050 * data values in arrays of double primitives.
051 *
052 * @since 1.0.2
053 */
054public class DefaultXYZDataset<S extends Comparable<S>> extends AbstractXYZDataset<S>
055        implements XYZDataset<S>, PublicCloneable {
056
057    /**
058     * Storage for the series keys.  This list must be kept in sync with the
059     * seriesList.
060     */
061    private List<S> seriesKeys;
062
063    /**
064     * Storage for the series in the dataset.  We use a list because the
065     * order of the series is significant.  This list must be kept in sync
066     * with the seriesKeys list.
067     */
068    private List<double[][]> seriesList;
069
070    /**
071     * Creates a new {@code DefaultXYZDataset} instance, initially
072     * containing no data.
073     */
074    public DefaultXYZDataset() {
075        this.seriesKeys = new ArrayList<>();
076        this.seriesList = new ArrayList<>();
077    }
078
079    /**
080     * Returns the number of series in the dataset.
081     *
082     * @return The series count.
083     */
084    @Override
085    public int getSeriesCount() {
086        return this.seriesList.size();
087    }
088
089    /**
090     * Returns the key for a series.
091     *
092     * @param series  the series index (in the range {@code 0} to
093     *     {@code getSeriesCount() - 1}).
094     *
095     * @return The key for the series.
096     *
097     * @throws IllegalArgumentException if {@code series} is not in the
098     *     specified range.
099     */
100    @Override
101    public S getSeriesKey(int series) {
102        Args.requireInRange(series, "series", 0, this.seriesList.size() - 1);
103        return this.seriesKeys.get(series);
104    }
105
106    /**
107     * Returns the index of the series with the specified key, or -1 if there
108     * is no such series in the dataset.
109     *
110     * @param seriesKey  the series key ({@code null} permitted).
111     *
112     * @return The index, or -1.
113     */
114    @Override
115    public int indexOf(S seriesKey) {
116        return this.seriesKeys.indexOf(seriesKey);
117    }
118
119    /**
120     * Returns the order of the domain (x-) values in the dataset.  In this
121     * implementation, we cannot guarantee that the x-values are ordered, so
122     * this method returns {@code DomainOrder.NONE}.
123     *
124     * @return {@code DomainOrder.NONE}.
125     */
126    @Override
127    public DomainOrder getDomainOrder() {
128        return DomainOrder.NONE;
129    }
130
131    /**
132     * Returns the number of items in the specified series.
133     *
134     * @param series  the series index (in the range {@code 0} to
135     *     {@code getSeriesCount() - 1}).
136     *
137     * @return The item count.
138     *
139     * @throws IllegalArgumentException if {@code series} is not in the
140     *     specified range.
141     */
142    @Override
143    public int getItemCount(int series) {
144        Args.requireInRange(series, "series", 0, this.seriesList.size() - 1);
145        double[][] seriesArray = (double[][]) this.seriesList.get(series);
146        return seriesArray[0].length;
147    }
148
149    /**
150     * Returns the x-value for an item within a series.
151     *
152     * @param series  the series index (in the range {@code 0} to
153     *     {@code getSeriesCount() - 1}).
154     * @param item  the item index (in the range {@code 0} to
155     *     {@code getItemCount(series)}).
156     *
157     * @return The x-value.
158     *
159     * @throws ArrayIndexOutOfBoundsException if {@code series} is not
160     *     within the specified range.
161     * @throws ArrayIndexOutOfBoundsException if {@code item} is not
162     *     within the specified range.
163     *
164     * @see #getX(int, int)
165     */
166    @Override
167    public double getXValue(int series, int item) {
168        double[][] seriesData = this.seriesList.get(series);
169        return seriesData[0][item];
170    }
171
172    /**
173     * Returns the x-value for an item within a series.
174     *
175     * @param series  the series index (in the range {@code 0} to
176     *     {@code getSeriesCount() - 1}).
177     * @param item  the item index (in the range {@code 0} to
178     *     {@code getItemCount(series)}).
179     *
180     * @return The x-value.
181     *
182     * @throws ArrayIndexOutOfBoundsException if {@code series} is not
183     *     within the specified range.
184     * @throws ArrayIndexOutOfBoundsException if {@code item} is not
185     *     within the specified range.
186     *
187     * @see #getXValue(int, int)
188     */
189    @Override
190    public Number getX(int series, int item) {
191        return getXValue(series, item);
192    }
193
194    /**
195     * Returns the y-value for an item within a series.
196     *
197     * @param series  the series index (in the range {@code 0} to
198     *     {@code getSeriesCount() - 1}).
199     * @param item  the item index (in the range {@code 0} to
200     *     {@code getItemCount(series)}).
201     *
202     * @return The y-value.
203     *
204     * @throws ArrayIndexOutOfBoundsException if {@code series} is not
205     *     within the specified range.
206     * @throws ArrayIndexOutOfBoundsException if {@code item} is not
207     *     within the specified range.
208     *
209     * @see #getY(int, int)
210     */
211    @Override
212    public double getYValue(int series, int item) {
213        double[][] seriesData = (double[][]) this.seriesList.get(series);
214        return seriesData[1][item];
215    }
216
217    /**
218     * Returns the y-value for an item within a series.
219     *
220     * @param series  the series index (in the range {@code 0} to
221     *     {@code getSeriesCount() - 1}).
222     * @param item  the item index (in the range {@code 0} to
223     *     {@code getItemCount(series)}).
224     *
225     * @return The y-value.
226     *
227     * @throws ArrayIndexOutOfBoundsException if {@code series} is not
228     *     within the specified range.
229     * @throws ArrayIndexOutOfBoundsException if {@code item} is not
230     *     within the specified range.
231     *
232     * @see #getX(int, int)
233     */
234    @Override
235    public Number getY(int series, int item) {
236        return 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} to
243     *     {@code getSeriesCount() - 1}).
244     * @param item  the item index (in the range {@code 0} to
245     *     {@code getItemCount(series)}).
246     *
247     * @return The z-value.
248     *
249     * @throws ArrayIndexOutOfBoundsException if {@code series} is not
250     *     within the specified range.
251     * @throws ArrayIndexOutOfBoundsException if {@code item} is not
252     *     within the specified range.
253     *
254     * @see #getZ(int, int)
255     */
256    @Override
257    public double getZValue(int series, int item) {
258        double[][] seriesData = this.seriesList.get(series);
259        return seriesData[2][item];
260    }
261
262    /**
263     * Returns the z-value for an item within a series.
264     *
265     * @param series  the series index (in the range {@code 0} to
266     *     {@code getSeriesCount() - 1}).
267     * @param item  the item index (in the range {@code 0} to
268     *     {@code getItemCount(series)}).
269     *
270     * @return The z-value.
271     *
272     * @throws ArrayIndexOutOfBoundsException if {@code series} is not
273     *     within the specified range.
274     * @throws ArrayIndexOutOfBoundsException if {@code item} is not
275     *     within the specified range.
276     *
277     * @see #getZ(int, int)
278     */
279    @Override
280    public Number getZ(int series, int item) {
281        return 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} 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(S seriesKey, double[][] data) {
296        Args.nullNotPermitted(seriesKey, "seriesKey");
297        Args.nullNotPermitted(data, "data");
298        if (data.length != 3) {
299            throw new IllegalArgumentException(
300                    "The 'data' array must have length == 3.");
301        }
302        if (data[0].length != data[1].length
303                || data[0].length != data[2].length) {
304            throw new IllegalArgumentException("The 'data' array must contain "
305                    + "three arrays all having the same length.");
306        }
307        int seriesIndex = indexOf(seriesKey);
308        if (seriesIndex == -1) {  // add a new series
309            this.seriesKeys.add(seriesKey);
310            this.seriesList.add(data);
311        }
312        else {  // replace an existing series
313            this.seriesList.remove(seriesIndex);
314            this.seriesList.add(seriesIndex, data);
315        }
316        notifyListeners(new DatasetChangeEvent(this, this));
317    }
318
319    /**
320     * Removes a series from the dataset, then sends a
321     * {@link DatasetChangeEvent} to all registered listeners.
322     *
323     * @param seriesKey  the series key ({@code null} not permitted).
324     *
325     */
326    public void removeSeries(S seriesKey) {
327        int seriesIndex = indexOf(seriesKey);
328        if (seriesIndex >= 0) {
329            this.seriesKeys.remove(seriesIndex);
330            this.seriesList.remove(seriesIndex);
331            notifyListeners(new DatasetChangeEvent(this, this));
332        }
333    }
334
335    /**
336     * Tests this {@code DefaultXYZDataset} instance for equality with an
337     * arbitrary object.  This method returns {@code true} if and only if:
338     * <ul>
339     * <li>{@code obj} is not {@code null};</li>
340     * <li>{@code obj} is an instance of {@code DefaultXYZDataset};</li>
341     * <li>both datasets have the same number of series, each containing
342     *         exactly the same values.</li>
343     * </ul>
344     *
345     * @param obj  the object ({@code null} permitted).
346     *
347     * @return A boolean.
348     */
349    @Override
350    public boolean equals(Object obj) {
351        if (obj == this) {
352            return true;
353        }
354        if (!(obj instanceof DefaultXYZDataset)) {
355            return false;
356        }
357        DefaultXYZDataset that = (DefaultXYZDataset) obj;
358        if (!this.seriesKeys.equals(that.seriesKeys)) {
359            return false;
360        }
361        for (int i = 0; i < this.seriesList.size(); i++) {
362            double[][] d1 = (double[][]) this.seriesList.get(i);
363            double[][] d2 = (double[][]) that.seriesList.get(i);
364            double[] d1x = d1[0];
365            double[] d2x = d2[0];
366            if (!Arrays.equals(d1x, d2x)) {
367                return false;
368            }
369            double[] d1y = d1[1];
370            double[] d2y = d2[1];
371            if (!Arrays.equals(d1y, d2y)) {
372                return false;
373            }
374            double[] d1z = d1[2];
375            double[] d2z = d2[2];
376            if (!Arrays.equals(d1z, d2z)) {
377                return false;
378            }
379        }
380        return true;
381    }
382
383    /**
384     * Returns a hash code for this instance.
385     *
386     * @return A hash code.
387     */
388    @Override
389    public int hashCode() {
390        int result;
391        result = this.seriesKeys.hashCode();
392        result = 29 * result + this.seriesList.hashCode();
393        return result;
394    }
395
396    /**
397     * Creates an independent copy of this dataset.
398     *
399     * @return The cloned dataset.
400     *
401     * @throws CloneNotSupportedException if there is a problem cloning the
402     *     dataset (for instance, if a non-cloneable object is used for a
403     *     series key).
404     */
405    @Override
406    public Object clone() throws CloneNotSupportedException {
407        DefaultXYZDataset clone = (DefaultXYZDataset) super.clone();
408        clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
409        clone.seriesList = new ArrayList(this.seriesList.size());
410        for (int i = 0; i < this.seriesList.size(); i++) {
411            double[][] data = (double[][]) this.seriesList.get(i);
412            double[] x = data[0];
413            double[] y = data[1];
414            double[] z = data[2];
415            double[] xx = new double[x.length];
416            double[] yy = new double[y.length];
417            double[] zz = new double[z.length];
418            System.arraycopy(x, 0, xx, 0, x.length);
419            System.arraycopy(y, 0, yy, 0, y.length);
420            System.arraycopy(z, 0, zz, 0, z.length);
421            clone.seriesList.add(i, new double[][] {xx, yy, zz});
422        }
423        return clone;
424    }
425
426}