001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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-2022, David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
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.ArrayList;
045import java.util.Collections;
046import java.util.List;
047import java.util.Objects;
048
049import org.jfree.chart.internal.Args;
050import org.jfree.chart.internal.CloneUtils;
051
052import org.jfree.data.general.Series;
053import org.jfree.data.general.SeriesChangeEvent;
054import org.jfree.data.general.SeriesException;
055
056/**
057 * Represents a sequence of zero or more data items in the form (x, y).  By
058 * default, items in the series will be sorted into ascending order by x-value,
059 * and duplicate x-values are permitted.  Both the sorting and duplicate
060 * defaults can be changed in the constructor.  Y-values can be
061 * {@code null} to represent missing values.
062 */
063public class XYSeries<K extends Comparable<K>> extends Series<K> 
064        implements Cloneable, Serializable {
065
066    /** For serialization. */
067    static final long serialVersionUID = -5908509288197150436L;
068
069    // In version 0.9.12, in response to several developer requests, I changed
070    // the 'data' attribute from 'private' to 'protected', so that others can
071    // make subclasses that work directly with the underlying data structure.
072
073    /** Storage for the data items in the series. */
074    protected List<XYDataItem> data;
075
076    /** The maximum number of items for the series. */
077    private int maximumItemCount = Integer.MAX_VALUE;
078
079    /**
080     * A flag that controls whether the items are automatically sorted
081     * (by x-value ascending).
082     */
083    private boolean autoSort;
084
085    /** A flag that controls whether or not duplicate x-values are allowed. */
086    private boolean allowDuplicateXValues;
087
088    /** The lowest x-value in the series, excluding Double.NaN values. */
089    private double minX;
090
091    /** The highest x-value in the series, excluding Double.NaN values. */
092    private double maxX;
093
094    /** The lowest y-value in the series, excluding Double.NaN values. */
095    private double minY;
096
097    /** The highest y-value in the series, excluding Double.NaN values. */
098    private double maxY;
099
100    /**
101     * Creates a new empty series.  By default, items added to the series will
102     * be sorted into ascending order by x-value, and duplicate x-values will
103     * be allowed (these defaults can be modified with another constructor).
104     *
105     * @param key  the series key ({@code null} not permitted).
106     */
107    public XYSeries(K key) {
108        this(key, true, true);
109    }
110
111    /**
112     * Constructs a new empty series, with the auto-sort flag set as requested,
113     * and duplicate values allowed.
114     *
115     * @param key  the series key ({@code null} not permitted).
116     * @param autoSort  a flag that controls whether or not the items in the
117     *                  series are sorted.
118     */
119    public XYSeries(K key, boolean autoSort) {
120        this(key, autoSort, true);
121    }
122
123    /**
124     * Constructs a new xy-series that contains no data.  You can specify
125     * whether or not duplicate x-values are allowed for the series.
126     *
127     * @param key  the series key ({@code null} not permitted).
128     * @param autoSort  a flag that controls whether or not the items in the
129     *                  series are sorted.
130     * @param allowDuplicateXValues  a flag that controls whether duplicate
131     *                               x-values are allowed.
132     */
133    public XYSeries(K key, boolean autoSort, boolean allowDuplicateXValues) {
134        super(key);
135        this.data = new java.util.ArrayList<>();
136        this.autoSort = autoSort;
137        this.allowDuplicateXValues = allowDuplicateXValues;
138        this.minX = Double.NaN;
139        this.maxX = Double.NaN;
140        this.minY = Double.NaN;
141        this.maxY = Double.NaN;
142    }
143
144    /**
145     * Returns the smallest x-value in the series, ignoring any Double.NaN
146     * values.  This method returns Double.NaN if there is no smallest x-value
147     * (for example, when the series is empty).
148     *
149     * @return The smallest x-value.
150     *
151     * @see #getMaxX()
152     *
153     * @since 1.0.13
154     */
155    public double getMinX() {
156        return this.minX;
157    }
158
159    /**
160     * Returns the largest x-value in the series, ignoring any Double.NaN
161     * values.  This method returns Double.NaN if there is no largest x-value
162     * (for example, when the series is empty).
163     *
164     * @return The largest x-value.
165     *
166     * @see #getMinX()
167     *
168     * @since 1.0.13
169     */
170    public double getMaxX() {
171        return this.maxX;
172    }
173
174    /**
175     * Returns the smallest y-value in the series, ignoring any null and
176     * Double.NaN values.  This method returns Double.NaN if there is no
177     * smallest y-value (for example, when the series is empty).
178     *
179     * @return The smallest y-value.
180     *
181     * @see #getMaxY()
182     *
183     * @since 1.0.13
184     */
185    public double getMinY() {
186        return this.minY;
187    }
188
189    /**
190     * Returns the largest y-value in the series, ignoring any Double.NaN
191     * values.  This method returns Double.NaN if there is no largest y-value
192     * (for example, when the series is empty).
193     *
194     * @return The largest y-value.
195     *
196     * @see #getMinY()
197     *
198     * @since 1.0.13
199     */
200    public double getMaxY() {
201        return this.maxY;
202    }
203
204    /**
205     * Updates the cached values for the minimum and maximum data values.
206     *
207     * @param item  the item added ({@code null} not permitted).
208     *
209     * @since 1.0.13
210     */
211    private void updateBoundsForAddedItem(XYDataItem item) {
212        double x = item.getXValue();
213        this.minX = minIgnoreNaN(this.minX, x);
214        this.maxX = maxIgnoreNaN(this.maxX, x);
215        if (item.getY() != null) {
216            double y = item.getYValue();
217            this.minY = minIgnoreNaN(this.minY, y);
218            this.maxY = maxIgnoreNaN(this.maxY, y);
219        }
220    }
221
222    /**
223     * Updates the cached values for the minimum and maximum data values on
224     * the basis that the specified item has just been removed.
225     *
226     * @param item  the item added ({@code null} not permitted).
227     *
228     * @since 1.0.13
229     */
230    private void updateBoundsForRemovedItem(XYDataItem item) {
231        boolean itemContributesToXBounds = false;
232        boolean itemContributesToYBounds = false;
233        double x = item.getXValue();
234        if (!Double.isNaN(x)) {
235            if (x <= this.minX || x >= this.maxX) {
236                itemContributesToXBounds = true;
237            }
238        }
239        if (item.getY() != null) {
240            double y = item.getYValue();
241            if (!Double.isNaN(y)) {
242                if (y <= this.minY || y >= this.maxY) {
243                    itemContributesToYBounds = true;
244                }
245            }
246        }
247        if (itemContributesToYBounds) {
248            findBoundsByIteration();
249        }
250        else if (itemContributesToXBounds) {
251            if (getAutoSort()) {
252                this.minX = getX(0).doubleValue();
253                this.maxX = getX(getItemCount() - 1).doubleValue();
254            }
255            else {
256                findBoundsByIteration();
257            }
258        }
259    }
260
261    /**
262     * Finds the bounds of the x and y values for the series, by iterating
263     * through all the data items.
264     *
265     * @since 1.0.13
266     */
267    private void findBoundsByIteration() {
268        this.minX = Double.NaN;
269        this.maxX = Double.NaN;
270        this.minY = Double.NaN;
271        this.maxY = Double.NaN;
272        for (XYDataItem item : this.data) {
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<XYDataItem> 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(Double.valueOf(x), Double.valueOf(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 = 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 = 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     * Returns a copy of 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 = 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 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 = 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 = 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 = 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    @SuppressWarnings("unchecked")
857    public Object clone() throws CloneNotSupportedException {
858        XYSeries<K> clone = (XYSeries) super.clone();
859        clone.data = CloneUtils.cloneList(this.data);
860        return clone;
861    }
862
863    /**
864     * Creates a new series by copying a subset of the data in this time series.
865     *
866     * @param start  the index of the first item to copy.
867     * @param end  the index of the last item to copy.
868     *
869     * @return A series containing a copy of this series from start until end.
870     *
871     * @throws CloneNotSupportedException if there is a cloning problem.
872     */
873    @SuppressWarnings("unchecked")
874    public XYSeries<K> createCopy(int start, int end)
875            throws CloneNotSupportedException {
876
877        XYSeries<K> copy = (XYSeries) super.clone();
878        copy.data = new ArrayList<>();
879        if (!this.data.isEmpty()) {
880            for (int index = start; index <= end; index++) {
881                XYDataItem item = this.data.get(index);
882                XYDataItem clone = CloneUtils.clone(item);
883                try {
884                    copy.add(clone);
885                }
886                catch (SeriesException e) {
887                    throw new RuntimeException(
888                            "Unable to add cloned data item.", e);
889                }
890            }
891        }
892        return copy;
893
894    }
895
896    /**
897     * Tests this series for equality with an arbitrary object.
898     *
899     * @param obj  the object to test against for equality
900     *             ({@code null} permitted).
901     *
902     * @return A boolean.
903     */
904    @Override
905    @SuppressWarnings("unchecked")
906    public boolean equals(Object obj) {
907        if (obj == this) {
908            return true;
909        }
910        if (!(obj instanceof XYSeries)) {
911            return false;
912        }
913        if (!super.equals(obj)) {
914            return false;
915        }
916        XYSeries<K> that = (XYSeries) obj;
917        if (this.maximumItemCount != that.maximumItemCount) {
918            return false;
919        }
920        if (this.autoSort != that.autoSort) {
921            return false;
922        }
923        if (this.allowDuplicateXValues != that.allowDuplicateXValues) {
924            return false;
925        }
926        if (!Objects.equals(this.data, that.data)) {
927            return false;
928        }
929        return true;
930    }
931
932    /**
933     * Returns a hash code.
934     *
935     * @return A hash code.
936     */
937    @Override
938    public int hashCode() {
939        int result = super.hashCode();
940        // it is too slow to look at every data item, so let's just look at
941        // the first, middle and last items...
942        int count = getItemCount();
943        if (count > 0) {
944            XYDataItem item = getRawDataItem(0);
945            result = 29 * result + item.hashCode();
946        }
947        if (count > 1) {
948            XYDataItem item = getRawDataItem(count - 1);
949            result = 29 * result + item.hashCode();
950        }
951        if (count > 2) {
952            XYDataItem item = getRawDataItem(count / 2);
953            result = 29 * result + item.hashCode();
954        }
955        result = 29 * result + this.maximumItemCount;
956        result = 29 * result + (this.autoSort ? 1 : 0);
957        result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
958        return result;
959    }
960
961}
962