1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public License
  20. * along with this library; if not, write to the Free Software Foundation,
  21. * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  22. *
  23. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  24. * in the United States and other countries.]
  25. *
  26. * -------------
  27. * XYSeries.java
  28. * -------------
  29. * (C) Copyright 2001-2005, Object Refinery Limited and Contributors.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): Aaron Metzger;
  33. * Jonathan Gabbai;
  34. * Richard Atkinson;
  35. * Michel Santos;
  36. *
  37. * $Id: XYSeries.java,v 1.8 2005/02/21 17:29:39 mungady Exp $
  38. *
  39. * Changes
  40. * -------
  41. * 15-Nov-2001 : Version 1 (DG);
  42. * 03-Apr-2002 : Added an add(double, double) method (DG);
  43. * 29-Apr-2002 : Added a clear() method (ARM);
  44. * 06-Jun-2002 : Updated Javadoc comments (DG);
  45. * 29-Aug-2002 : Modified to give user control over whether or not duplicate
  46. * x-values are allowed (DG);
  47. * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48. * 11-Nov-2002 : Added maximum item count, code contributed by Jonathan
  49. * Gabbai (DG);
  50. * 26-Mar-2003 : Implemented Serializable (DG);
  51. * 04-Aug-2003 : Added getItems() method (DG);
  52. * 15-Aug-2003 : Changed 'data' from private to protected, added new add()
  53. * methods with a 'notify' argument (DG);
  54. * 22-Sep-2003 : Added getAllowDuplicateXValues() method (RA);
  55. * 29-Jan-2004 : Added autoSort attribute, based on a contribution by
  56. * Michel Santos - see patch 886740 (DG);
  57. * 03-Feb-2004 : Added indexOf() method (DG);
  58. * 16-Feb-2004 : Added remove() method (DG);
  59. * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
  60. * 21-Feb-2005 : Added update(Number, Number) and addOrUpdate(Number, Number)
  61. * methods (DG);
  62. *
  63. */
  64. package org.jfree.data.xy;
  65. import java.io.Serializable;
  66. import java.util.Collections;
  67. import java.util.List;
  68. import org.jfree.data.general.Series;
  69. import org.jfree.data.general.SeriesChangeEvent;
  70. import org.jfree.data.general.SeriesException;
  71. import org.jfree.util.ObjectUtilities;
  72. /**
  73. * Represents a sequence of zero or more data items in the form (x, y). Items
  74. * in the series will be sorted into ascending order by X-value, and duplicate
  75. * X-values are permitted. Both the sorting and duplicate defaults can be
  76. * changed in the constructor. Y-values can be <code>null</code> to represent
  77. * missing values.
  78. */
  79. public class XYSeries extends Series implements Cloneable, Serializable {
  80. // In version 0.9.12, in response to several developer requests, I changed
  81. // the 'data' attribute from 'private' to 'protected', so that others can
  82. // make subclasses that work directly with the underlying data structure.
  83. /** Storage for the data items in the series. */
  84. protected List data;
  85. /** The maximum number of items for the series. */
  86. private int maximumItemCount = Integer.MAX_VALUE;
  87. /** A flag that controls whether the items are automatically sorted. */
  88. private boolean autoSort;
  89. /** A flag that controls whether or not duplicate x-values are allowed. */
  90. private boolean allowDuplicateXValues;
  91. /**
  92. * Creates a new empty series. By default, items added to the series will
  93. * be sorted into ascending order by x-value, and duplicate x-values will
  94. * be allowed (these defaults can be modified with another constructor.
  95. *
  96. * @param name the series name (<code>null</code> not permitted).
  97. */
  98. public XYSeries(String name) {
  99. this(name, true, true);
  100. }
  101. /**
  102. * Constructs a new xy-series that contains no data. You can specify
  103. * whether or not duplicate x-values are allowed for the series.
  104. *
  105. * @param name the series name (<code>null</code> not permitted).
  106. * @param autoSort a flag that controls whether or not the items in the
  107. * series are sorted.
  108. * @param allowDuplicateXValues a flag that controls whether duplicate
  109. * x-values are allowed.
  110. */
  111. public XYSeries(String name,
  112. boolean autoSort,
  113. boolean allowDuplicateXValues) {
  114. super(name);
  115. this.data = new java.util.ArrayList();
  116. this.autoSort = autoSort;
  117. this.allowDuplicateXValues = allowDuplicateXValues;
  118. }
  119. /**
  120. * Returns the flag that controls whether the items in the series are
  121. * automatically sorted. There is no setter for this flag, it must be
  122. * defined in the series constructor.
  123. *
  124. * @return A boolean.
  125. */
  126. public boolean getAutoSort() {
  127. return this.autoSort;
  128. }
  129. /**
  130. * Returns a flag that controls whether duplicate x-values are allowed.
  131. * This flag can only be set in the constructor.
  132. *
  133. * @return A boolean.
  134. */
  135. public boolean getAllowDuplicateXValues() {
  136. return this.allowDuplicateXValues;
  137. }
  138. /**
  139. * Returns the number of items in the series.
  140. *
  141. * @return The item count.
  142. */
  143. public int getItemCount() {
  144. return this.data.size();
  145. }
  146. /**
  147. * Returns the list of data items for the series (the list contains
  148. * {@link XYDataItem} objects and is unmodifiable).
  149. *
  150. * @return The list of data items.
  151. */
  152. public List getItems() {
  153. return Collections.unmodifiableList(this.data);
  154. }
  155. /**
  156. * Returns the maximum number of items that will be retained in the series.
  157. * The default value is <code>Integer.MAX_VALUE</code>).
  158. *
  159. * @return The maximum item count.
  160. */
  161. public int getMaximumItemCount() {
  162. return this.maximumItemCount;
  163. }
  164. /**
  165. * Sets the maximum number of items that will be retained in the series.
  166. * <P>
  167. * If you add a new item to the series such that the number of items will
  168. * exceed the maximum item count, then the FIRST element in the series is
  169. * automatically removed, ensuring that the maximum item count is not
  170. * exceeded.
  171. *
  172. * @param maximum the maximum.
  173. */
  174. public void setMaximumItemCount(int maximum) {
  175. this.maximumItemCount = maximum;
  176. }
  177. /**
  178. * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
  179. * all registered listeners.
  180. *
  181. * @param item the (x, y) item (<code>null</code> not permitted).
  182. */
  183. public void add(XYDataItem item) {
  184. // argument checking delegated...
  185. add(item, true);
  186. }
  187. /**
  188. * Adds a data item to the series and, if requested, sends a
  189. * {@link SeriesChangeEvent} to all registered listeners.
  190. *
  191. * @param item the (x, y) item (<code>null</code> not permitted).
  192. * @param notify a flag that controls whether or not a
  193. * {@link SeriesChangeEvent} is sent to all registered
  194. * listeners.
  195. */
  196. public void add(XYDataItem item, boolean notify) {
  197. if (item == null) {
  198. throw new IllegalArgumentException("Null 'item' argument.");
  199. }
  200. int index = Collections.binarySearch(this.data, item);
  201. if (index < 0) {
  202. if (this.autoSort) {
  203. this.data.add(-index - 1, item);
  204. }
  205. else {
  206. this.data.add(item);
  207. }
  208. if (getItemCount() > this.maximumItemCount) {
  209. this.data.remove(0);
  210. }
  211. }
  212. else {
  213. if (this.allowDuplicateXValues) {
  214. if (this.autoSort) {
  215. // need to make sure we are adding *after* any duplicates
  216. int size = this.data.size();
  217. while (index < size
  218. && item.compareTo(this.data.get(index)) == 0) {
  219. index++;
  220. }
  221. if (index < this.data.size()) {
  222. this.data.add(index, item);
  223. }
  224. else {
  225. this.data.add(item);
  226. }
  227. }
  228. else {
  229. this.data.add(item);
  230. }
  231. if (getItemCount() > this.maximumItemCount) {
  232. this.data.remove(0);
  233. }
  234. }
  235. else {
  236. throw new SeriesException("X-value already exists.");
  237. }
  238. }
  239. if (notify) {
  240. fireSeriesChanged();
  241. }
  242. }
  243. /**
  244. * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
  245. * all registered listeners.
  246. *
  247. * @param x the x value.
  248. * @param y the y value.
  249. */
  250. public void add(double x, double y) {
  251. add(new Double(x), new Double(y), true);
  252. }
  253. /**
  254. * Adds a data item to the series and, if requested, sends a
  255. * {@link SeriesChangeEvent} to all registered listeners.
  256. *
  257. * @param x the x value.
  258. * @param y the y value.
  259. * @param notify a flag that controls whether or not a
  260. * {@link SeriesChangeEvent} is sent to all registered
  261. * listeners.
  262. */
  263. public void add(double x, double y, boolean notify) {
  264. add(new Double(x), new Double(y), notify);
  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. * Adds a data item to the series and, if requested, sends a
  279. * {@link SeriesChangeEvent} to all registered listeners. The unusual
  280. * pairing of parameter types is to make it easier to add null y-values.
  281. *
  282. * @param x the x value.
  283. * @param y the y value (<code>null</code> permitted).
  284. * @param notify a flag that controls whether or not a
  285. * {@link SeriesChangeEvent} is sent to all registered
  286. * listeners.
  287. */
  288. public void add(double x, Number y, boolean notify) {
  289. add(new Double(x), y, notify);
  290. }
  291. /**
  292. * Adds new data to the series and sends a {@link SeriesChangeEvent} to
  293. * all registered listeners.
  294. * <P>
  295. * Throws an exception if the x-value is a duplicate AND the
  296. * allowDuplicateXValues flag is false.
  297. *
  298. * @param x the x-value (<code>null</code> not permitted).
  299. * @param y the y-value (<code>null</code> permitted).
  300. */
  301. public void add(Number x, Number y) {
  302. // argument checking delegated...
  303. add(x, y, true);
  304. }
  305. /**
  306. * Adds new data to the series and, if requested, sends a
  307. * {@link SeriesChangeEvent} to all registered listeners.
  308. * <P>
  309. * Throws an exception if the x-value is a duplicate AND the
  310. * allowDuplicateXValues flag is false.
  311. *
  312. * @param x the x-value (<code>null</code> not permitted).
  313. * @param y the y-value (<code>null</code> permitted).
  314. * @param notify a flag the controls whether or not a
  315. * {@link SeriesChangeEvent} is sent to all registered
  316. * listeners.
  317. */
  318. public void add(Number x, Number y, boolean notify) {
  319. if (x == null) {
  320. throw new IllegalArgumentException("Null 'x' argument.");
  321. }
  322. XYDataItem item = new XYDataItem(x, y);
  323. add(item, notify);
  324. }
  325. /**
  326. * Deletes a range of items from the series and sends a
  327. * {@link SeriesChangeEvent} to all registered listeners.
  328. *
  329. * @param start the start index (zero-based).
  330. * @param end the end index (zero-based).
  331. */
  332. public void delete(int start, int end) {
  333. for (int i = start; i <= end; i++) {
  334. this.data.remove(start);
  335. }
  336. fireSeriesChanged();
  337. }
  338. /**
  339. * Removes the item at the specified index.
  340. *
  341. * @param index the index.
  342. *
  343. * @return The item removed.
  344. */
  345. public XYDataItem remove(int index) {
  346. return (XYDataItem) this.data.remove(index);
  347. }
  348. /**
  349. * Removes the item(s) with the specified x-value.
  350. *
  351. * @param x the x-value.
  352. * @return The item removed.
  353. */
  354. public XYDataItem remove(Number x) {
  355. return remove(indexOf(x));
  356. }
  357. /**
  358. * Removes all data items from the series.
  359. */
  360. public void clear() {
  361. if (this.data.size() > 0) {
  362. this.data.clear();
  363. fireSeriesChanged();
  364. }
  365. }
  366. /**
  367. * Return the data item with the specified index.
  368. *
  369. * @param index the index.
  370. *
  371. * @return The data item with the specified index.
  372. */
  373. public XYDataItem getDataItem(int index) {
  374. return (XYDataItem) this.data.get(index);
  375. }
  376. /**
  377. * Returns the x-value at the specified index.
  378. *
  379. * @param index the index (zero-based).
  380. *
  381. * @return The x-value (never <code>null</code>).
  382. */
  383. public Number getX(int index) {
  384. return getDataItem(index).getX();
  385. }
  386. /**
  387. * Returns the y-value at the specified index.
  388. *
  389. * @param index the index (zero-based).
  390. *
  391. * @return The y-value (possibly <code>null</code>).
  392. */
  393. public Number getY(int index) {
  394. return getDataItem(index).getY();
  395. }
  396. /**
  397. * Updates the value of an item in the series and sends a
  398. * {@link SeriesChangeEvent} to all registered listeners.
  399. *
  400. * @param index the item (zero based index).
  401. * @param y the new value (<code>null</code> permitted).
  402. */
  403. public void update(int index, Number y) {
  404. XYDataItem item = getDataItem(index);
  405. item.setY(y);
  406. fireSeriesChanged();
  407. }
  408. /**
  409. * Updates an item in the series.
  410. *
  411. * @param x the x-value (<code>null</code> not permitted).
  412. * @param y the y-value (<code>null</code> permitted).
  413. *
  414. * @throws SeriesException if there is no existing item with the specified
  415. * x-value.
  416. */
  417. public void update(Number x, Number y) {
  418. XYDataItem item = new XYDataItem(x, y);
  419. int index = Collections.binarySearch(this.data, item);
  420. if (index < 0) {
  421. throw new SeriesException("No observation for x = " + x);
  422. }
  423. else {
  424. this.data.set(index, item);
  425. }
  426. }
  427. /**
  428. * Adds or updates an item in the series and sends a
  429. * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  430. * listeners.
  431. *
  432. * @param x the x-value (<code>null</code> not permitted).
  433. * @param y the y-value (<code>null</code> permitted).
  434. *
  435. * @return A copy of the overwritten data item, or <code>null</code> if no
  436. * item was overwritten.
  437. */
  438. public XYDataItem addOrUpdate(Number x,Number y) {
  439. if (x == null) {
  440. throw new IllegalArgumentException("Null 'x' argument.");
  441. }
  442. XYDataItem overwritten = null;
  443. XYDataItem key = new XYDataItem(x, y);
  444. int index = Collections.binarySearch(this.data, key);
  445. if (index >= 0) {
  446. XYDataItem existing = (XYDataItem) this.data.get(index);
  447. try {
  448. overwritten = (XYDataItem) existing.clone();
  449. }
  450. catch (CloneNotSupportedException e) {
  451. throw new SeriesException("Couldn't clone XYDataItem!");
  452. }
  453. existing.setY(y);
  454. fireSeriesChanged();
  455. }
  456. else {
  457. this.data.add(-index - 1, new XYDataItem(x, y));
  458. // check if this addition will exceed the maximum item count...
  459. if (getItemCount() > this.maximumItemCount) {
  460. this.data.remove(0);
  461. }
  462. fireSeriesChanged();
  463. }
  464. return overwritten;
  465. }
  466. /**
  467. * Returns the index of the item with the specified x-value. Note: if the
  468. * series is not sorted in order of ascending x-values, the result is
  469. * undefined.
  470. *
  471. * @param x the x-value (<code>null</code> not permitted).
  472. *
  473. * @return The index.
  474. */
  475. public int indexOf(Number x) {
  476. return Collections.binarySearch(this.data, new XYDataItem(x, null));
  477. }
  478. /**
  479. * Returns a clone of the series.
  480. *
  481. * @return A clone of the time series.
  482. *
  483. * @throws CloneNotSupportedException if there is a cloning problem.
  484. */
  485. public Object clone() throws CloneNotSupportedException {
  486. Object clone = createCopy(0, getItemCount() - 1);
  487. return clone;
  488. }
  489. /**
  490. * Creates a new series by copying a subset of the data in this time series.
  491. *
  492. * @param start the index of the first item to copy.
  493. * @param end the index of the last item to copy.
  494. *
  495. * @return A series containing a copy of this series from start until end.
  496. *
  497. * @throws CloneNotSupportedException if there is a cloning problem.
  498. */
  499. public XYSeries createCopy(int start, int end)
  500. throws CloneNotSupportedException {
  501. XYSeries copy = (XYSeries) super.clone();
  502. copy.data = new java.util.ArrayList();
  503. if (this.data.size() > 0) {
  504. for (int index = start; index <= end; index++) {
  505. XYDataItem item = (XYDataItem) this.data.get(index);
  506. XYDataItem clone = (XYDataItem) item.clone();
  507. try {
  508. copy.add(clone);
  509. }
  510. catch (SeriesException e) {
  511. System.err.println("Unable to add cloned data item.");
  512. }
  513. }
  514. }
  515. return copy;
  516. }
  517. /**
  518. * Tests this series for equality with an arbitrary object.
  519. *
  520. * @param object the object to test against for equality
  521. * (<code>null</code> permitted).
  522. *
  523. * @return A boolean.
  524. */
  525. public boolean equals(Object object) {
  526. if (object == null) {
  527. return false;
  528. }
  529. if (object == this) {
  530. return true;
  531. }
  532. if (!super.equals(object)) {
  533. return false;
  534. }
  535. if (!(object instanceof XYSeries)) {
  536. return false;
  537. }
  538. XYSeries s = (XYSeries) object;
  539. if (this.maximumItemCount != s.maximumItemCount) {
  540. return false;
  541. }
  542. if (this.autoSort != s.autoSort) {
  543. return false;
  544. }
  545. if (this.allowDuplicateXValues != s.allowDuplicateXValues) {
  546. return false;
  547. }
  548. if (!ObjectUtilities.equal(this.data, s.data)) {
  549. return false;
  550. }
  551. return true;
  552. }
  553. /**
  554. * Returns a hash code.
  555. *
  556. * @return A hash code.
  557. */
  558. public int hashCode() {
  559. int result = super.hashCode();
  560. result = 29 * result + (this.data != null ? this.data.hashCode() : 0);
  561. result = 29 * result + this.maximumItemCount;
  562. result = 29 * result + (this.autoSort ? 1 : 0);
  563. result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
  564. return result;
  565. }
  566. }