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