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