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