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