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