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     * DefaultIntervalXYDataset.java
029     * -----------------------------
030     * (C) Copyright 2006-2008, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 23-Oct-2006 : Version 1 (DG);
038     * 02-Nov-2006 : Fixed a problem with adding a new series with the same key
039     *               as an existing series (see bug 1589392) (DG);
040     * 28-Nov-2006 : New override for clone() (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.general.DatasetChangeEvent;
052    import org.jfree.util.PublicCloneable;
053    
054    /**
055     * A dataset that defines a range (interval) for both the x-values and the
056     * y-values.  This implementation uses six arrays to store the x, start-x,
057     * end-x, y, start-y and end-y values.
058     * <br><br>
059     * An alternative implementation of the {@link IntervalXYDataset} interface
060     * is provided by the {@link XYIntervalSeriesCollection} class.
061     *
062     * @since 1.0.3
063     */
064    public class DefaultIntervalXYDataset extends AbstractIntervalXYDataset
065            implements PublicCloneable {
066    
067        /**
068         * Storage for the series keys.  This list must be kept in sync with the
069         * seriesList.
070         */
071        private List seriesKeys;
072    
073        /**
074         * Storage for the series in the dataset.  We use a list because the
075         * order of the series is significant.  This list must be kept in sync
076         * with the seriesKeys list.
077         */
078        private List seriesList;
079    
080        /**
081         * Creates a new <code>DefaultIntervalXYDataset</code> instance, initially
082         * containing no data.
083         */
084        public DefaultIntervalXYDataset() {
085            this.seriesKeys = new java.util.ArrayList();
086            this.seriesList = new java.util.ArrayList();
087        }
088    
089        /**
090         * Returns the number of series in the dataset.
091         *
092         * @return The series count.
093         */
094        public int getSeriesCount() {
095            return this.seriesList.size();
096        }
097    
098        /**
099         * Returns the key for a series.
100         *
101         * @param series  the series index (in the range <code>0</code> to
102         *     <code>getSeriesCount() - 1</code>).
103         *
104         * @return The key for the series.
105         *
106         * @throws IllegalArgumentException if <code>series</code> is not in the
107         *     specified range.
108         */
109        public Comparable getSeriesKey(int series) {
110            if ((series < 0) || (series >= getSeriesCount())) {
111                throw new IllegalArgumentException("Series index out of bounds");
112            }
113            return (Comparable) this.seriesKeys.get(series);
114        }
115    
116        /**
117         * Returns the number of items in the specified series.
118         *
119         * @param series  the series index (in the range <code>0</code> to
120         *     <code>getSeriesCount() - 1</code>).
121         *
122         * @return The item count.
123         *
124         * @throws IllegalArgumentException if <code>series</code> is not in the
125         *     specified range.
126         */
127        public int getItemCount(int series) {
128            if ((series < 0) || (series >= getSeriesCount())) {
129                throw new IllegalArgumentException("Series index out of bounds");
130            }
131            double[][] seriesArray = (double[][]) this.seriesList.get(series);
132            return seriesArray[0].length;
133        }
134    
135        /**
136         * Returns the x-value for an item within a series.
137         *
138         * @param series  the series index (in the range <code>0</code> to
139         *     <code>getSeriesCount() - 1</code>).
140         * @param item  the item index (in the range <code>0</code> to
141         *     <code>getItemCount(series)</code>).
142         *
143         * @return The x-value.
144         *
145         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
146         *     within the specified range.
147         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
148         *     within the specified range.
149         *
150         * @see #getX(int, int)
151         */
152        public double getXValue(int series, int item) {
153            double[][] seriesData = (double[][]) this.seriesList.get(series);
154            return seriesData[0][item];
155        }
156    
157        /**
158         * Returns the y-value for an item within a series.
159         *
160         * @param series  the series index (in the range <code>0</code> to
161         *     <code>getSeriesCount() - 1</code>).
162         * @param item  the item index (in the range <code>0</code> to
163         *     <code>getItemCount(series)</code>).
164         *
165         * @return The y-value.
166         *
167         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
168         *     within the specified range.
169         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
170         *     within the specified range.
171         *
172         * @see #getY(int, int)
173         */
174        public double getYValue(int series, int item) {
175            double[][] seriesData = (double[][]) this.seriesList.get(series);
176            return seriesData[3][item];
177        }
178    
179        /**
180         * Returns the starting x-value for an item within a series.
181         *
182         * @param series  the series index (in the range <code>0</code> to
183         *     <code>getSeriesCount() - 1</code>).
184         * @param item  the item index (in the range <code>0</code> to
185         *     <code>getItemCount(series)</code>).
186         *
187         * @return The starting x-value.
188         *
189         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
190         *     within the specified range.
191         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
192         *     within the specified range.
193         *
194         * @see #getStartX(int, int)
195         */
196        public double getStartXValue(int series, int item) {
197            double[][] seriesData = (double[][]) this.seriesList.get(series);
198            return seriesData[1][item];
199        }
200    
201        /**
202         * Returns the ending x-value for an item within a series.
203         *
204         * @param series  the series index (in the range <code>0</code> to
205         *     <code>getSeriesCount() - 1</code>).
206         * @param item  the item index (in the range <code>0</code> to
207         *     <code>getItemCount(series)</code>).
208         *
209         * @return The ending x-value.
210         *
211         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
212         *     within the specified range.
213         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
214         *     within the specified range.
215         *
216         * @see #getEndX(int, int)
217         */
218        public double getEndXValue(int series, int item) {
219            double[][] seriesData = (double[][]) this.seriesList.get(series);
220            return seriesData[2][item];
221        }
222    
223        /**
224         * Returns the starting y-value for an item within a series.
225         *
226         * @param series  the series index (in the range <code>0</code> to
227         *     <code>getSeriesCount() - 1</code>).
228         * @param item  the item index (in the range <code>0</code> to
229         *     <code>getItemCount(series)</code>).
230         *
231         * @return The starting y-value.
232         *
233         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
234         *     within the specified range.
235         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
236         *     within the specified range.
237         *
238         * @see #getStartY(int, int)
239         */
240        public double getStartYValue(int series, int item) {
241            double[][] seriesData = (double[][]) this.seriesList.get(series);
242            return seriesData[4][item];
243        }
244    
245        /**
246         * Returns the ending y-value for an item within a series.
247         *
248         * @param series  the series index (in the range <code>0</code> to
249         *     <code>getSeriesCount() - 1</code>).
250         * @param item  the item index (in the range <code>0</code> to
251         *     <code>getItemCount(series)</code>).
252         *
253         * @return The ending y-value.
254         *
255         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
256         *     within the specified range.
257         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
258         *     within the specified range.
259         *
260         * @see #getEndY(int, int)
261         */
262        public double getEndYValue(int series, int item) {
263            double[][] seriesData = (double[][]) this.seriesList.get(series);
264            return seriesData[5][item];
265        }
266    
267        /**
268         * Returns the ending x-value for an item within a series.
269         *
270         * @param series  the series index (in the range <code>0</code> to
271         *     <code>getSeriesCount() - 1</code>).
272         * @param item  the item index (in the range <code>0</code> to
273         *     <code>getItemCount(series)</code>).
274         *
275         * @return The ending x-value.
276         *
277         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
278         *     within the specified range.
279         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
280         *     within the specified range.
281         *
282         * @see #getEndXValue(int, int)
283         */
284        public Number getEndX(int series, int item) {
285            return new Double(getEndXValue(series, item));
286        }
287    
288        /**
289         * Returns the ending y-value for an item within a series.
290         *
291         * @param series  the series index (in the range <code>0</code> to
292         *     <code>getSeriesCount() - 1</code>).
293         * @param item  the item index (in the range <code>0</code> to
294         *     <code>getItemCount(series)</code>).
295         *
296         * @return The ending y-value.
297         *
298         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
299         *     within the specified range.
300         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
301         *     within the specified range.
302         *
303         * @see #getEndYValue(int, int)
304         */
305        public Number getEndY(int series, int item) {
306            return new Double(getEndYValue(series, item));
307        }
308    
309        /**
310         * Returns the starting x-value for an item within a series.
311         *
312         * @param series  the series index (in the range <code>0</code> to
313         *     <code>getSeriesCount() - 1</code>).
314         * @param item  the item index (in the range <code>0</code> to
315         *     <code>getItemCount(series)</code>).
316         *
317         * @return The starting x-value.
318         *
319         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
320         *     within the specified range.
321         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
322         *     within the specified range.
323         *
324         * @see #getStartXValue(int, int)
325         */
326        public Number getStartX(int series, int item) {
327            return new Double(getStartXValue(series, item));
328        }
329    
330        /**
331         * Returns the starting y-value for an item within a series.
332         *
333         * @param series  the series index (in the range <code>0</code> to
334         *     <code>getSeriesCount() - 1</code>).
335         * @param item  the item index (in the range <code>0</code> to
336         *     <code>getItemCount(series)</code>).
337         *
338         * @return The starting y-value.
339         *
340         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
341         *     within the specified range.
342         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
343         *     within the specified range.
344         *
345         * @see #getStartYValue(int, int)
346         */
347        public Number getStartY(int series, int item) {
348            return new Double(getStartYValue(series, item));
349        }
350    
351        /**
352         * Returns the x-value for an item within a series.
353         *
354         * @param series  the series index (in the range <code>0</code> to
355         *     <code>getSeriesCount() - 1</code>).
356         * @param item  the item index (in the range <code>0</code> to
357         *     <code>getItemCount(series)</code>).
358         *
359         * @return The x-value.
360         *
361         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
362         *     within the specified range.
363         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
364         *     within the specified range.
365         *
366         * @see #getXValue(int, int)
367         */
368        public Number getX(int series, int item) {
369            return new Double(getXValue(series, item));
370        }
371    
372        /**
373         * Returns the y-value for an item within a series.
374         *
375         * @param series  the series index (in the range <code>0</code> to
376         *     <code>getSeriesCount() - 1</code>).
377         * @param item  the item index (in the range <code>0</code> to
378         *     <code>getItemCount(series)</code>).
379         *
380         * @return The y-value.
381         *
382         * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
383         *     within the specified range.
384         * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
385         *     within the specified range.
386         *
387         * @see #getYValue(int, int)
388         */
389        public Number getY(int series, int item) {
390            return new Double(getYValue(series, item));
391        }
392    
393        /**
394         * Adds a series or if a series with the same key already exists replaces
395         * the data for that series, then sends a {@link DatasetChangeEvent} to
396         * all registered listeners.
397         *
398         * @param seriesKey  the series key (<code>null</code> not permitted).
399         * @param data  the data (must be an array with length 6, containing six
400         *     arrays of equal length, the first containing the x-values and the
401         *     second containing the y-values).
402         */
403        public void addSeries(Comparable seriesKey, double[][] data) {
404            if (seriesKey == null) {
405                throw new IllegalArgumentException(
406                        "The 'seriesKey' cannot be null.");
407            }
408            if (data == null) {
409                throw new IllegalArgumentException("The 'data' is null.");
410            }
411            if (data.length != 6) {
412                throw new IllegalArgumentException(
413                        "The 'data' array must have length == 6.");
414            }
415            int length = data[0].length;
416            if (length != data[1].length || length != data[2].length
417                    || length != data[3].length || length != data[4].length
418                    || length != data[5].length) {
419                throw new IllegalArgumentException(
420                    "The 'data' array must contain two arrays with equal length.");
421            }
422            int seriesIndex = indexOf(seriesKey);
423            if (seriesIndex == -1) {  // add a new series
424                this.seriesKeys.add(seriesKey);
425                this.seriesList.add(data);
426            }
427            else {  // replace an existing series
428                this.seriesList.remove(seriesIndex);
429                this.seriesList.add(seriesIndex, data);
430            }
431            notifyListeners(new DatasetChangeEvent(this, this));
432        }
433    
434        /**
435         * Tests this <code>DefaultIntervalXYDataset</code> instance for equality
436         * with an arbitrary object.  This method returns <code>true</code> if and
437         * only if:
438         * <ul>
439         * <li><code>obj</code> is not <code>null</code>;</li>
440         * <li><code>obj</code> is an instance of
441         *         <code>DefaultIntervalXYDataset</code>;</li>
442         * <li>both datasets have the same number of series, each containing
443         *         exactly the same values.</li>
444         * </ul>
445         *
446         * @param obj  the object (<code>null</code> permitted).
447         *
448         * @return A boolean.
449         */
450        public boolean equals(Object obj) {
451            if (obj == this) {
452                return true;
453            }
454            if (!(obj instanceof DefaultIntervalXYDataset)) {
455                return false;
456            }
457            DefaultIntervalXYDataset that = (DefaultIntervalXYDataset) obj;
458            if (!this.seriesKeys.equals(that.seriesKeys)) {
459                return false;
460            }
461            for (int i = 0; i < this.seriesList.size(); i++) {
462                double[][] d1 = (double[][]) this.seriesList.get(i);
463                double[][] d2 = (double[][]) that.seriesList.get(i);
464                double[] d1x = d1[0];
465                double[] d2x = d2[0];
466                if (!Arrays.equals(d1x, d2x)) {
467                    return false;
468                }
469                double[] d1xs = d1[1];
470                double[] d2xs = d2[1];
471                if (!Arrays.equals(d1xs, d2xs)) {
472                    return false;
473                }
474                double[] d1xe = d1[2];
475                double[] d2xe = d2[2];
476                if (!Arrays.equals(d1xe, d2xe)) {
477                    return false;
478                }
479                double[] d1y = d1[3];
480                double[] d2y = d2[3];
481                if (!Arrays.equals(d1y, d2y)) {
482                    return false;
483                }
484                double[] d1ys = d1[4];
485                double[] d2ys = d2[4];
486                if (!Arrays.equals(d1ys, d2ys)) {
487                    return false;
488                }
489                double[] d1ye = d1[5];
490                double[] d2ye = d2[5];
491                if (!Arrays.equals(d1ye, d2ye)) {
492                    return false;
493                }
494            }
495            return true;
496        }
497    
498        /**
499         * Returns a hash code for this instance.
500         *
501         * @return A hash code.
502         */
503        public int hashCode() {
504            int result;
505            result = this.seriesKeys.hashCode();
506            result = 29 * result + this.seriesList.hashCode();
507            return result;
508        }
509    
510        /**
511         * Returns a clone of this dataset.
512         *
513         * @return A clone.
514         *
515         * @throws CloneNotSupportedException if the dataset contains a series with
516         *         a key that cannot be cloned.
517         */
518        public Object clone() throws CloneNotSupportedException {
519            DefaultIntervalXYDataset clone
520                    = (DefaultIntervalXYDataset) super.clone();
521            clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
522            clone.seriesList = new ArrayList(this.seriesList.size());
523            for (int i = 0; i < this.seriesList.size(); i++) {
524                double[][] data = (double[][]) this.seriesList.get(i);
525                double[] x = data[0];
526                double[] xStart = data[1];
527                double[] xEnd = data[2];
528                double[] y = data[3];
529                double[] yStart = data[4];
530                double[] yEnd = data[5];
531                double[] xx = new double[x.length];
532                double[] xxStart = new double[xStart.length];
533                double[] xxEnd = new double[xEnd.length];
534                double[] yy = new double[y.length];
535                double[] yyStart = new double[yStart.length];
536                double[] yyEnd = new double[yEnd.length];
537                System.arraycopy(x, 0, xx, 0, x.length);
538                System.arraycopy(xStart, 0, xxStart, 0, xStart.length);
539                System.arraycopy(xEnd, 0, xxEnd, 0, xEnd.length);
540                System.arraycopy(y, 0, yy, 0, y.length);
541                System.arraycopy(yStart, 0, yyStart, 0, yStart.length);
542                System.arraycopy(yEnd, 0, yyEnd, 0, yEnd.length);
543                clone.seriesList.add(i, new double[][] {xx, xxStart, xxEnd, yy,
544                        yyStart, yyEnd});
545            }
546            return clone;
547        }
548    
549    }