    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 * DefaultXYDataset.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 * 06-Jul-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 * 25-Jan-2007 : Implemented PublicCloneable (DG);
    041 *
    042 */
    043
    044package org.jfree.data.xy;
    045
    046import java.util.ArrayList;
    047import java.util.Arrays;
    048import java.util.List;
    049
    050import org.jfree.data.DomainOrder;
    051import org.jfree.data.general.DatasetChangeEvent;
    052import org.jfree.util.PublicCloneable;
    053
    054/**
    055 * A default implementation of the {@link XYDataset} interface that stores
    056 * data values in arrays of double primitives.
    057 *
    058 * @since 1.0.2
    059 */
    060public class DefaultXYDataset extends AbstractXYDataset
    061        implements XYDataset, PublicCloneable {
    062
    063    /**
    064     * Storage for the series keys.  This list must be kept in sync with the
    065     * seriesList.
    066     */
    067    private List seriesKeys;
    068
    069    /**
    070     * Storage for the series in the dataset.  We use a list because the
    071     * order of the series is significant.  This list must be kept in sync
    072     * with the seriesKeys list.
    073     */
    074    private List seriesList;
    075
    076    /**
    077     * Creates a new <code>DefaultXYDataset</code> instance, initially
    078     * containing no data.
    079     */
    080    public DefaultXYDataset() {
    081        this.seriesKeys = new java.util.ArrayList();
    082        this.seriesList = new java.util.ArrayList();
    083    }
    084
    085    /**
    086     * Returns the number of series in the dataset.
    087     *
    088     * @return The series count.
    089     */
    090    public int getSeriesCount() {
    091        return this.seriesList.size();
    092    }
    093
    094    /**
    095     * Returns the key for a series.
    096     *
    097     * @param series  the series index (in the range <code>0</code> to
    098     *     <code>getSeriesCount() - 1</code>).
    099     *
    100     * @return The key for the series.
    101     *
    102     * @throws IllegalArgumentException if <code>series</code> is not in the
    103     *     specified range.
    104     */
    105    public Comparable getSeriesKey(int series) {
    106        if ((series < 0) || (series >= getSeriesCount())) {
    107            throw new IllegalArgumentException("Series index out of bounds");
    108        }
    109        return (Comparable) this.seriesKeys.get(series);
    110    }
    111
    112    /**
    113     * Returns the index of the series with the specified key, or -1 if there
    114     * is no such series in the dataset.
    115     *
    116     * @param seriesKey  the series key (<code>null</code> permitted).
    117     *
    118     * @return The index, or -1.
    119     */
    120    public int indexOf(Comparable seriesKey) {
    121        return this.seriesKeys.indexOf(seriesKey);
    122    }
    123
    124    /**
    125     * Returns the order of the domain (x-) values in the dataset.  In this
    126     * implementation, we cannot guarantee that the x-values are ordered, so
    127     * this method returns <code>DomainOrder.NONE</code>.
    128     *
    129     * @return <code>DomainOrder.NONE</code>.
    130     */
    131    public DomainOrder getDomainOrder() {
    132        return DomainOrder.NONE;
    133    }
    134
    135    /**
    136     * Returns the number of items in the specified series.
    137     *
    138     * @param series  the series index (in the range <code>0</code> to
    139     *     <code>getSeriesCount() - 1</code>).
    140     *
    141     * @return The item count.
    142     *
    143     * @throws IllegalArgumentException if <code>series</code> is not in the
    144     *     specified range.
    145     */
    146    public int getItemCount(int series) {
    147        if ((series < 0) || (series >= getSeriesCount())) {
    148            throw new IllegalArgumentException("Series index out of bounds");
    149        }
    150        double[][] seriesArray = (double[][]) this.seriesList.get(series);
    151        return seriesArray[0].length;
    152    }
    153
    154    /**
    155     * Returns the x-value for an item within a series.
    156     *
    157     * @param series  the series index (in the range <code>0</code> to
    158     *     <code>getSeriesCount() - 1</code>).
    159     * @param item  the item index (in the range <code>0</code> to
    160     *     <code>getItemCount(series)</code>).
    161     *
    162     * @return The x-value.
    163     *
    164     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
    165     *     within the specified range.
    166     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
    167     *     within the specified range.
    168     *
    169     * @see #getX(int, int)
    170     */
    171    public double getXValue(int series, int item) {
    172        double[][] seriesData = (double[][]) this.seriesList.get(series);
    173        return seriesData[0][item];
    174    }
    175
    176    /**
    177     * Returns the x-value for an item within a series.
    178     *
    179     * @param series  the series index (in the range <code>0</code> to
    180     *     <code>getSeriesCount() - 1</code>).
    181     * @param item  the item index (in the range <code>0</code> to
    182     *     <code>getItemCount(series)</code>).
    183     *
    184     * @return The x-value.
    185     *
    186     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
    187     *     within the specified range.
    188     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
    189     *     within the specified range.
    190     *
    191     * @see #getXValue(int, int)
    192     */
    193    public Number getX(int series, int item) {
    194        return new Double(getXValue(series, item));
    195    }
    196
    197    /**
    198     * Returns the y-value for an item within a series.
    199     *
    200     * @param series  the series index (in the range <code>0</code> to
    201     *     <code>getSeriesCount() - 1</code>).
    202     * @param item  the item index (in the range <code>0</code> to
    203     *     <code>getItemCount(series)</code>).
    204     *
    205     * @return The y-value.
    206     *
    207     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
    208     *     within the specified range.
    209     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
    210     *     within the specified range.
    211     *
    212     * @see #getY(int, int)
    213     */
    214    public double getYValue(int series, int item) {
    215        double[][] seriesData = (double[][]) this.seriesList.get(series);
    216        return seriesData[1][item];
    217    }
    218
    219    /**
    220     * Returns the y-value for an item within a series.
    221     *
    222     * @param series  the series index (in the range <code>0</code> to
    223     *     <code>getSeriesCount() - 1</code>).
    224     * @param item  the item index (in the range <code>0</code> to
    225     *     <code>getItemCount(series)</code>).
    226     *
    227     * @return The y-value.
    228     *
    229     * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
    230     *     within the specified range.
    231     * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
    232     *     within the specified range.
    233     *
    234     * @see #getX(int, int)
    235     */
    236    public Number getY(int series, int item) {
    237        return new Double(getYValue(series, item));
    238    }
    239
    240    /**
    241     * Adds a series or if a series with the same key already exists replaces
    242     * the data for that series, then sends a {@link DatasetChangeEvent} to
    243     * all registered listeners.
    244     *
    245     * @param seriesKey  the series key (<code>null</code> not permitted).
    246     * @param data  the data (must be an array with length 2, containing two
    247     *     arrays of equal length, the first containing the x-values and the
    248     *     second containing the y-values).
    249     */
    250    public void addSeries(Comparable seriesKey, double[][] data) {
    251        if (seriesKey == null) {
    252            throw new IllegalArgumentException(
    253                    "The 'seriesKey' cannot be null.");
    254        }
    255        if (data == null) {
    256            throw new IllegalArgumentException("The 'data' is null.");
    257        }
    258        if (data.length != 2) {
    259            throw new IllegalArgumentException(
    260                    "The 'data' array must have length == 2.");
    261        }
    262        if (data[0].length != data[1].length) {
    263            throw new IllegalArgumentException(
    264                "The 'data' array must contain two arrays with equal length.");
    265        }
    266        int seriesIndex = indexOf(seriesKey);
    267        if (seriesIndex == -1) {  // add a new series
    268            this.seriesKeys.add(seriesKey);
    269            this.seriesList.add(data);
    270        }
    271        else {  // replace an existing series
    272            this.seriesList.remove(seriesIndex);
    273            this.seriesList.add(seriesIndex, data);
    274        }
    275        notifyListeners(new DatasetChangeEvent(this, this));
    276    }
    277
    278    /**
    279     * Removes a series from the dataset, then sends a
    280     * {@link DatasetChangeEvent} to all registered listeners.
    281     *
    282     * @param seriesKey  the series key (<code>null</code> not permitted).
    283     *
    284     */
    285    public void removeSeries(Comparable seriesKey) {
    286        int seriesIndex = indexOf(seriesKey);
    287        if (seriesIndex >= 0) {
    288            this.seriesKeys.remove(seriesIndex);
    289            this.seriesList.remove(seriesIndex);
    290            notifyListeners(new DatasetChangeEvent(this, this));
    291        }
    292    }
    293
    294    /**
    295     * Tests this <code>DefaultXYDataset</code> instance for equality with an
    296     * arbitrary object.  This method returns <code>true</code> if and only if:
    297     * <ul>
    298     * <li><code>obj</code> is not <code>null</code>;</li>
    299     * <li><code>obj</code> is an instance of
    300     *         <code>DefaultXYDataset</code>;</li>
    301     * <li>both datasets have the same number of series, each containing
    302     *         exactly the same values.</li>
    303     * </ul>
    304     *
    305     * @param obj  the object (<code>null</code> permitted).
    306     *
    307     * @return A boolean.
    308     */
    309    public boolean equals(Object obj) {
    310        if (obj == this) {
    311            return true;
    312        }
    313        if (!(obj instanceof DefaultXYDataset)) {
    314            return false;
    315        }
    316        DefaultXYDataset that = (DefaultXYDataset) obj;
    317        if (!this.seriesKeys.equals(that.seriesKeys)) {
    318            return false;
    319        }
    320        for (int i = 0; i < this.seriesList.size(); i++) {
    321            double[][] d1 = (double[][]) this.seriesList.get(i);
    322            double[][] d2 = (double[][]) that.seriesList.get(i);
    323            double[] d1x = d1[0];
    324            double[] d2x = d2[0];
    325            if (!Arrays.equals(d1x, d2x)) {
    326                return false;
    327            }
    328            double[] d1y = d1[1];
    329            double[] d2y = d2[1];
    330            if (!Arrays.equals(d1y, d2y)) {
    331                return false;
    332            }
    333        }
    334        return true;
    335    }
    336
    337    /**
    338     * Returns a hash code for this instance.
    339     *
    340     * @return A hash code.
    341     */
    342    public int hashCode() {
    343        int result;
    344        result = this.seriesKeys.hashCode();
    345        result = 29 * result + this.seriesList.hashCode();
    346        return result;
    347    }
    348
    349    /**
    350     * Creates an independent copy of this dataset.
    351     *
    352     * @return The cloned dataset.
    353     *
    354     * @throws CloneNotSupportedException if there is a problem cloning the
    355     *     dataset (for instance, if a non-cloneable object is used for a
    356     *     series key).
    357     */
    358    public Object clone() throws CloneNotSupportedException {
    359        DefaultXYDataset clone = (DefaultXYDataset) super.clone();
    360        clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
    361        clone.seriesList = new ArrayList(this.seriesList.size());
    362        for (int i = 0; i < this.seriesList.size(); i++) {
    363            double[][] data = (double[][]) this.seriesList.get(i);
    364            double[] x = data[0];
    365            double[] y = data[1];
    366            double[] xx = new double[x.length];
    367            double[] yy = new double[y.length];
    368            System.arraycopy(x, 0, xx, 0, x.length);
    369            System.arraycopy(y, 0, yy, 0, y.length);
    370            clone.seriesList.add(i, new double[][] {xx, yy});
    371        }
    372        return clone;
    373    }
    374
    375}