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