    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2009, 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     * XYSeries.java
    029     * -------------
    030     * (C) Copyright 2001-2009, Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Aaron Metzger;
    034     *                   Jonathan Gabbai;
    035     *                   Richard Atkinson;
    036     *                   Michel Santos;
    037     *                   Ted Schwartz (fix for bug 1955483);
    038     *
    039     * Changes
    040     * -------
    041     * 15-Nov-2001 : Version 1 (DG);
    042     * 03-Apr-2002 : Added an add(double, double) method (DG);
    043     * 29-Apr-2002 : Added a clear() method (ARM);
    044     * 06-Jun-2002 : Updated Javadoc comments (DG);
    045     * 29-Aug-2002 : Modified to give user control over whether or not duplicate
    046     *               x-values are allowed (DG);
    047     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    048     * 11-Nov-2002 : Added maximum item count, code contributed by Jonathan
    049     *               Gabbai (DG);
    050     * 26-Mar-2003 : Implemented Serializable (DG);
    051     * 04-Aug-2003 : Added getItems() method (DG);
    052     * 15-Aug-2003 : Changed 'data' from private to protected, added new add()
    053     *               methods with a 'notify' argument (DG);
    054     * 22-Sep-2003 : Added getAllowDuplicateXValues() method (RA);
    055     * 29-Jan-2004 : Added autoSort attribute, based on a contribution by
    056     *               Michel Santos - see patch 886740 (DG);
    057     * 03-Feb-2004 : Added indexOf() method (DG);
    058     * 16-Feb-2004 : Added remove() method (DG);
    059     * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
    060     * 21-Feb-2005 : Added update(Number, Number) and addOrUpdate(Number, Number)
    061     *               methods (DG);
    062     * 03-May-2005 : Added a new constructor, fixed the setMaximumItemCount()
    063     *               method to remove items (and notify listeners) if necessary,
    064     *               fixed the add() and addOrUpdate() methods to handle unsorted
    065     *               series (DG);
    066     * ------------- JFreeChart 1.0.x ---------------------------------------------
    067     * 11-Jan-2005 : Renamed update(int, Number) --> updateByIndex() (DG);
    068     * 15-Jan-2007 : Added toArray() method (DG);
    069     * 31-Oct-2007 : Implemented faster hashCode() (DG);
    070     * 22-Nov-2007 : Reimplemented clone() (DG);
    071     * 01-May-2008 : Fixed bug 1955483 in addOrUpdate() method, thanks to
    072     *               Ted Schwartz (DG);
    073     * 24-Nov-2008 : Further fix for 1955483 (DG);
    074     * 06-Mar-2009 : Added minX, maxX, minY and maxY fields (DG);
    075     *
    076     */
    077
    078    package org.jfree.data.xy;
    079
    080    import java.io.Serializable;
    081    import java.util.Collections;
    082    import java.util.Iterator;
    083    import java.util.List;
    084
    085    import org.jfree.data.general.Series;
    086    import org.jfree.data.general.SeriesChangeEvent;
    087    import org.jfree.data.general.SeriesException;
    088    import org.jfree.util.ObjectUtilities;
    089
    090    /**
    091     * Represents a sequence of zero or more data items in the form (x, y).  By
    092     * default, items in the series will be sorted into ascending order by x-value,
    093     * and duplicate x-values are permitted.  Both the sorting and duplicate
    094     * defaults can be changed in the constructor.  Y-values can be
    095     * <code>null</code> to represent missing values.
    096     */
    097    public class XYSeries extends Series implements Cloneable, Serializable {
    098
    099        /** For serialization. */
    100        static final long serialVersionUID = -5908509288197150436L;
    101
    102        // In version 0.9.12, in response to several developer requests, I changed
    103        // the 'data' attribute from 'private' to 'protected', so that others can
    104        // make subclasses that work directly with the underlying data structure.
    105
    106        /** Storage for the data items in the series. */
    107        protected List data;
    108
    109        /** The maximum number of items for the series. */
    110        private int maximumItemCount = Integer.MAX_VALUE;
    111
    112        /**
    113         * A flag that controls whether the items are automatically sorted
    114         * (by x-value ascending).
    115         */
    116        private boolean autoSort;
    117
    118        /** A flag that controls whether or not duplicate x-values are allowed. */
    119        private boolean allowDuplicateXValues;
    120
    121        /** The lowest x-value in the series, excluding Double.NaN values. */
    122        private double minX;
    123
    124        /** The highest x-value in the series, excluding Double.NaN values. */
    125        private double maxX;
    126
    127        /** The lowest y-value in the series, excluding Double.NaN values. */
    128        private double minY;
    129
    130        /** The highest y-value in the series, excluding Double.NaN values. */
    131        private double maxY;
    132
    133        /**
    134         * Creates a new empty series.  By default, items added to the series will
    135         * be sorted into ascending order by x-value, and duplicate x-values will
    136         * be allowed (these defaults can be modified with another constructor.
    137         *
    138         * @param key  the series key (<code>null</code> not permitted).
    139         */
    140        public XYSeries(Comparable key) {
    141            this(key, true, true);
    142        }
    143
    144        /**
    145         * Constructs a new empty series, with the auto-sort flag set as requested,
    146         * and duplicate values allowed.
    147         *
    148         * @param key  the series key (<code>null</code> not permitted).
    149         * @param autoSort  a flag that controls whether or not the items in the
    150         *                  series are sorted.
    151         */
    152        public XYSeries(Comparable key, boolean autoSort) {
    153            this(key, autoSort, true);
    154        }
    155
    156        /**
    157         * Constructs a new xy-series that contains no data.  You can specify
    158         * whether or not duplicate x-values are allowed for the series.
    159         *
    160         * @param key  the series key (<code>null</code> not permitted).
    161         * @param autoSort  a flag that controls whether or not the items in the
    162         *                  series are sorted.
    163         * @param allowDuplicateXValues  a flag that controls whether duplicate
    164         *                               x-values are allowed.
    165         */
    166        public XYSeries(Comparable key, boolean autoSort,
    167                boolean allowDuplicateXValues) {
    168            super(key);
    169            this.data = new java.util.ArrayList();
    170            this.autoSort = autoSort;
    171            this.allowDuplicateXValues = allowDuplicateXValues;
    172            this.minX = Double.NaN;
    173            this.maxX = Double.NaN;
    174            this.minY = Double.NaN;
    175            this.maxY = Double.NaN;
    176        }
    177
    178        /**
    179         * Returns the smallest x-value in the series, ignoring any Double.NaN
    180         * values.  This method returns Double.NaN if there is no smallest x-value
    181         * (for example, when the series is empty).
    182         *
    183         * @return The smallest x-value.
    184         *
    185         * @see #getMaxX()
    186         *
    187         * @since 1.0.13
    188         */
    189        public double getMinX() {
    190            return this.minX;
    191        }
    192
    193        /**
    194         * Returns the largest x-value in the series, ignoring any Double.NaN
    195         * values.  This method returns Double.NaN if there is no largest x-value
    196         * (for example, when the series is empty).
    197         *
    198         * @return The largest x-value.
    199         *
    200         * @see #getMinX()
    201         *
    202         * @since 1.0.13
    203         */
    204        public double getMaxX() {
    205            return this.maxX;
    206        }
    207
    208        /**
    209         * Returns the smallest y-value in the series, ignoring any null and
    210         * Double.NaN values.  This method returns Double.NaN if there is no
    211         * smallest y-value (for example, when the series is empty).
    212         *
    213         * @return The smallest y-value.
    214         *
    215         * @see #getMaxY()
    216         *
    217         * @since 1.0.13
    218         */
    219        public double getMinY() {
    220            return this.minY;
    221        }
    222
    223        /**
    224         * Returns the largest y-value in the series, ignoring any Double.NaN
    225         * values.  This method returns Double.NaN if there is no largest y-value
    226         * (for example, when the series is empty).
    227         *
    228         * @return The largest y-value.
    229         *
    230         * @see #getMinY()
    231         *
    232         * @since 1.0.13
    233         */
    234        public double getMaxY() {
    235            return this.maxY;
    236        }
    237
    238        /**
    239         * Updates the cached values for the minimum and maximum data values.
    240         *
    241         * @param item  the item added (<code>null</code> not permitted).
    242         *
    243         * @since 1.0.13
    244         */
    245        private void updateBoundsForAddedItem(XYDataItem item) {
    246            double x = item.getXValue();
    247            this.minX = minIgnoreNaN(this.minX, x);
    248            this.maxX = maxIgnoreNaN(this.maxX, x);
    249            if (item.getY() != null) {
    250                double y = item.getYValue();
    251                this.minY = minIgnoreNaN(this.minY, y);
    252                this.maxY = maxIgnoreNaN(this.maxY, y);
    253            }
    254        }
    255
    256        /**
    257         * Updates the cached values for the minimum and maximum data values on
    258         * the basis that the specified item has just been removed.
    259         *
    260         * @param item  the item added (<code>null</code> not permitted).
    261         *
    262         * @since 1.0.13
    263         */
    264        private void updateBoundsForRemovedItem(XYDataItem item) {
    265            boolean itemContributesToXBounds = false;
    266            boolean itemContributesToYBounds = false;
    267            double x = item.getXValue();
    268            if (!Double.isNaN(x)) {
    269                if (x <= this.minX || x >= this.maxX) {
    270                    itemContributesToXBounds = true;
    271                }
    272            }
    273            if (item.getY() != null) {
    274                double y = item.getYValue();
    275                if (!Double.isNaN(y)) {
    276                    if (y <= this.minY || y >= this.maxY) {
    277                        itemContributesToYBounds = true;
    278                    }
    279                }
    280            }
    281            if (itemContributesToYBounds) {
    282                findBoundsByIteration();
    283            }
    284            else if (itemContributesToXBounds) {
    285                if (getAutoSort()) {
    286                    this.minX = getX(0).doubleValue();
    287                    this.maxX = getX(getItemCount() - 1).doubleValue();
    288                }
    289                else {
    290                    findBoundsByIteration();
    291                }
    292            }
    293        }
    294
    295        /**
    296         * Finds the bounds of the x and y values for the series, by iterating
    297         * through all the data items.
    298         *
    299         * @since 1.0.13
    300         */
    301        private void findBoundsByIteration() {
    302            this.minX = Double.NaN;
    303            this.maxX = Double.NaN;
    304            this.minY = Double.NaN;
    305            this.maxY = Double.NaN;
    306            Iterator iterator = this.data.iterator();
    307            while (iterator.hasNext()) {
    308                XYDataItem item = (XYDataItem) iterator.next();
    309                updateBoundsForAddedItem(item);
    310            }
    311        }
    312
    313        /**
    314         * Returns the flag that controls whether the items in the series are
    315         * automatically sorted.  There is no setter for this flag, it must be
    316         * defined in the series constructor.
    317         *
    318         * @return A boolean.
    319         */
    320        public boolean getAutoSort() {
    321            return this.autoSort;
    322        }
    323
    324        /**
    325         * Returns a flag that controls whether duplicate x-values are allowed.
    326         * This flag can only be set in the constructor.
    327         *
    328         * @return A boolean.
    329         */
    330        public boolean getAllowDuplicateXValues() {
    331            return this.allowDuplicateXValues;
    332        }
    333
    334        /**
    335         * Returns the number of items in the series.
    336         *
    337         * @return The item count.
    338         *
    339         * @see #getItems()
    340         */
    341        public int getItemCount() {
    342            return this.data.size();
    343        }
    344
    345        /**
    346         * Returns the list of data items for the series (the list contains
    347         * {@link XYDataItem} objects and is unmodifiable).
    348         *
    349         * @return The list of data items.
    350         */
    351        public List getItems() {
    352            return Collections.unmodifiableList(this.data);
    353        }
    354
    355        /**
    356         * Returns the maximum number of items that will be retained in the series.
    357         * The default value is <code>Integer.MAX_VALUE</code>.
    358         *
    359         * @return The maximum item count.
    360         *
    361         * @see #setMaximumItemCount(int)
    362         */
    363        public int getMaximumItemCount() {
    364            return this.maximumItemCount;
    365        }
    366
    367        /**
    368         * Sets the maximum number of items that will be retained in the series.
    369         * If you add a new item to the series such that the number of items will
    370         * exceed the maximum item count, then the first element in the series is
    371         * automatically removed, ensuring that the maximum item count is not
    372         * exceeded.
    373         * <p>
    374         * Typically this value is set before the series is populated with data,
    375         * but if it is applied later, it may cause some items to be removed from
    376         * the series (in which case a {@link SeriesChangeEvent} will be sent to
    377         * all registered listeners).
    378         *
    379         * @param maximum  the maximum number of items for the series.
    380         */
    381        public void setMaximumItemCount(int maximum) {
    382            this.maximumItemCount = maximum;
    383            int remove = this.data.size() - maximum;
    384            if (remove > 0) {
    385                this.data.subList(0, remove).clear();
    386                findBoundsByIteration();
    387                fireSeriesChanged();
    388            }
    389        }
    390
    391        /**
    392         * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
    393         * all registered listeners.
    394         *
    395         * @param item  the (x, y) item (<code>null</code> not permitted).
    396         */
    397        public void add(XYDataItem item) {
    398            // argument checking delegated...
    399            add(item, true);
    400        }
    401
    402        /**
    403         * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
    404         * all registered listeners.
    405         *
    406         * @param x  the x value.
    407         * @param y  the y value.
    408         */
    409        public void add(double x, double y) {
    410            add(new Double(x), new Double(y), true);
    411        }
    412
    413        /**
    414         * Adds a data item to the series and, if requested, sends a
    415         * {@link SeriesChangeEvent} to all registered listeners.
    416         *
    417         * @param x  the x value.
    418         * @param y  the y value.
    419         * @param notify  a flag that controls whether or not a
    420         *                {@link SeriesChangeEvent} is sent to all registered
    421         *                listeners.
    422         */
    423        public void add(double x, double y, boolean notify) {
    424            add(new Double(x), new Double(y), notify);
    425        }
    426
    427        /**
    428         * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
    429         * all registered listeners.  The unusual pairing of parameter types is to
    430         * make it easier to add <code>null</code> y-values.
    431         *
    432         * @param x  the x value.
    433         * @param y  the y value (<code>null</code> permitted).
    434         */
    435        public void add(double x, Number y) {
    436            add(new Double(x), y);
    437        }
    438
    439        /**
    440         * Adds a data item to the series and, if requested, sends a
    441         * {@link SeriesChangeEvent} to all registered listeners.  The unusual
    442         * pairing of parameter types is to make it easier to add null y-values.
    443         *
    444         * @param x  the x value.
    445         * @param y  the y value (<code>null</code> permitted).
    446         * @param notify  a flag that controls whether or not a
    447         *                {@link SeriesChangeEvent} is sent to all registered
    448         *                listeners.
    449         */
    450        public void add(double x, Number y, boolean notify) {
    451            add(new Double(x), y, notify);
    452        }
    453
    454        /**
    455         * Adds a new data item to the series (in the correct position if the
    456         * <code>autoSort</code> flag is set for the series) and sends a
    457         * {@link SeriesChangeEvent} to all registered listeners.
    458         * <P>
    459         * Throws an exception if the x-value is a duplicate AND the
    460         * allowDuplicateXValues flag is false.
    461         *
    462         * @param x  the x-value (<code>null</code> not permitted).
    463         * @param y  the y-value (<code>null</code> permitted).
    464         *
    465         * @throws SeriesException if the x-value is a duplicate and the
    466         *     <code>allowDuplicateXValues</code> flag is not set for this series.
    467         */
    468        public void add(Number x, Number y) {
    469            // argument checking delegated...
    470            add(x, y, true);
    471        }
    472
    473        /**
    474         * Adds new data to the series and, if requested, sends a
    475         * {@link SeriesChangeEvent} to all registered listeners.
    476         * <P>
    477         * Throws an exception if the x-value is a duplicate AND the
    478         * allowDuplicateXValues flag is false.
    479         *
    480         * @param x  the x-value (<code>null</code> not permitted).
    481         * @param y  the y-value (<code>null</code> permitted).
    482         * @param notify  a flag the controls whether or not a
    483         *                {@link SeriesChangeEvent} is sent to all registered
    484         *                listeners.
    485         */
    486        public void add(Number x, Number y, boolean notify) {
    487            // delegate argument checking to XYDataItem...
    488            XYDataItem item = new XYDataItem(x, y);
    489            add(item, notify);
    490        }
    491
    492        /**
    493         * Adds a data item to the series and, if requested, sends a
    494         * {@link SeriesChangeEvent} to all registered listeners.
    495         *
    496         * @param item  the (x, y) item (<code>null</code> not permitted).
    497         * @param notify  a flag that controls whether or not a
    498         *                {@link SeriesChangeEvent} is sent to all registered
    499         *                listeners.
    500         */
    501        public void add(XYDataItem item, boolean notify) {
    502            if (item == null) {
    503                throw new IllegalArgumentException("Null 'item' argument.");
    504            }
    505            if (this.autoSort) {
    506                int index = Collections.binarySearch(this.data, item);
    507                if (index < 0) {
    508                    this.data.add(-index - 1, item);
    509                }
    510                else {
    511                    if (this.allowDuplicateXValues) {
    512                        // need to make sure we are adding *after* any duplicates
    513                        int size = this.data.size();
    514                        while (index < size && item.compareTo(
    515                                this.data.get(index)) == 0) {
    516                            index++;
    517                        }
    518                        if (index < this.data.size()) {
    519                            this.data.add(index, item);
    520                        }
    521                        else {
    522                            this.data.add(item);
    523                        }
    524                    }
    525                    else {
    526                        throw new SeriesException("X-value already exists.");
    527                    }
    528                }
    529            }
    530            else {
    531                if (!this.allowDuplicateXValues) {
    532                    // can't allow duplicate values, so we need to check whether
    533                    // there is an item with the given x-value already
    534                    int index = indexOf(item.getX());
    535                    if (index >= 0) {
    536                        throw new SeriesException("X-value already exists.");
    537                    }
    538                }
    539                this.data.add(item);
    540            }
    541            updateBoundsForAddedItem(item);
    542            if (getItemCount() > this.maximumItemCount) {
    543                XYDataItem removed = (XYDataItem) this.data.remove(0);
    544                updateBoundsForRemovedItem(removed);
    545            }
    546            if (notify) {
    547                fireSeriesChanged();
    548            }
    549        }
    550
    551        /**
    552         * Deletes a range of items from the series and sends a
    553         * {@link SeriesChangeEvent} to all registered listeners.
    554         *
    555         * @param start  the start index (zero-based).
    556         * @param end  the end index (zero-based).
    557         */
    558        public void delete(int start, int end) {
    559            this.data.subList(start, end + 1).clear();
    560            findBoundsByIteration();
    561            fireSeriesChanged();
    562        }
    563
    564        /**
    565         * Removes the item at the specified index and sends a
    566         * {@link SeriesChangeEvent} to all registered listeners.
    567         *
    568         * @param index  the index.
    569         *
    570         * @return The item removed.
    571         */
    572        public XYDataItem remove(int index) {
    573            XYDataItem removed = (XYDataItem) this.data.remove(index);
    574            updateBoundsForRemovedItem(removed);
    575            fireSeriesChanged();
    576            return removed;
    577        }
    578
    579        /**
    580         * Removes an item with the specified x-value and sends a
    581         * {@link SeriesChangeEvent} to all registered listeners.  Note that when
    582         * a series permits multiple items with the same x-value, this method
    583         * could remove any one of the items with that x-value.
    584         *
    585         * @param x  the x-value.
    586
    587         * @return The item removed.
    588         */
    589        public XYDataItem remove(Number x) {
    590            return remove(indexOf(x));
    591        }
    592
    593        /**
    594         * Removes all data items from the series and sends a
    595         * {@link SeriesChangeEvent} to all registered listeners.
    596         */
    597        public void clear() {
    598            if (this.data.size() > 0) {
    599                this.data.clear();
    600                this.minX = Double.NaN;
    601                this.maxX = Double.NaN;
    602                this.minY = Double.NaN;
    603                this.maxY = Double.NaN;
    604                fireSeriesChanged();
    605            }
    606        }
    607
    608        /**
    609         * Return the data item with the specified index.
    610         *
    611         * @param index  the index.
    612         *
    613         * @return The data item with the specified index.
    614         */
    615        public XYDataItem getDataItem(int index) {
    616            return (XYDataItem) this.data.get(index);
    617        }
    618
    619        /**
    620         * Returns the x-value at the specified index.
    621         *
    622         * @param index  the index (zero-based).
    623         *
    624         * @return The x-value (never <code>null</code>).
    625         */
    626        public Number getX(int index) {
    627            return getDataItem(index).getX();
    628        }
    629
    630        /**
    631         * Returns the y-value at the specified index.
    632         *
    633         * @param index  the index (zero-based).
    634         *
    635         * @return The y-value (possibly <code>null</code>).
    636         */
    637        public Number getY(int index) {
    638            return getDataItem(index).getY();
    639        }
    640
    641        /**
    642         * Updates the value of an item in the series and sends a
    643         * {@link SeriesChangeEvent} to all registered listeners.
    644         *
    645         * @param index  the item (zero based index).
    646         * @param y  the new value (<code>null</code> permitted).
    647         *
    648         * @deprecated Renamed {@link #updateByIndex(int, Number)} to avoid
    649         *         confusion with the {@link #update(Number, Number)} method.
    650         */
    651        public void update(int index, Number y) {
    652            XYDataItem item = getDataItem(index);
    653
    654            // figure out if we need to iterate through all the y-values
    655            boolean iterate = false;
    656            double oldY = item.getYValue();
    657            if (!Double.isNaN(oldY)) {
    658                iterate = oldY <= this.minY || oldY >= this.maxY;
    659            }
    660            item.setY(y);
    661
    662            if (iterate) {
    663                findBoundsByIteration();
    664            }
    665            else if (y != null) {
    666                double yy = y.doubleValue();
    667                this.minY = minIgnoreNaN(this.minY, yy);
    668                this.maxY = maxIgnoreNaN(this.maxY, yy);
    669            }
    670            fireSeriesChanged();
    671        }
    672
    673        /**
    674         * A function to find the minimum of two values, but ignoring any
    675         * Double.NaN values.
    676         *
    677         * @param a  the first value.
    678         * @param b  the second value.
    679         *
    680         * @return The minimum of the two values.
    681         */
    682        private double minIgnoreNaN(double a, double b) {
    683            if (Double.isNaN(a)) {
    684                return b;
    685            }
    686            else {
    687                if (Double.isNaN(b)) {
    688                    return a;
    689                }
    690                else {
    691                    return Math.min(a, b);
    692                }
    693            }
    694        }
    695
    696        /**
    697         * A function to find the maximum of two values, but ignoring any
    698         * Double.NaN values.
    699         *
    700         * @param a  the first value.
    701         * @param b  the second value.
    702         *
    703         * @return The maximum of the two values.
    704         */
    705        private double maxIgnoreNaN(double a, double b) {
    706            if (Double.isNaN(a)) {
    707                return b;
    708            }
    709            else {
    710                if (Double.isNaN(b)) {
    711                    return a;
    712                }
    713                else {
    714                    return Math.max(a, b);
    715                }
    716            }
    717        }
    718
    719        /**
    720         * Updates the value of an item in the series and sends a
    721         * {@link SeriesChangeEvent} to all registered listeners.
    722         *
    723         * @param index  the item (zero based index).
    724         * @param y  the new value (<code>null</code> permitted).
    725         *
    726         * @since 1.0.1
    727         */
    728        public void updateByIndex(int index, Number y) {
    729            update(index, y);
    730        }
    731
    732        /**
    733         * Updates an item in the series.
    734         *
    735         * @param x  the x-value (<code>null</code> not permitted).
    736         * @param y  the y-value (<code>null</code> permitted).
    737         *
    738         * @throws SeriesException if there is no existing item with the specified
    739         *         x-value.
    740         */
    741        public void update(Number x, Number y) {
    742            int index = indexOf(x);
    743            if (index < 0) {
    744                throw new SeriesException("No observation for x = " + x);
    745            }
    746            else {
    747                updateByIndex(index, y);
    748            }
    749        }
    750
    751        /**
    752         * Adds or updates an item in the series and sends a
    753         * {@link SeriesChangeEvent} to all registered listeners.
    754         *
    755         * @param x  the x-value.
    756         * @param y  the y-value.
    757         *
    758         * @return The item that was overwritten, if any.
    759         *
    760         * @since 1.0.10
    761         */
    762        public XYDataItem addOrUpdate(double x, double y) {
    763            return addOrUpdate(new Double(x), new Double(y));
    764        }
    765
    766        /**
    767         * Adds or updates an item in the series and sends a
    768         * {@link SeriesChangeEvent} to all registered listeners.
    769         *
    770         * @param x  the x-value (<code>null</code> not permitted).
    771         * @param y  the y-value (<code>null</code> permitted).
    772         *
    773         * @return A copy of the overwritten data item, or <code>null</code> if no
    774         *         item was overwritten.
    775         */
    776        public XYDataItem addOrUpdate(Number x, Number y) {
    777            if (x == null) {
    778                throw new IllegalArgumentException("Null 'x' argument.");
    779            }
    780            if (this.allowDuplicateXValues) {
    781                add(x, y);
    782                return null;
    783            }
    784
    785            // if we get to here, we know that duplicate X values are not permitted
    786            XYDataItem overwritten = null;
    787            int index = indexOf(x);
    788            if (index >= 0) {
    789                XYDataItem existing = (XYDataItem) this.data.get(index);
    790                try {
    791                    overwritten = (XYDataItem) existing.clone();
    792                }
    793                catch (CloneNotSupportedException e) {
    794                    throw new SeriesException("Couldn't clone XYDataItem!");
    795                }
    796                // figure out if we need to iterate through all the y-values
    797                boolean iterate = false;
    798                double oldY = existing.getYValue();
    799                if (!Double.isNaN(oldY)) {
    800                    iterate = oldY <= this.minY || oldY >= this.maxY;
    801                }
    802                existing.setY(y);
    803
    804                if (iterate) {
    805                    findBoundsByIteration();
    806                }
    807                else if (y != null) {
    808                    double yy = y.doubleValue();
    809                    this.minY = minIgnoreNaN(this.minY, yy);
    810                    this.maxY = minIgnoreNaN(this.maxY, yy);
    811                }
    812            }
    813            else {
    814                // if the series is sorted, the negative index is a result from
    815                // Collections.binarySearch() and tells us where to insert the
    816                // new item...otherwise it will be just -1 and we should just
    817                // append the value to the list...
    818                XYDataItem item = new XYDataItem(x, y);
    819                if (this.autoSort) {
    820                    this.data.add(-index - 1, item);
    821                }
    822                else {
    823                    this.data.add(item);
    824                }
    825                updateBoundsForAddedItem(item);
    826
    827                // check if this addition will exceed the maximum item count...
    828                if (getItemCount() > this.maximumItemCount) {
    829                    XYDataItem removed = (XYDataItem) this.data.remove(0);
    830                    updateBoundsForRemovedItem(removed);
    831                }
    832            }
    833            fireSeriesChanged();
    834            return overwritten;
    835        }
    836
    837        /**
    838         * Returns the index of the item with the specified x-value, or a negative
    839         * index if the series does not contain an item with that x-value.  Be
    840         * aware that for an unsorted series, the index is found by iterating
    841         * through all items in the series.
    842         *
    843         * @param x  the x-value (<code>null</code> not permitted).
    844         *
    845         * @return The index.
    846         */
    847        public int indexOf(Number x) {
    848            if (this.autoSort) {
    849                return Collections.binarySearch(this.data, new XYDataItem(x, null));
    850            }
    851            else {
    852                for (int i = 0; i < this.data.size(); i++) {
    853                    XYDataItem item = (XYDataItem) this.data.get(i);
    854                    if (item.getX().equals(x)) {
    855                        return i;
    856                    }
    857                }
    858                return -1;
    859            }
    860        }
    861
    862        /**
    863         * Returns a new array containing the x and y values from this series.
    864         *
    865         * @return A new array containing the x and y values from this series.
    866         *
    867         * @since 1.0.4
    868         */
    869        public double[][] toArray() {
    870            int itemCount = getItemCount();
    871            double[][] result = new double[2][itemCount];
    872            for (int i = 0; i < itemCount; i++) {
    873                result[0][i] = this.getX(i).doubleValue();
    874                Number y = getY(i);
    875                if (y != null) {
    876                    result[1][i] = y.doubleValue();
    877                }
    878                else {
    879                    result[1][i] = Double.NaN;
    880                }
    881            }
    882            return result;
    883        }
    884
    885        /**
    886         * Returns a clone of the series.
    887         *
    888         * @return A clone of the series.
    889         *
    890         * @throws CloneNotSupportedException if there is a cloning problem.
    891         */
    892        public Object clone() throws CloneNotSupportedException {
    893            XYSeries clone = (XYSeries) super.clone();
    894            clone.data = (List) ObjectUtilities.deepClone(this.data);
    895            return clone;
    896        }
    897
    898        /**
    899         * Creates a new series by copying a subset of the data in this time series.
    900         *
    901         * @param start  the index of the first item to copy.
    902         * @param end  the index of the last item to copy.
    903         *
    904         * @return A series containing a copy of this series from start until end.
    905         *
    906         * @throws CloneNotSupportedException if there is a cloning problem.
    907         */
    908        public XYSeries createCopy(int start, int end)
    909                throws CloneNotSupportedException {
    910
    911            XYSeries copy = (XYSeries) super.clone();
    912            copy.data = new java.util.ArrayList();
    913            if (this.data.size() > 0) {
    914                for (int index = start; index <= end; index++) {
    915                    XYDataItem item = (XYDataItem) this.data.get(index);
    916                    XYDataItem clone = (XYDataItem) item.clone();
    917                    try {
    918                        copy.add(clone);
    919                    }
    920                    catch (SeriesException e) {
    921                        System.err.println("Unable to add cloned data item.");
    922                    }
    923                }
    924            }
    925            return copy;
    926
    927        }
    928
    929        /**
    930         * Tests this series for equality with an arbitrary object.
    931         *
    932         * @param obj  the object to test against for equality
    933         *             (<code>null</code> permitted).
    934         *
    935         * @return A boolean.
    936         */
    937        public boolean equals(Object obj) {
    938            if (obj == this) {
    939                return true;
    940            }
    941            if (!(obj instanceof XYSeries)) {
    942                return false;
    943            }
    944            if (!super.equals(obj)) {
    945                return false;
    946            }
    947            XYSeries that = (XYSeries) obj;
    948            if (this.maximumItemCount != that.maximumItemCount) {
    949                return false;
    950            }
    951            if (this.autoSort != that.autoSort) {
    952                return false;
    953            }
    954            if (this.allowDuplicateXValues != that.allowDuplicateXValues) {
    955                return false;
    956            }
    957            if (!ObjectUtilities.equal(this.data, that.data)) {
    958                return false;
    959            }
    960            return true;
    961        }
    962
    963        /**
    964         * Returns a hash code.
    965         *
    966         * @return A hash code.
    967         */
    968        public int hashCode() {
    969            int result = super.hashCode();
    970            // it is too slow to look at every data item, so let's just look at
    971            // the first, middle and last items...
    972            int count = getItemCount();
    973            if (count > 0) {
    974                XYDataItem item = getDataItem(0);
    975                result = 29 * result + item.hashCode();
    976            }
    977            if (count > 1) {
    978                XYDataItem item = getDataItem(count - 1);
    979                result = 29 * result + item.hashCode();
    980            }
    981            if (count > 2) {
    982                XYDataItem item = getDataItem(count / 2);
    983                result = 29 * result + item.hashCode();
    984            }
    985            result = 29 * result + this.maximumItemCount;
    986            result = 29 * result + (this.autoSort ? 1 : 0);
    987            result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
    988            return result;
    989        }
    990
    991    }
    992