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 under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * ---------------------
  25. * TimePeriodValues.java
  26. * ---------------------
  27. * (C) Copyright 2003-2005, by Object Refinery Limited.
  28. *
  29. * Original Author: David Gilbert (for Object Refinery Limited);
  30. * Contributor(s): -;
  31. *
  32. * $Id: TimePeriodValues.java,v 1.3 2005/03/08 10:49:31 mungady Exp $
  33. *
  34. * Changes
  35. * -------
  36. * 22-Apr-2003 : Version 1 (DG);
  37. * 30-Jul-2003 : Added clone and equals methods while testing (DG);
  38. *
  39. */
  40. package org.jfree.data.time;
  41. import java.io.Serializable;
  42. import java.util.ArrayList;
  43. import java.util.List;
  44. import org.jfree.data.general.Series;
  45. import org.jfree.data.general.SeriesException;
  46. /**
  47. * A structure containing zero, one or many {@link TimePeriodValue} instances. The time periods
  48. * can overlap, and are maintained in the order that they are added to the collection.
  49. * <p>
  50. * This is similar to the {@link TimeSeries} class, except that the time periods can have
  51. * irregular lengths.
  52. */
  53. public class TimePeriodValues extends Series implements Serializable {
  54. /** Default value for the domain description. */
  55. protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time";
  56. /** Default value for the range description. */
  57. protected static final String DEFAULT_RANGE_DESCRIPTION = "Value";
  58. /** A description of the domain. */
  59. private String domain;
  60. /** A description of the range. */
  61. private String range;
  62. /** The list of data pairs in the series. */
  63. private List data;
  64. /** Index of the time period with the minimum start milliseconds. */
  65. private int minStartIndex = -1;
  66. /** Index of the time period with the maximum start milliseconds. */
  67. private int maxStartIndex = -1;
  68. /** Index of the time period with the minimum middle milliseconds. */
  69. private int minMiddleIndex = -1;
  70. /** Index of the time period with the maximum middle milliseconds. */
  71. private int maxMiddleIndex = -1;
  72. /** Index of the time period with the minimum end milliseconds. */
  73. private int minEndIndex = -1;
  74. /** Index of the time period with the maximum end milliseconds. */
  75. private int maxEndIndex = -1;
  76. /**
  77. * Creates a new (empty) collection of time period values.
  78. *
  79. * @param name the name of the series.
  80. */
  81. public TimePeriodValues(String name) {
  82. this(
  83. name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION
  84. );
  85. }
  86. /**
  87. * Creates a new time series that contains no data.
  88. * <P>
  89. * Descriptions can be specified for the domain and range. One situation
  90. * where this is helpful is when generating a chart for the time series -
  91. * axis labels can be taken from the domain and range description.
  92. *
  93. * @param name the name of the series.
  94. * @param domain the domain description.
  95. * @param range the range description.
  96. */
  97. public TimePeriodValues(String name, String domain, String range) {
  98. super(name);
  99. this.domain = domain;
  100. this.range = range;
  101. this.data = new ArrayList();
  102. }
  103. /**
  104. * Returns the domain description.
  105. *
  106. * @return The domain description.
  107. */
  108. public String getDomainDescription() {
  109. return this.domain;
  110. }
  111. /**
  112. * Sets the domain description.
  113. * <P>
  114. * A property change event is fired, and an undoable edit is posted.
  115. *
  116. * @param description the new description.
  117. */
  118. public void setDomainDescription(String description) {
  119. String old = this.domain;
  120. this.domain = description;
  121. firePropertyChange("Domain", old, description);
  122. }
  123. /**
  124. * Returns the range description.
  125. *
  126. * @return The range description.
  127. */
  128. public String getRangeDescription() {
  129. return this.range;
  130. }
  131. /**
  132. * Sets the range description.
  133. * <P>
  134. * Registered listeners are notified of the change.
  135. *
  136. * @param description the new description.
  137. */
  138. public void setRangeDescription(String description) {
  139. String old = this.range;
  140. this.range = description;
  141. firePropertyChange("Range", old, description);
  142. }
  143. /**
  144. * Returns the number of items in the series.
  145. *
  146. * @return The item count.
  147. */
  148. public int getItemCount() {
  149. return this.data.size();
  150. }
  151. /**
  152. * Returns one data item for the series.
  153. *
  154. * @param index the item index (zero-based).
  155. *
  156. * @return One data item for the series.
  157. */
  158. public TimePeriodValue getDataItem(int index) {
  159. return (TimePeriodValue) this.data.get(index);
  160. }
  161. /**
  162. * Returns the time period at the specified index.
  163. *
  164. * @param index the index of the data pair.
  165. *
  166. * @return The time period at the specified index.
  167. */
  168. public TimePeriod getTimePeriod(int index) {
  169. return getDataItem(index).getPeriod();
  170. }
  171. /**
  172. * Returns the value at the specified index.
  173. *
  174. * @param index index of a value.
  175. *
  176. * @return The value at the specified index.
  177. */
  178. public Number getValue(int index) {
  179. return getDataItem(index).getValue();
  180. }
  181. /**
  182. * Adds a data item to the series.
  183. *
  184. * @param item the (timeperiod, value) pair.
  185. */
  186. public void add(TimePeriodValue item) {
  187. // check arguments...
  188. if (item == null) {
  189. throw new IllegalArgumentException("Null item not allowed.");
  190. }
  191. // make the change
  192. this.data.add(item);
  193. updateBounds(item.getPeriod(), this.data.size() - 1);
  194. }
  195. /**
  196. * Update the index values for the maximum and minimum bounds.
  197. *
  198. * @param period the time period.
  199. * @param index the index of the time period.
  200. */
  201. private void updateBounds(TimePeriod period, int index) {
  202. long start = period.getStart().getTime();
  203. long end = period.getEnd().getTime();
  204. long middle = start + ((end - start) / 2);
  205. if (this.minStartIndex >= 0) {
  206. long minStart = getDataItem(this.minStartIndex).getPeriod().getStart().getTime();
  207. if (start < minStart) {
  208. this.minStartIndex = index;
  209. }
  210. }
  211. else {
  212. this.minStartIndex = index;
  213. }
  214. if (this.maxStartIndex >= 0) {
  215. long maxStart = getDataItem(this.maxStartIndex).getPeriod().getStart().getTime();
  216. if (start > maxStart) {
  217. this.maxStartIndex = index;
  218. }
  219. }
  220. else {
  221. this.maxStartIndex = index;
  222. }
  223. if (this.minMiddleIndex >= 0) {
  224. long s = getDataItem(this.minMiddleIndex).getPeriod().getStart().getTime();
  225. long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd().getTime();
  226. long minMiddle = s + (e - s) / 2;
  227. if (middle < minMiddle) {
  228. this.minMiddleIndex = index;
  229. }
  230. }
  231. else {
  232. this.minMiddleIndex = index;
  233. }
  234. if (this.maxMiddleIndex >= 0) {
  235. long s = getDataItem(this.minMiddleIndex).getPeriod().getStart().getTime();
  236. long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd().getTime();
  237. long maxMiddle = s + (e - s) / 2;
  238. if (middle > maxMiddle) {
  239. this.maxMiddleIndex = index;
  240. }
  241. }
  242. else {
  243. this.maxMiddleIndex = index;
  244. }
  245. if (this.minEndIndex >= 0) {
  246. long minEnd = getDataItem(this.minEndIndex).getPeriod().getEnd().getTime();
  247. if (end < minEnd) {
  248. this.minEndIndex = index;
  249. }
  250. }
  251. else {
  252. this.minEndIndex = index;
  253. }
  254. if (this.maxEndIndex >= 0) {
  255. long maxEnd = getDataItem(this.maxEndIndex).getPeriod().getEnd().getTime();
  256. if (end > maxEnd) {
  257. this.maxEndIndex = index;
  258. }
  259. }
  260. else {
  261. this.maxEndIndex = index;
  262. }
  263. }
  264. /**
  265. * Recalculates the bounds for the collection of items.
  266. */
  267. private void recalculateBounds() {
  268. for (int i = 0; i < this.data.size(); i++) {
  269. TimePeriodValue tpv = (TimePeriodValue) this.data.get(i);
  270. updateBounds(tpv.getPeriod(), i);
  271. }
  272. }
  273. /**
  274. * Adds a new data item to the series.
  275. *
  276. * @param period the time period.
  277. * @param value the value.
  278. */
  279. public void add(TimePeriod period, double value) {
  280. TimePeriodValue item = new TimePeriodValue(period, value);
  281. add(item);
  282. }
  283. /**
  284. * Adds a new data item to the series.
  285. *
  286. * @param period the time period.
  287. * @param value the value.
  288. */
  289. public void add(TimePeriod period, Number value) {
  290. TimePeriodValue item = new TimePeriodValue(period, value);
  291. add(item);
  292. }
  293. /**
  294. * Updates (changes) the value of a data item.
  295. *
  296. * @param index the index of the data item to update.
  297. * @param value the new value.
  298. */
  299. public void update(int index, Number value) {
  300. TimePeriodValue item = getDataItem(index);
  301. item.setValue(value);
  302. fireSeriesChanged();
  303. }
  304. /**
  305. * Deletes data from start until end index (end inclusive).
  306. *
  307. * @param start the index of the first period to delete.
  308. * @param end the index of the last period to delete.
  309. */
  310. public void delete(int start, int end) {
  311. for (int i = 0; i <= (end - start); i++) {
  312. this.data.remove(start);
  313. }
  314. recalculateBounds();
  315. fireSeriesChanged();
  316. }
  317. /**
  318. * Tests the series for equality with another object.
  319. *
  320. * @param obj the object.
  321. *
  322. * @return <code>true</code> or <code>false</code>.
  323. */
  324. public boolean equals(Object obj) {
  325. if (obj == this) {
  326. return true;
  327. }
  328. if (!super.equals(obj)) {
  329. return false;
  330. }
  331. if (!(obj instanceof TimePeriodValues)) {
  332. return false;
  333. }
  334. TimePeriodValues tpvs = (TimePeriodValues) obj;
  335. if (!getDomainDescription().equals(tpvs.getDomainDescription())) {
  336. return false;
  337. }
  338. if (!getRangeDescription().equals(tpvs.getRangeDescription())) {
  339. return false;
  340. }
  341. int count = getItemCount();
  342. if (count != tpvs.getItemCount()) {
  343. return false;
  344. }
  345. for (int i = 0; i < count; i++) {
  346. if (!getDataItem(i).equals(tpvs.getDataItem(i))) {
  347. return false;
  348. }
  349. }
  350. return true;
  351. }
  352. /**
  353. * Returns a hash code value for the object.
  354. *
  355. * @return the hashcode
  356. */
  357. public int hashCode() {
  358. int result;
  359. result = (this.domain != null ? this.domain.hashCode() : 0);
  360. result = 29 * result + (this.range != null ? this.range.hashCode() : 0);
  361. result = 29 * result + this.data.hashCode();
  362. result = 29 * result + this.minStartIndex;
  363. result = 29 * result + this.maxStartIndex;
  364. result = 29 * result + this.minMiddleIndex;
  365. result = 29 * result + this.maxMiddleIndex;
  366. result = 29 * result + this.minEndIndex;
  367. result = 29 * result + this.maxEndIndex;
  368. return result;
  369. }
  370. /**
  371. * Returns a clone of the collection.
  372. * <P>
  373. * Notes:
  374. * <ul>
  375. * <li>
  376. * no need to clone the domain and range descriptions, since String object is immutable;
  377. * </li>
  378. * <li>
  379. * we pass over to the more general method createCopy(start, end).
  380. * </li>
  381. * </ul>
  382. *
  383. * @return a clone of the time series.
  384. *
  385. * @throws CloneNotSupportedException if there is a cloning problem.
  386. */
  387. public Object clone() throws CloneNotSupportedException {
  388. Object clone = createCopy(0, getItemCount() - 1);
  389. return clone;
  390. }
  391. /**
  392. * Creates a new instance by copying a subset of the data in this collection.
  393. *
  394. * @param start the index of the first item to copy.
  395. * @param end the index of the last item to copy.
  396. *
  397. * @return A copy of a subset of the items.
  398. *
  399. * @throws CloneNotSupportedException if there is a cloning problem.
  400. */
  401. public TimePeriodValues createCopy(int start, int end)
  402. throws CloneNotSupportedException {
  403. TimePeriodValues copy = (TimePeriodValues) super.clone();
  404. copy.data = new ArrayList();
  405. if (this.data.size() > 0) {
  406. for (int index = start; index <= end; index++) {
  407. TimePeriodValue item = (TimePeriodValue) this.data.get(index);
  408. TimePeriodValue clone = (TimePeriodValue) item.clone();
  409. try {
  410. copy.add(clone);
  411. }
  412. catch (SeriesException e) {
  413. System.err.println("TimePeriodValues.createCopy(): unable to add cloned item.");
  414. }
  415. }
  416. }
  417. return copy;
  418. }
  419. /**
  420. * Returns the index of the time period with the minimum start milliseconds.
  421. *
  422. * @return The index.
  423. */
  424. public int getMinStartIndex() {
  425. return this.minStartIndex;
  426. }
  427. /**
  428. * Returns the index of the time period with the maximum start milliseconds.
  429. *
  430. * @return The index.
  431. */
  432. public int getMaxStartIndex() {
  433. return this.maxStartIndex;
  434. }
  435. /**
  436. * Returns the index of the time period with the minimum middle milliseconds.
  437. *
  438. * @return The index.
  439. */
  440. public int getMinMiddleIndex() {
  441. return this.minMiddleIndex;
  442. }
  443. /**
  444. * Returns the index of the time period with the maximum middle milliseconds.
  445. *
  446. * @return The index.
  447. */
  448. public int getMaxMiddleIndex() {
  449. return this.maxMiddleIndex;
  450. }
  451. /**
  452. * Returns the index of the time period with the minimum end milliseconds.
  453. *
  454. * @return The index.
  455. */
  456. public int getMinEndIndex() {
  457. return this.minEndIndex;
  458. }
  459. /**
  460. * Returns the index of the time period with the maximum end milliseconds.
  461. *
  462. * @return The index.
  463. */
  464. public int getMaxEndIndex() {
  465. return this.maxEndIndex;
  466. }
  467. }