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. * TimeTableXYDataset.java
  28. * -----------------------
  29. * (C) Copyright 2004, 2005, by Andreas Schroeder and Contributors.
  30. *
  31. * Original Author: Andreas Schroeder;
  32. * Contributor(s): David Gilbert (for Object Refinery Limited);
  33. *
  34. * $Id: TimeTableXYDataset.java,v 1.9 2005/01/28 13:52:20 mungady Exp $
  35. *
  36. * Changes
  37. * -------
  38. * 01-Apr-2004 : Version 1 (AS);
  39. * 05-May-2004 : Now implements AbstractIntervalXYDataset (DG);
  40. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
  41. * getYValue() (DG);
  42. * 15-Sep-2004 : Added getXPosition(), setXPosition(), equals() and
  43. * clone() (DG);
  44. * 17-Nov-2004 : Updated methods for changes in DomainInfo interface (DG);
  45. * 25-Nov-2004 : Added getTimePeriod(int) method (DG);
  46. * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
  47. * release (DG);
  48. * 27-Jan-2005 : Modified to use TimePeriod rather than RegularTimePeriod (DG);
  49. *
  50. */
  51. package org.jfree.data.time;
  52. import java.util.Calendar;
  53. import java.util.List;
  54. import java.util.Locale;
  55. import java.util.TimeZone;
  56. import org.jfree.data.DefaultKeyedValues2D;
  57. import org.jfree.data.DomainInfo;
  58. import org.jfree.data.Range;
  59. import org.jfree.data.general.DatasetChangeEvent;
  60. import org.jfree.data.xy.AbstractIntervalXYDataset;
  61. import org.jfree.data.xy.IntervalXYDataset;
  62. import org.jfree.data.xy.TableXYDataset;
  63. import org.jfree.util.PublicCloneable;
  64. /**
  65. * A dataset for regular time periods that implements the
  66. * {@link TableXYDataset} interface.
  67. *
  68. * @see org.jfree.data.xy.TableXYDataset
  69. * @author andreas.schroeder
  70. */
  71. public class TimeTableXYDataset extends AbstractIntervalXYDataset
  72. implements Cloneable, PublicCloneable,
  73. IntervalXYDataset,
  74. DomainInfo,
  75. TableXYDataset {
  76. /**
  77. * The data structure to store the values. Each column represents
  78. * a series (elsewhere in JFreeChart rows are typically used for series,
  79. * but it doesn't matter that much since this data structure is private
  80. * and symmetrical anyway), each row contains values for the same
  81. * {@link RegularTimePeriod} (the rows are sorted into ascending order).
  82. */
  83. private DefaultKeyedValues2D values;
  84. /**
  85. * A flag that indicates that the domain is 'points in time'. If this flag
  86. * is true, only the x-value (and not the x-interval) is used to determine
  87. * the range of values in the domain.
  88. */
  89. private boolean domainIsPointsInTime;
  90. /**
  91. * The point within each time period that is used for the X value when this
  92. * collection is used as an {@link org.jfree.data.xy.XYDataset}. This can
  93. * be the start, middle or end of the time period.
  94. */
  95. private TimePeriodAnchor xPosition;
  96. /** A working calendar (to recycle) */
  97. private Calendar workingCalendar;
  98. /**
  99. * Creates a new dataset.
  100. */
  101. public TimeTableXYDataset() {
  102. // defer argument checking
  103. this(TimeZone.getDefault(), Locale.getDefault());
  104. }
  105. /**
  106. * Creates a new dataset with the given time zone.
  107. *
  108. * @param zone the time zone to use (<code>null</code> not permitted).
  109. */
  110. public TimeTableXYDataset(TimeZone zone) {
  111. // defer argument checking
  112. this(zone, Locale.getDefault());
  113. }
  114. /**
  115. * Creates a new dataset with the given time zone and locale.
  116. *
  117. * @param zone the time zone to use (<code>null</code> not permitted).
  118. * @param locale the locale to use (<code>null</code> not permitted).
  119. */
  120. public TimeTableXYDataset(TimeZone zone, Locale locale) {
  121. if (zone == null) {
  122. throw new IllegalArgumentException("Null 'zone' argument.");
  123. }
  124. if (locale == null) {
  125. throw new IllegalArgumentException("Null 'locale' argument.");
  126. }
  127. this.values = new DefaultKeyedValues2D(true);
  128. this.workingCalendar = Calendar.getInstance(zone, locale);
  129. this.xPosition = TimePeriodAnchor.START;
  130. }
  131. /**
  132. * Returns a flag that controls whether the domain is treated as 'points in
  133. * time'.
  134. * <P>
  135. * This flag is used when determining the max and min values for the domain.
  136. * If true, then only the x-values are considered for the max and min
  137. * values. If false, then the start and end x-values will also be taken
  138. * into consideration.
  139. *
  140. * @return The flag.
  141. */
  142. public boolean getDomainIsPointsInTime() {
  143. return this.domainIsPointsInTime;
  144. }
  145. /**
  146. * Sets a flag that controls whether the domain is treated as 'points in
  147. * time', or time periods. A {@link DatasetChangeEvent} is sent to all
  148. * registered listeners.
  149. *
  150. * @param flag the new value of the flag.
  151. */
  152. public void setDomainIsPointsInTime(boolean flag) {
  153. this.domainIsPointsInTime = flag;
  154. notifyListeners(new DatasetChangeEvent(this, this));
  155. }
  156. /**
  157. * Returns the position within each time period that is used for the X
  158. * value.
  159. *
  160. * @return The anchor position (never <code>null</code>).
  161. */
  162. public TimePeriodAnchor getXPosition() {
  163. return this.xPosition;
  164. }
  165. /**
  166. * Sets the position within each time period that is used for the X values,
  167. * then sends a {@link DatasetChangeEvent} to all registered listeners.
  168. *
  169. * @param anchor the anchor position (<code>null</code> not permitted).
  170. */
  171. public void setXPosition(TimePeriodAnchor anchor) {
  172. if (anchor == null) {
  173. throw new IllegalArgumentException("Null 'anchor' argument.");
  174. }
  175. this.xPosition = anchor;
  176. notifyListeners(new DatasetChangeEvent(this, this));
  177. }
  178. /**
  179. * Adds a new data item to the dataset and sends a
  180. * {@link org.jfree.data.general.DatasetChangeEvent} to all registered
  181. * listeners.
  182. *
  183. * @param period the time period.
  184. * @param y the value for this period.
  185. * @param seriesName the name of the series to add the value.
  186. */
  187. public void add(TimePeriod period, double y, String seriesName) {
  188. add(period, new Double(y), seriesName, true);
  189. }
  190. /**
  191. * Adds a new data item to the dataset.
  192. *
  193. * @param period the time period (<code>null</code> not permitted).
  194. * @param y the value for this period (<code>null</code> permitted).
  195. * @param seriesName the name of the series to add the value
  196. * (<code>null</code> not permitted).
  197. * @param notify whether dataset listener are notified or not.
  198. */
  199. public void add(TimePeriod period, Number y, String seriesName,
  200. boolean notify) {
  201. this.values.addValue(y, period, seriesName);
  202. if (notify) {
  203. fireDatasetChanged();
  204. }
  205. }
  206. /**
  207. * Removes an existing data item from the dataset.
  208. *
  209. * @param period the (existing!) time period of the value to remove
  210. * (<code>null</code> not permitted).
  211. * @param seriesName the (existing!) series name to remove the value
  212. * (<code>null</code> not permitted).
  213. */
  214. public void remove(TimePeriod period, String seriesName) {
  215. remove(period, seriesName, true);
  216. }
  217. /**
  218. * Removes an existing data item from the dataset.
  219. *
  220. * @param period the (existing!) time period of the value to remove
  221. * (<code>null</code> not permitted).
  222. * @param seriesName the (existing!) series name to remove the value
  223. * (<code>null</code> not permitted).
  224. * @param notify whether dataset listener are notified or not.
  225. */
  226. public void remove(TimePeriod period, String seriesName, boolean notify) {
  227. this.values.removeValue(period, seriesName);
  228. if (notify) {
  229. fireDatasetChanged();
  230. }
  231. }
  232. /**
  233. * Returns the time period for the specified item. Bear in mind that all
  234. * series share the same set of time periods.
  235. *
  236. * @param item the item index (0 <= i <= {@link #getItemCount()}).
  237. *
  238. * @return The time period.
  239. */
  240. public TimePeriod getTimePeriod(int item) {
  241. return (TimePeriod) this.values.getRowKey(item);
  242. }
  243. /**
  244. * Returns the number of items in ALL series.
  245. *
  246. * @return The item count.
  247. */
  248. public int getItemCount() {
  249. return this.values.getRowCount();
  250. }
  251. /**
  252. * Returns the number of items in a series. This is the same value
  253. * that is returned by {@link #getItemCount()} since all series
  254. * share the same x-values (time periods).
  255. *
  256. * @param series the series (zero-based index, ignored).
  257. *
  258. * @return The number of items within the series.
  259. */
  260. public int getItemCount(int series) {
  261. return getItemCount();
  262. }
  263. /**
  264. * Returns the number of series in the dataset.
  265. *
  266. * @return The series count.
  267. */
  268. public int getSeriesCount() {
  269. return this.values.getColumnCount();
  270. }
  271. /**
  272. * Returns the name of a series.
  273. *
  274. * @param series the series (zero-based index).
  275. *
  276. * @return The name of the series.
  277. */
  278. public String getSeriesName(int series) {
  279. return this.values.getColumnKey(series).toString();
  280. }
  281. /**
  282. * Returns the x-value for an item within a series. The x-values may or
  283. * may not be returned in ascending order, that is up to the class
  284. * implementing the interface.
  285. *
  286. * @param series the series (zero-based index).
  287. * @param item the item (zero-based index).
  288. *
  289. * @return The x-value.
  290. */
  291. public Number getX(int series, int item) {
  292. return new Double(getXValue(series, item));
  293. }
  294. /**
  295. * Returns the x-value (as a double primitive) for an item within a series.
  296. *
  297. * @param series the series index (zero-based).
  298. * @param item the item index (zero-based).
  299. *
  300. * @return The value.
  301. */
  302. public double getXValue(int series, int item) {
  303. TimePeriod period = (TimePeriod) this.values.getRowKey(item);
  304. return getXValue(period);
  305. }
  306. /**
  307. * Returns the starting X value for the specified series and item.
  308. *
  309. * @param series the series (zero-based index).
  310. * @param item the item within a series (zero-based index).
  311. *
  312. * @return The starting X value for the specified series and item.
  313. */
  314. public Number getStartX(int series, int item) {
  315. return new Double(getStartXValue(series, item));
  316. }
  317. /**
  318. * Returns the start x-value (as a double primitive) for an item within
  319. * a series.
  320. *
  321. * @param series the series index (zero-based).
  322. * @param item the item index (zero-based).
  323. *
  324. * @return The value.
  325. */
  326. public double getStartXValue(int series, int item) {
  327. TimePeriod period = (TimePeriod) this.values.getRowKey(item);
  328. return period.getStart().getTime();
  329. }
  330. /**
  331. * Returns the ending X value for the specified series and item.
  332. *
  333. * @param series the series (zero-based index).
  334. * @param item the item within a series (zero-based index).
  335. *
  336. * @return The ending X value for the specified series and item.
  337. */
  338. public Number getEndX(int series, int item) {
  339. return new Double(getEndXValue(series, item));
  340. }
  341. /**
  342. * Returns the end x-value (as a double primitive) for an item within
  343. * a series.
  344. *
  345. * @param series the series index (zero-based).
  346. * @param item the item index (zero-based).
  347. *
  348. * @return The value.
  349. */
  350. public double getEndXValue(int series, int item) {
  351. TimePeriod period = (TimePeriod) this.values.getRowKey(item);
  352. return period.getEnd().getTime();
  353. }
  354. /**
  355. * Returns the y-value for an item within a series.
  356. *
  357. * @param series the series (zero-based index).
  358. * @param item the item (zero-based index).
  359. *
  360. * @return The y-value (possibly <code>null</code>).
  361. */
  362. public Number getY(int series, int item) {
  363. return this.values.getValue(item, series);
  364. }
  365. /**
  366. * Returns the starting Y value for the specified series and item.
  367. *
  368. * @param series the series (zero-based index).
  369. * @param item the item within a series (zero-based index).
  370. *
  371. * @return The starting Y value for the specified series and item.
  372. */
  373. public Number getStartY(int series, int item) {
  374. return getY(series, item);
  375. }
  376. /**
  377. * Returns the ending Y value for the specified series and item.
  378. *
  379. * @param series the series (zero-based index).
  380. * @param item the item within a series (zero-based index).
  381. *
  382. * @return The ending Y value for the specified series and item.
  383. */
  384. public Number getEndY(int series, int item) {
  385. return getY(series, item);
  386. }
  387. /**
  388. * Returns the x-value for a time period.
  389. *
  390. * @param period the time period.
  391. *
  392. * @return The x-value.
  393. */
  394. private long getXValue(TimePeriod period) {
  395. long result = 0L;
  396. if (this.xPosition == TimePeriodAnchor.START) {
  397. result = period.getStart().getTime();
  398. }
  399. else if (this.xPosition == TimePeriodAnchor.MIDDLE) {
  400. long t0 = period.getStart().getTime();
  401. long t1 = period.getEnd().getTime();
  402. result = t0 + (t1 - t0) / 2L;
  403. }
  404. else if (this.xPosition == TimePeriodAnchor.END) {
  405. result = period.getEnd().getTime();
  406. }
  407. return result;
  408. }
  409. /**
  410. * Returns the minimum x-value in the dataset.
  411. *
  412. * @param includeInterval a flag that determines whether or not the
  413. * x-interval is taken into account.
  414. *
  415. * @return The minimum value.
  416. */
  417. public double getDomainLowerBound(boolean includeInterval) {
  418. double result = Double.NaN;
  419. Range r = getDomainBounds(includeInterval);
  420. if (r != null) {
  421. result = r.getLowerBound();
  422. }
  423. return result;
  424. }
  425. /**
  426. * Returns the maximum x-value in the dataset.
  427. *
  428. * @param includeInterval a flag that determines whether or not the
  429. * x-interval is taken into account.
  430. *
  431. * @return The maximum value.
  432. */
  433. public double getDomainUpperBound(boolean includeInterval) {
  434. double result = Double.NaN;
  435. Range r = getDomainBounds(includeInterval);
  436. if (r != null) {
  437. result = r.getUpperBound();
  438. }
  439. return result;
  440. }
  441. /**
  442. * Returns the range of the values in this dataset's domain.
  443. *
  444. * @param includeInterval a flag that controls whether or not the
  445. * x-intervals are taken into account.
  446. *
  447. * @return The range.
  448. */
  449. public Range getDomainBounds(boolean includeInterval) {
  450. List keys = this.values.getRowKeys();
  451. if (keys.isEmpty()) {
  452. return null;
  453. }
  454. TimePeriod first = (TimePeriod) keys.get(0);
  455. TimePeriod last = (TimePeriod) keys.get(keys.size() - 1);
  456. if (!includeInterval || this.domainIsPointsInTime) {
  457. return new Range(getXValue(first), getXValue(last));
  458. }
  459. else {
  460. return new Range(
  461. first.getStart().getTime(), last.getEnd().getTime()
  462. );
  463. }
  464. }
  465. /**
  466. * Tests this dataset for equality with an arbitrary object.
  467. *
  468. * @param obj the object (<code>null</code> permitted).
  469. *
  470. * @return A boolean.
  471. */
  472. public boolean equals(Object obj) {
  473. if (obj == this) {
  474. return true;
  475. }
  476. if (!(obj instanceof TimeTableXYDataset)) {
  477. return false;
  478. }
  479. TimeTableXYDataset that = (TimeTableXYDataset) obj;
  480. if (this.domainIsPointsInTime != that.domainIsPointsInTime) {
  481. return false;
  482. }
  483. if (this.xPosition != that.xPosition) {
  484. return false;
  485. }
  486. if (!this.workingCalendar.getTimeZone().equals(
  487. that.workingCalendar.getTimeZone())
  488. ) {
  489. return false;
  490. }
  491. if (!this.values.equals(that.values)) {
  492. return false;
  493. }
  494. return true;
  495. }
  496. /**
  497. * Returns a clone of this dataset.
  498. *
  499. * @return A clone.
  500. *
  501. * @throws CloneNotSupportedException if the dataset cannot be cloned.
  502. */
  503. public Object clone() throws CloneNotSupportedException {
  504. TimeTableXYDataset clone = (TimeTableXYDataset) super.clone();
  505. clone.values = (DefaultKeyedValues2D) this.values.clone();
  506. clone.workingCalendar = (Calendar) this.workingCalendar.clone();
  507. return clone;
  508. }
  509. }