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