    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}