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