001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * -------------
028: * XYSeries.java
029: * -------------
030: * (C) Copyright 2001-2007, 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: *
038: * $Id: XYSeries.java,v 1.13.2.5 2007/04/04 10:24:07 mungady Exp $
039: *
040: * Changes
041: * -------
042: * 15-Nov-2001 : Version 1 (DG);
043: * 03-Apr-2002 : Added an add(double, double) method (DG);
044: * 29-Apr-2002 : Added a clear() method (ARM);
045: * 06-Jun-2002 : Updated Javadoc comments (DG);
046: * 29-Aug-2002 : Modified to give user control over whether or not duplicate
047: * x-values are allowed (DG);
048: * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
049: * 11-Nov-2002 : Added maximum item count, code contributed by Jonathan
050: * Gabbai (DG);
051: * 26-Mar-2003 : Implemented Serializable (DG);
052: * 04-Aug-2003 : Added getItems() method (DG);
053: * 15-Aug-2003 : Changed 'data' from private to protected, added new add()
054: * methods with a 'notify' argument (DG);
055: * 22-Sep-2003 : Added getAllowDuplicateXValues() method (RA);
056: * 29-Jan-2004 : Added autoSort attribute, based on a contribution by
057: * Michel Santos - see patch 886740 (DG);
058: * 03-Feb-2004 : Added indexOf() method (DG);
059: * 16-Feb-2004 : Added remove() method (DG);
060: * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
061: * 21-Feb-2005 : Added update(Number, Number) and addOrUpdate(Number, Number)
062: * methods (DG);
063: * 03-May-2005 : Added a new constructor, fixed the setMaximumItemCount()
064: * method to remove items (and notify listeners) if necessary,
065: * fixed the add() and addOrUpdate() methods to handle unsorted
066: * series (DG);
067: * ------------- JFreeChart 1.0.x ---------------------------------------------
068: * 11-Jan-2005 : Renamed update(int, Number) --> updateByIndex() (DG);
069: * 15-Jan-2007 : Added toArray() method (DG);
070: *
071: */
072:
073: package org.jfree.data.xy;
074:
075: import java.io.Serializable;
076: import java.util.Collections;
077: import java.util.List;
078:
079: import org.jfree.data.general.Series;
080: import org.jfree.data.general.SeriesChangeEvent;
081: import org.jfree.data.general.SeriesException;
082: import org.jfree.util.ObjectUtilities;
083:
084: /**
085: * Represents a sequence of zero or more data items in the form (x, y). By
086: * default, items in the series will be sorted into ascending order by x-value,
087: * and duplicate x-values are permitted. Both the sorting and duplicate
088: * defaults can be changed in the constructor. Y-values can be
089: * <code>null</code> to represent missing values.
090: */
091: public class XYSeries extends Series implements Cloneable, Serializable {
092:
093: /** For serialization. */
094: static final long serialVersionUID = -5908509288197150436L;
095:
096: // In version 0.9.12, in response to several developer requests, I changed
097: // the 'data' attribute from 'private' to 'protected', so that others can
098: // make subclasses that work directly with the underlying data structure.
099:
100: /** Storage for the data items in the series. */
101: protected List data;
102:
103: /** The maximum number of items for the series. */
104: private int maximumItemCount = Integer.MAX_VALUE;
105:
106: /** A flag that controls whether the items are automatically sorted. */
107: private boolean autoSort;
108:
109: /** A flag that controls whether or not duplicate x-values are allowed. */
110: private boolean allowDuplicateXValues;
111:
112: /**
113: * Creates a new empty series. By default, items added to the series will
114: * be sorted into ascending order by x-value, and duplicate x-values will
115: * be allowed (these defaults can be modified with another constructor.
116: *
117: * @param key the series key (<code>null</code> not permitted).
118: */
119: public XYSeries(Comparable key) {
120: this (key, true, true);
121: }
122:
123: /**
124: * Constructs a new empty series, with the auto-sort flag set as requested,
125: * and duplicate values allowed.
126: *
127: * @param key the series key (<code>null</code> not permitted).
128: * @param autoSort a flag that controls whether or not the items in the
129: * series are sorted.
130: */
131: public XYSeries(Comparable key, boolean autoSort) {
132: this (key, autoSort, true);
133: }
134:
135: /**
136: * Constructs a new xy-series that contains no data. You can specify
137: * whether or not duplicate x-values are allowed for the series.
138: *
139: * @param key the series key (<code>null</code> not permitted).
140: * @param autoSort a flag that controls whether or not the items in the
141: * series are sorted.
142: * @param allowDuplicateXValues a flag that controls whether duplicate
143: * x-values are allowed.
144: */
145: public XYSeries(Comparable key, boolean autoSort,
146: boolean allowDuplicateXValues) {
147: super (key);
148: this .data = new java.util.ArrayList();
149: this .autoSort = autoSort;
150: this .allowDuplicateXValues = allowDuplicateXValues;
151: }
152:
153: /**
154: * Returns the flag that controls whether the items in the series are
155: * automatically sorted. There is no setter for this flag, it must be
156: * defined in the series constructor.
157: *
158: * @return A boolean.
159: */
160: public boolean getAutoSort() {
161: return this .autoSort;
162: }
163:
164: /**
165: * Returns a flag that controls whether duplicate x-values are allowed.
166: * This flag can only be set in the constructor.
167: *
168: * @return A boolean.
169: */
170: public boolean getAllowDuplicateXValues() {
171: return this .allowDuplicateXValues;
172: }
173:
174: /**
175: * Returns the number of items in the series.
176: *
177: * @return The item count.
178: */
179: public int getItemCount() {
180: return this .data.size();
181: }
182:
183: /**
184: * Returns the list of data items for the series (the list contains
185: * {@link XYDataItem} objects and is unmodifiable).
186: *
187: * @return The list of data items.
188: */
189: public List getItems() {
190: return Collections.unmodifiableList(this .data);
191: }
192:
193: /**
194: * Returns the maximum number of items that will be retained in the series.
195: * The default value is <code>Integer.MAX_VALUE</code>.
196: *
197: * @return The maximum item count.
198: * @see #setMaximumItemCount(int)
199: */
200: public int getMaximumItemCount() {
201: return this .maximumItemCount;
202: }
203:
204: /**
205: * Sets the maximum number of items that will be retained in the series.
206: * If you add a new item to the series such that the number of items will
207: * exceed the maximum item count, then the first element in the series is
208: * automatically removed, ensuring that the maximum item count is not
209: * exceeded.
210: * <p>
211: * Typically this value is set before the series is populated with data,
212: * but if it is applied later, it may cause some items to be removed from
213: * the series (in which case a {@link SeriesChangeEvent} will be sent to
214: * all registered listeners.
215: *
216: * @param maximum the maximum number of items for the series.
217: */
218: public void setMaximumItemCount(int maximum) {
219: this .maximumItemCount = maximum;
220: boolean dataRemoved = false;
221: while (this .data.size() > maximum) {
222: this .data.remove(0);
223: dataRemoved = true;
224: }
225: if (dataRemoved) {
226: fireSeriesChanged();
227: }
228: }
229:
230: /**
231: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
232: * all registered listeners.
233: *
234: * @param item the (x, y) item (<code>null</code> not permitted).
235: */
236: public void add(XYDataItem item) {
237: // argument checking delegated...
238: add(item, true);
239: }
240:
241: /**
242: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
243: * all registered listeners.
244: *
245: * @param x the x value.
246: * @param y the y value.
247: */
248: public void add(double x, double y) {
249: add(new Double(x), new Double(y), true);
250: }
251:
252: /**
253: * Adds a data item to the series and, if requested, sends a
254: * {@link SeriesChangeEvent} to all registered listeners.
255: *
256: * @param x the x value.
257: * @param y the y value.
258: * @param notify a flag that controls whether or not a
259: * {@link SeriesChangeEvent} is sent to all registered
260: * listeners.
261: */
262: public void add(double x, double y, boolean notify) {
263: add(new Double(x), new Double(y), notify);
264: }
265:
266: /**
267: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
268: * all registered listeners. The unusual pairing of parameter types is to
269: * make it easier to add <code>null</code> y-values.
270: *
271: * @param x the x value.
272: * @param y the y value (<code>null</code> permitted).
273: */
274: public void add(double x, Number y) {
275: add(new Double(x), y);
276: }
277:
278: /**
279: * Adds a data item to the series and, if requested, sends a
280: * {@link SeriesChangeEvent} to all registered listeners. The unusual
281: * pairing of parameter types is to make it easier to add null y-values.
282: *
283: * @param x the x value.
284: * @param y the y value (<code>null</code> permitted).
285: * @param notify a flag that controls whether or not a
286: * {@link SeriesChangeEvent} is sent to all registered
287: * listeners.
288: */
289: public void add(double x, Number y, boolean notify) {
290: add(new Double(x), y, notify);
291: }
292:
293: /**
294: * Adds new data to the series and sends a {@link SeriesChangeEvent} to
295: * all registered listeners.
296: * <P>
297: * Throws an exception if the x-value is a duplicate AND the
298: * allowDuplicateXValues flag is false.
299: *
300: * @param x the x-value (<code>null</code> not permitted).
301: * @param y the y-value (<code>null</code> permitted).
302: */
303: public void add(Number x, Number y) {
304: // argument checking delegated...
305: add(x, y, true);
306: }
307:
308: /**
309: * Adds new data to the series and, if requested, sends a
310: * {@link SeriesChangeEvent} to all registered listeners.
311: * <P>
312: * Throws an exception if the x-value is a duplicate AND the
313: * allowDuplicateXValues flag is false.
314: *
315: * @param x the x-value (<code>null</code> not permitted).
316: * @param y the y-value (<code>null</code> permitted).
317: * @param notify a flag the controls whether or not a
318: * {@link SeriesChangeEvent} is sent to all registered
319: * listeners.
320: */
321: public void add(Number x, Number y, boolean notify) {
322: // delegate argument checking to XYDataItem...
323: XYDataItem item = new XYDataItem(x, y);
324: add(item, notify);
325: }
326:
327: /**
328: * Adds a data item to the series and, if requested, sends a
329: * {@link SeriesChangeEvent} to all registered listeners.
330: *
331: * @param item the (x, y) item (<code>null</code> not permitted).
332: * @param notify a flag that controls whether or not a
333: * {@link SeriesChangeEvent} is sent to all registered
334: * listeners.
335: */
336: public void add(XYDataItem item, boolean notify) {
337:
338: if (item == null) {
339: throw new IllegalArgumentException("Null 'item' argument.");
340: }
341:
342: if (this .autoSort) {
343: int index = Collections.binarySearch(this .data, item);
344: if (index < 0) {
345: this .data.add(-index - 1, item);
346: } else {
347: if (this .allowDuplicateXValues) {
348: // need to make sure we are adding *after* any duplicates
349: int size = this .data.size();
350: while (index < size
351: && item.compareTo(this .data.get(index)) == 0) {
352: index++;
353: }
354: if (index < this .data.size()) {
355: this .data.add(index, item);
356: } else {
357: this .data.add(item);
358: }
359: } else {
360: throw new SeriesException("X-value already exists.");
361: }
362: }
363: } else {
364: if (!this .allowDuplicateXValues) {
365: // can't allow duplicate values, so we need to check whether
366: // there is an item with the given x-value already
367: int index = indexOf(item.getX());
368: if (index >= 0) {
369: throw new SeriesException("X-value already exists.");
370: }
371: }
372: this .data.add(item);
373: }
374: if (getItemCount() > this .maximumItemCount) {
375: this .data.remove(0);
376: }
377: if (notify) {
378: fireSeriesChanged();
379: }
380: }
381:
382: /**
383: * Deletes a range of items from the series and sends a
384: * {@link SeriesChangeEvent} to all registered listeners.
385: *
386: * @param start the start index (zero-based).
387: * @param end the end index (zero-based).
388: */
389: public void delete(int start, int end) {
390: for (int i = start; i <= end; i++) {
391: this .data.remove(start);
392: }
393: fireSeriesChanged();
394: }
395:
396: /**
397: * Removes the item at the specified index and sends a
398: * {@link SeriesChangeEvent} to all registered listeners.
399: *
400: * @param index the index.
401: *
402: * @return The item removed.
403: */
404: public XYDataItem remove(int index) {
405: XYDataItem result = (XYDataItem) this .data.remove(index);
406: fireSeriesChanged();
407: return result;
408: }
409:
410: /**
411: * Removes the item with the specified x-value and sends a
412: * {@link SeriesChangeEvent} to all registered listeners.
413: *
414: * @param x the x-value.
415:
416: * @return The item removed.
417: */
418: public XYDataItem remove(Number x) {
419: return remove(indexOf(x));
420: }
421:
422: /**
423: * Removes all data items from the series.
424: */
425: public void clear() {
426: if (this .data.size() > 0) {
427: this .data.clear();
428: fireSeriesChanged();
429: }
430: }
431:
432: /**
433: * Return the data item with the specified index.
434: *
435: * @param index the index.
436: *
437: * @return The data item with the specified index.
438: */
439: public XYDataItem getDataItem(int index) {
440: return (XYDataItem) this .data.get(index);
441: }
442:
443: /**
444: * Returns the x-value at the specified index.
445: *
446: * @param index the index (zero-based).
447: *
448: * @return The x-value (never <code>null</code>).
449: */
450: public Number getX(int index) {
451: return getDataItem(index).getX();
452: }
453:
454: /**
455: * Returns the y-value at the specified index.
456: *
457: * @param index the index (zero-based).
458: *
459: * @return The y-value (possibly <code>null</code>).
460: */
461: public Number getY(int index) {
462: return getDataItem(index).getY();
463: }
464:
465: /**
466: * Updates the value of an item in the series and sends a
467: * {@link SeriesChangeEvent} to all registered listeners.
468: *
469: * @param index the item (zero based index).
470: * @param y the new value (<code>null</code> permitted).
471: *
472: * @deprecated Renamed {@link #updateByIndex(int, Number)} to avoid
473: * confusion with the {@link #update(Number, Number)} method.
474: */
475: public void update(int index, Number y) {
476: XYDataItem item = getDataItem(index);
477: item.setY(y);
478: fireSeriesChanged();
479: }
480:
481: /**
482: * Updates the value of an item in the series and sends a
483: * {@link SeriesChangeEvent} to all registered listeners.
484: *
485: * @param index the item (zero based index).
486: * @param y the new value (<code>null</code> permitted).
487: *
488: * @since 1.0.1
489: */
490: public void updateByIndex(int index, Number y) {
491: update(index, y);
492: }
493:
494: /**
495: * Updates an item in the series.
496: *
497: * @param x the x-value (<code>null</code> not permitted).
498: * @param y the y-value (<code>null</code> permitted).
499: *
500: * @throws SeriesException if there is no existing item with the specified
501: * x-value.
502: */
503: public void update(Number x, Number y) {
504: int index = indexOf(x);
505: if (index < 0) {
506: throw new SeriesException("No observation for x = " + x);
507: } else {
508: XYDataItem item = getDataItem(index);
509: item.setY(y);
510: fireSeriesChanged();
511: }
512: }
513:
514: /**
515: * Adds or updates an item in the series and sends a
516: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
517: * listeners.
518: *
519: * @param x the x-value (<code>null</code> not permitted).
520: * @param y the y-value (<code>null</code> permitted).
521: *
522: * @return A copy of the overwritten data item, or <code>null</code> if no
523: * item was overwritten.
524: */
525: public XYDataItem addOrUpdate(Number x, Number y) {
526: if (x == null) {
527: throw new IllegalArgumentException("Null 'x' argument.");
528: }
529: XYDataItem overwritten = null;
530: int index = indexOf(x);
531: if (index >= 0) {
532: XYDataItem existing = (XYDataItem) this .data.get(index);
533: try {
534: overwritten = (XYDataItem) existing.clone();
535: } catch (CloneNotSupportedException e) {
536: throw new SeriesException("Couldn't clone XYDataItem!");
537: }
538: existing.setY(y);
539: } else {
540: // if the series is sorted, the negative index is a result from
541: // Collections.binarySearch() and tells us where to insert the
542: // new item...otherwise it will be just -1 and we should just
543: // append the value to the list...
544: if (this .autoSort) {
545: this .data.add(-index - 1, new XYDataItem(x, y));
546: } else {
547: this .data.add(new XYDataItem(x, y));
548: }
549: // check if this addition will exceed the maximum item count...
550: if (getItemCount() > this .maximumItemCount) {
551: this .data.remove(0);
552: }
553: }
554: fireSeriesChanged();
555: return overwritten;
556: }
557:
558: /**
559: * Returns the index of the item with the specified x-value, or a negative
560: * index if the series does not contain an item with that x-value. Be
561: * aware that for an unsorted series, the index is found by iterating
562: * through all items in the series.
563: *
564: * @param x the x-value (<code>null</code> not permitted).
565: *
566: * @return The index.
567: */
568: public int indexOf(Number x) {
569: if (this .autoSort) {
570: return Collections.binarySearch(this .data, new XYDataItem(
571: x, null));
572: } else {
573: for (int i = 0; i < this .data.size(); i++) {
574: XYDataItem item = (XYDataItem) this .data.get(i);
575: if (item.getX().equals(x)) {
576: return i;
577: }
578: }
579: return -1;
580: }
581: }
582:
583: /**
584: * Returns a new array containing the x and y values from this series.
585: *
586: * @return A new array containing the x and y values from this series.
587: *
588: * @since 1.0.4
589: */
590: public double[][] toArray() {
591: int itemCount = getItemCount();
592: double[][] result = new double[2][itemCount];
593: for (int i = 0; i < itemCount; i++) {
594: result[0][i] = this .getX(i).doubleValue();
595: Number y = getY(i);
596: if (y != null) {
597: result[1][i] = y.doubleValue();
598: } else {
599: result[1][i] = Double.NaN;
600: }
601: }
602: return result;
603: }
604:
605: /**
606: * Returns a clone of the series.
607: *
608: * @return A clone of the time series.
609: *
610: * @throws CloneNotSupportedException if there is a cloning problem.
611: */
612: public Object clone() throws CloneNotSupportedException {
613: Object clone = createCopy(0, getItemCount() - 1);
614: return clone;
615: }
616:
617: /**
618: * Creates a new series by copying a subset of the data in this time series.
619: *
620: * @param start the index of the first item to copy.
621: * @param end the index of the last item to copy.
622: *
623: * @return A series containing a copy of this series from start until end.
624: *
625: * @throws CloneNotSupportedException if there is a cloning problem.
626: */
627: public XYSeries createCopy(int start, int end)
628: throws CloneNotSupportedException {
629:
630: XYSeries copy = (XYSeries) super .clone();
631: copy.data = new java.util.ArrayList();
632: if (this .data.size() > 0) {
633: for (int index = start; index <= end; index++) {
634: XYDataItem item = (XYDataItem) this .data.get(index);
635: XYDataItem clone = (XYDataItem) item.clone();
636: try {
637: copy.add(clone);
638: } catch (SeriesException e) {
639: System.err
640: .println("Unable to add cloned data item.");
641: }
642: }
643: }
644: return copy;
645:
646: }
647:
648: /**
649: * Tests this series for equality with an arbitrary object.
650: *
651: * @param obj the object to test against for equality
652: * (<code>null</code> permitted).
653: *
654: * @return A boolean.
655: */
656: public boolean equals(Object obj) {
657: if (obj == this ) {
658: return true;
659: }
660: if (!(obj instanceof XYSeries)) {
661: return false;
662: }
663: if (!super .equals(obj)) {
664: return false;
665: }
666: XYSeries that = (XYSeries) obj;
667: if (this .maximumItemCount != that.maximumItemCount) {
668: return false;
669: }
670: if (this .autoSort != that.autoSort) {
671: return false;
672: }
673: if (this .allowDuplicateXValues != that.allowDuplicateXValues) {
674: return false;
675: }
676: if (!ObjectUtilities.equal(this .data, that.data)) {
677: return false;
678: }
679: return true;
680: }
681:
682: /**
683: * Returns a hash code.
684: *
685: * @return A hash code.
686: */
687: public int hashCode() {
688: int result = super .hashCode();
689: result = 29 * result
690: + (this .data != null ? this .data.hashCode() : 0);
691: result = 29 * result + this .maximumItemCount;
692: result = 29 * result + (this .autoSort ? 1 : 0);
693: result = 29 * result + (this .allowDuplicateXValues ? 1 : 0);
694: return result;
695: }
696:
697: }
|