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