    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
    080    package org.jfree.data.xy;
    081
    082    import java.io.Serializable;
    083    import java.util.Collections;
    084    import java.util.Iterator;
    085    import java.util.List;
    086
    087    import org.jfree.data.general.Series;
    088    import org.jfree.data.general.SeriesChangeEvent;
    089    import org.jfree.data.general.SeriesException;
    090    import 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     */
    099    public 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