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