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